From 6947c912a78079335d31d940f3e097d1751ec487 Mon Sep 17 00:00:00 2001 From: Scott Bell Date: Tue, 3 Oct 2023 00:28:02 +0200 Subject: [PATCH] Add markdown to notebook entries (#7084) * try marked out * fix url validation * now rendering blockquotes properly * add abbrv, link titles, and strikethrough * fix tests and lint * Closes #6060 - CSS resets and styling for markdown-related HTML markup in Notebook entries. - Better styling and cursor affordances for Notebook entry selection and editing interaction flow. * add line breaks option * Closes #6060 - Tab * Closes #6060 - Conversion of contenteditable-div to textarea started. - Stubbed in textarea with styles. * have it markdown with a textarea and adjust size automatically * Closes #6060 - Padding added back to text `div` area. * Closes #6060 - Styles added to support Shift Log and hover behavior for entries on locked pages. - Removed `--major` styling from Shift Log Commit Entries button to reduce confusion with entry commit button. - CSS code cleanups. * two step focus/edit. also scroll into view for editing * add markdown, strip all tags, and truncate * lint * remove unneeded code * fix notebook entry, selected page may also be null * fix existing notebook tests * lint * fix whitelist * readd whitelist * lint * fix link tests * fix tests * fix tagging test * add some markdown test * get rid of pause * add another sanitization step --------- Co-authored-by: Charles Hacskaylo Co-authored-by: Jesse Mazzella --- e2e/helper/notebookUtils.js | 4 +- .../plugins/notebook/notebook.e2e.spec.js | 50 +- .../notebook/notebookSnapshots.e2e.spec.js | 2 - .../plugins/notebook/tags.e2e.spec.js | 10 +- .../contract/notebook.contract.perf.spec.js | 5 +- package.json | 1 + src/plugins/notebook/components/Notebook.vue | 16 +- .../notebook/components/NotebookEntry.vue | 176 ++- src/plugins/notebook/pluginSpec.js | 6 +- src/styles/notebook.scss | 1357 +++++++++-------- .../layout/search/AnnotationSearchResult.vue | 73 +- 11 files changed, 960 insertions(+), 740 deletions(-) diff --git a/e2e/helper/notebookUtils.js b/e2e/helper/notebookUtils.js index 4e27da9b54..ae4e6bd39e 100644 --- a/e2e/helper/notebookUtils.js +++ b/e2e/helper/notebookUtils.js @@ -34,7 +34,8 @@ async function enterTextEntry(page, text) { await page.locator(NOTEBOOK_DROP_AREA).click(); // enter text - await page.locator('[aria-label="Notebook Entry"].is-selected div.c-ne__text').fill(text); + await page.getByLabel('Notebook Entry Display').last().click(); + await page.getByLabel('Notebook Entry Input').last().fill(text); await commitEntry(page); } @@ -52,7 +53,6 @@ async function dragAndDropEmbed(page, notebookObject) { await page.click('button[title="Show selected item in tree"]'); // Drag and drop the SWG into the notebook await page.dragAndDrop(`text=${swg.name}`, NOTEBOOK_DROP_AREA); - await commitEntry(page); } /** diff --git a/e2e/tests/functional/plugins/notebook/notebook.e2e.spec.js b/e2e/tests/functional/plugins/notebook/notebook.e2e.spec.js index 79fb500474..fe68487377 100644 --- a/e2e/tests/functional/plugins/notebook/notebook.e2e.spec.js +++ b/e2e/tests/functional/plugins/notebook/notebook.e2e.spec.js @@ -279,8 +279,8 @@ test.describe('Notebook entry tests', () => { // Click .c-notebook__drag-area await page.locator('.c-notebook__drag-area').click(); - await expect(page.locator('[aria-label="Notebook Entry Input"]')).toBeVisible(); - await expect(page.locator('[aria-label="Notebook Entry"]')).toHaveClass(/is-selected/); + await expect(page.getByLabel('Notebook Entry Display')).toBeVisible(); + await expect(page.getByLabel('Notebook Entry', { exact: true })).toHaveClass(/is-selected/); }); test('When an object is dropped into a notebook, a new entry is created and it should be focused @unstable', async ({ page @@ -369,6 +369,8 @@ test.describe('Notebook entry tests', () => { const validLink = page.locator(`a[href="${TEST_LINK}"]`); + expect(await validLink.count()).toBe(1); + // Start waiting for popup before clicking. Note no await. const popupPromise = page.waitForEvent('popup'); @@ -378,8 +380,6 @@ test.describe('Notebook entry tests', () => { // Wait for the popup to load. await popup.waitForLoadState(); expect.soft(popup.url()).toContain('www.google.com'); - - expect(await validLink.count()).toBe(1); }); test('when an invalid link is entered into a notebook entry, it does not become clickable when viewing', async ({ page @@ -447,6 +447,8 @@ test.describe('Notebook entry tests', () => { const validLink = page.locator(`a[href="${TEST_LINK}"]`); + expect(await validLink.count()).toBe(1); + // Start waiting for popup before clicking. Note no await. const popupPromise = page.waitForEvent('popup'); @@ -456,8 +458,6 @@ test.describe('Notebook entry tests', () => { // Wait for the popup to load. await popup.waitForLoadState(); expect.soft(popup.url()).toContain('www.google.com'); - - expect(await validLink.count()).toBe(1); }); test('when a nefarious link is entered into a notebook entry, it is sanitized when viewing', async ({ page @@ -482,4 +482,42 @@ test.describe('Notebook entry tests', () => { expect.soft(await sanitizedLink.count()).toBe(1); expect(await unsanitizedLink.count()).toBe(0); }); + test('Can add markdown to a notebook entry', async ({ page }) => { + await page.goto(notebookObject.url); + + // Headers + const headerMarkdown = `# Big Header\n## Large Header\n### Medium Header\n#### Small Header`; + await nbUtils.enterTextEntry(page, headerMarkdown); + await expect(page.getByRole('heading', { name: 'Big Header' })).toBeVisible(); + + // Text markup + const markupText = + '**This is bold.** _This is italic_. `This is code`. ~This is strikethrough~'; + await nbUtils.enterTextEntry(page, markupText); + await expect(page.locator('strong:has-text("This is bold.")')).toBeVisible(); + + // Tables + const tablesText = '|Col 1|Col 2|Col3|\n|-|-|-|\n |Value 1|Value 2|Value 3|\n'; + await nbUtils.enterTextEntry(page, tablesText); + await expect(page.getByRole('cell', { name: 'Value 2' })).toBeVisible(); + + // Links + const linksText = + 'Raw links https://www.google.com and Markdown links like [Google](https://www.google.com) work'; + await nbUtils.enterTextEntry(page, linksText); + await expect(page.getByRole('link', { name: 'https://www.google.com' })).toBeVisible(); + await expect(page.getByRole('link', { name: 'Google', exact: true })).toBeVisible(); + + // Lists + const listsText = '- List item 1\n - Item 1A \n- List Item 2\n 1. Order 1\n 1. Order 2\n'; + await nbUtils.enterTextEntry(page, listsText); + const childItem = page.locator('li:has-text("List Item 2") ol li:has-text("Order 2")'); + await expect(childItem).toBeVisible(); + + // Blocks + const blockTest = '```javascript\nconst foo = "bar";\nconst bar = "foo";\n```'; + await nbUtils.enterTextEntry(page, blockTest); + const codeBlock = page.locator('code.language-javascript:has-text("const foo = \\"bar\\";")'); + await expect(codeBlock).toBeVisible(); + }); }); diff --git a/e2e/tests/functional/plugins/notebook/notebookSnapshots.e2e.spec.js b/e2e/tests/functional/plugins/notebook/notebookSnapshots.e2e.spec.js index e0d76dc53f..e4b80b62ee 100644 --- a/e2e/tests/functional/plugins/notebook/notebookSnapshots.e2e.spec.js +++ b/e2e/tests/functional/plugins/notebook/notebookSnapshots.e2e.spec.js @@ -192,8 +192,6 @@ test.describe('Snapshot image tests', () => { // be sure that entry was created await expect(page.getByText('favicon-96x96.png')).toBeVisible(); - // click on image (need to click twice to focus) - await page.getByRole('img', { name: 'favicon-96x96.png thumbnail' }).click(); await page.getByRole('img', { name: 'favicon-96x96.png thumbnail' }).click(); // expect large image to be displayed diff --git a/e2e/tests/functional/plugins/notebook/tags.e2e.spec.js b/e2e/tests/functional/plugins/notebook/tags.e2e.spec.js index 586336f381..89f46c7d3c 100644 --- a/e2e/tests/functional/plugins/notebook/tags.e2e.spec.js +++ b/e2e/tests/functional/plugins/notebook/tags.e2e.spec.js @@ -147,16 +147,14 @@ test.describe('Tagging in Notebooks @addInit', () => { type: 'issue', description: 'https://github.com/nasa/openmct/issues/5823' }); - await createNotebookEntryAndTags(page); await page.locator('text=To start a new entry, click here or drag and drop any object').click(); - const entryLocator = `[aria-label="Notebook Entry Input"] >> nth = 1`; - await page.locator(entryLocator).click(); - await page.locator(entryLocator).fill(`An entry without tags`); - await page.locator('[aria-label="Notebook Entry Input"] >> nth=1').press('Enter'); + await page.getByLabel('Notebook Entry Display').last().click(); + await page.getByLabel('Notebook Entry Input').fill(`An entry without tags`); + await page.locator('.c-ne__save-button > button').click(); - await page.hover('[aria-label="Notebook Entry Input"] >> nth=1'); + await page.hover('[aria-label="Notebook Entry Display"] >> nth=1'); await page.locator('button[title="Delete this entry"]').last().click(); await expect( page.locator('text=This action will permanently delete this entry. Do you wish to continue?') diff --git a/e2e/tests/performance/contract/notebook.contract.perf.spec.js b/e2e/tests/performance/contract/notebook.contract.perf.spec.js index 4402e6960e..a2db7a0f3c 100644 --- a/e2e/tests/performance/contract/notebook.contract.perf.spec.js +++ b/e2e/tests/performance/contract/notebook.contract.perf.spec.js @@ -131,8 +131,9 @@ test.describe('Performance tests', () => { await page.evaluate(() => window.performance.mark('new-notebook-entry-created')); // Enter Notebook Entry text - await page.locator('div.c-ne__text').last().fill('New Entry'); - await page.keyboard.press('Enter'); + await page.getByLabel('Notebook Entry').last().click(); + await page.getByLabel('Notebook Entry Input').last().fill('New Entry'); + await page.locator('.c-ne__save-button').click(); await page.evaluate(() => window.performance.mark('new-notebook-entry-filled')); //Individual Notebook Entry Search diff --git a/package.json b/package.json index ef67d55b1b..837f29e4cd 100644 --- a/package.json +++ b/package.json @@ -52,6 +52,7 @@ "karma-webpack": "5.0.0", "location-bar": "3.0.1", "lodash": "4.17.21", + "marked": "9.0.3", "mini-css-extract-plugin": "2.7.6", "moment": "2.29.4", "moment-duration-format": "2.3.2", diff --git a/src/plugins/notebook/components/Notebook.vue b/src/plugins/notebook/components/Notebook.vue index fb65a2aaf0..eb4edde72f 100644 --- a/src/plugins/notebook/components/Notebook.vue +++ b/src/plugins/notebook/components/Notebook.vue @@ -19,7 +19,6 @@ this source code distribution or the Licensing information page available at runtime from the About dialog for additional information. --> -