diff --git a/app/client/ui/DocTour.ts b/app/client/ui/DocTour.ts index d1c2385f5c..61d5a6e884 100644 --- a/app/client/ui/DocTour.ts +++ b/app/client/ui/DocTour.ts @@ -10,6 +10,9 @@ import {IconList, IconName} from 'app/client/ui2018/IconList'; import {DocData} from 'app/common/DocData'; import {dom} from 'grainjs'; import sortBy = require('lodash/sortBy'); +import {marked} from "marked"; +import {renderer} from 'app/client/ui/DocTourRenderer'; +import {sanitizeTourHTML} from "app/client/ui/sanitizeHTML"; const t = makeT('DocTour'); @@ -44,17 +47,22 @@ async function makeDocTour(docData: DocData, docComm: DocComm): Promise + gristIconLink(constructUrl(href), text).outerHTML; + +renderer.html = ({raw}) => escape(raw); diff --git a/app/client/ui/sanitizeHTML.ts b/app/client/ui/sanitizeHTML.ts index 31d6f7e261..f506b67a99 100644 --- a/app/client/ui/sanitizeHTML.ts +++ b/app/client/ui/sanitizeHTML.ts @@ -26,6 +26,19 @@ export function sanitizeHTMLIntoDOM(source: string | Node): DocumentFragment { } } +export function sanitizeTourHTML(source: string | Node): DocumentFragment { + return tourPurifier.sanitize(source, { + RETURN_DOM_FRAGMENT: true, + ALLOWED_TAGS: [ + 'p', 'span', 'h1', 'h2', 'h3', 'h4', 'table', 'tr', 'td', + 'strong', 'em', 'bold', 'code', 'pre', 'blockquote', 'ul', 'ol', 'li', 'del', + 'br', 'img', 'iframe', 'a' + ], + ADD_ATTR: ['allowFullscreen'] + }); + +} + export function sanitizeTutorialHTML(source: string | Node): string { return tutorialPurifier.sanitize(source, { ADD_TAGS: ['iframe'], @@ -35,6 +48,7 @@ export function sanitizeTutorialHTML(source: string | Node): string { const defaultPurifier = createDOMPurifier(); const tutorialPurifier = createDOMPurifier(); +const tourPurifier = createDOMPurifier(); // If we are executed in a browser, we can add hooks to the purifiers to customize their behavior. // But sometimes this code is included in tests, where `window` is not defined. diff --git a/test/fixtures/docs/doctour.grist b/test/fixtures/docs/doctour.grist index 9c95eeb874..d939024129 100644 Binary files a/test/fixtures/docs/doctour.grist and b/test/fixtures/docs/doctour.grist differ diff --git a/test/nbrowser/DocTour.ts b/test/nbrowser/DocTour.ts index 1d20a0b260..4923ba5624 100644 --- a/test/nbrowser/DocTour.ts +++ b/test/nbrowser/DocTour.ts @@ -47,7 +47,9 @@ describe('DocTour', function () { const docTour = await driver.executeScript('return window._gristDocTour()'); assert.deepEqual(docTour, [ { - body: 'General Kenobi!', + body: '' + + '

General Kenobi!

\n' + + '
', placement: 'bottom', selector: '.active_cursor', showHasModal: false, @@ -55,7 +57,9 @@ describe('DocTour', function () { urlState: {colRef: 2, rowId: 2, sectionId: 1} }, { - body: 'no title', + body: '' + + '

no title

\n' + + '
', placement: 'auto', selector: '.active_cursor', showHasModal: true, @@ -63,15 +67,15 @@ describe('DocTour', function () { urlState: null, }, { - body: '
' + - '

' + + body: '' + + '' + '

' + - '
', + '', placement: 'auto', selector: '.active_cursor', showHasModal: false, @@ -79,19 +83,139 @@ describe('DocTour', function () { urlState: {colRef: 4, rowId: 4, sectionId: 1} }, { - body: '
' + - '

Good riddance

' + + body: '' + + '

Good riddance

\n
' + '

' + - '
', + '', placement: 'auto', selector: '.active_cursor', showHasModal: true, title: 'Bye', urlState: null, + }, + { + body: '

' + + 'bold' + + ' italicized' + + ' code' + + ' strikethrough' + + '

\n' + + '
', + placement: 'auto', + selector: '.active_cursor', + showHasModal: true, + title: 'Markdown formating', + urlState: null, + }, + { + body: '

' + + '\"Grist' + + '

\n
', + + + placement: 'auto', + selector: '.active_cursor', + showHasModal: false, + title: 'Markdown image', + urlState: { + "colRef": 8, + "rowId": 8, + "sectionId": 1 + }, + }, + { + body: '' + + '

H1

\n

H2

\n

H3

\n' + + '
', + placement: 'auto', + selector: '.active_cursor', + showHasModal: true, + title: 'Markdown headings', + urlState: null, + }, + { + body: '' + + '
    \n
  1. First item
  2. \n
  3. Second item
  4. \n
  5. Third item
  6. \n
\n' + + '
', + placement: 'auto', + selector: '.active_cursor', + showHasModal: false, + title: 'Markdown ordered list', + urlState: { + "colRef": 10, + "rowId": 10, + "sectionId": 1 + }, + }, + { + body: '' + + '\n' + + '', + placement: 'auto', + selector: '.active_cursor', + showHasModal: true, + title: 'Markdown unordered list', + urlState: null, + }, + { + body: '' + + '
\n

blockquote

\n
\n' + + '
', + placement: 'auto', + selector: '.active_cursor', + showHasModal: false, + title: 'Markdown quote', + urlState: { + "colRef": 12, + "rowId": 12, + "sectionId": 1 + }, + }, + { + body: '' + + '
{\n  ' +
+          '"firstName": "John",\n  "lastName": "Smith",\n  "age": 25\n' +
+          '}\n' +
+          '
\n' + + '
', + placement: 'auto', + selector: '.active_cursor', + showHasModal: true, + title: 'Markdown code block', + urlState: null + }, + { + body: '' + + '

title

\n' + + '
', + placement: 'auto', + selector: '.active_cursor', + showHasModal: false, + title: 'Markdown link', + urlState: { + "colRef": 14, + "rowId": 14, + "sectionId": 1 + }, + }, + { + body: '' + + '

Do not render HTML only Markdown

\n

This is a H1 Title

\n' + + '

Previous SHOULD BE a level 1 Title.

\n' + + '

Next should NOT BE a level 1 Title :

\n' + + '<H1>Level 1 title</H1> \n\n' + + '

The HTML <H1> within this text should not be rendered but the previous Markdown # should

\n' + + '

This is another test where the following text SHOULD NOT BE BOLD : <b>should just render the tags without bold </b>

\n' + + '
', + placement: 'auto', + selector: '.active_cursor', + showHasModal: true, + title: 'Markdown with HTML', + urlState: null } ]); }); @@ -154,7 +278,9 @@ describe('DocTour', function () { async function checkDocTourPresent() { // Check the expected message. - assert.match(await driver.findWait('.test-onboarding-popup', 500).getText(), /General Kenobi/); + assert.isTrue(await driver.findWait('.test-onboarding-popup', 500) + .findContent('div', 'General Kenobi!').isPresent()); + // Check that there is only one popup, and no errors. assert.lengthOf(await driver.findAll('.test-onboarding-popup'), 1); await gu.checkForErrors();