diff --git a/app/assets/javascripts/content_editor/extensions/bullet_list.js b/app/assets/javascripts/content_editor/extensions/bullet_list.js index dfd9cac4c6611063d8e5cdc10973bd37f5ac4f99..148d0df49fc6a6dd62b2e9ad56785d485fb9ec62 100644 --- a/app/assets/javascripts/content_editor/extensions/bullet_list.js +++ b/app/assets/javascripts/content_editor/extensions/bullet_list.js @@ -18,7 +18,7 @@ export default BulletList.extend({ bullet: { default: '*', parseHTML(element) { - const bullet = getMarkdownSource(element)?.charAt(0); + const bullet = getMarkdownSource(element)?.trim().charAt(0); return '*+-'.includes(bullet) ? bullet : '*'; }, diff --git a/app/assets/javascripts/content_editor/extensions/ordered_list.js b/app/assets/javascripts/content_editor/extensions/ordered_list.js index d0b760010de1696a8b01bee80bd1ae410b613acd..221ce0af86b1a73bf53c1bf1fd7b725a8b53e3a5 100644 --- a/app/assets/javascripts/content_editor/extensions/ordered_list.js +++ b/app/assets/javascripts/content_editor/extensions/ordered_list.js @@ -17,7 +17,7 @@ export default OrderedList.extend({ parens: { default: false, - parseHTML: (element) => /^[0-9]+\)/.test(getMarkdownSource(element)), + parseHTML: (element) => /^[0-9]+\)/.test(getMarkdownSource(element)?.trim()), }, }; }, diff --git a/app/assets/javascripts/content_editor/extensions/sourcemap.js b/app/assets/javascripts/content_editor/extensions/sourcemap.js index f02d0c2ca52a89d438c05f57772222949eccef28..d982f54785e0784528702467f22d4998bb598bb7 100644 --- a/app/assets/javascripts/content_editor/extensions/sourcemap.js +++ b/app/assets/javascripts/content_editor/extensions/sourcemap.js @@ -1,4 +1,5 @@ import { Extension } from '@tiptap/core'; +import { getMarkdownSource, docHasSourceMap } from '../services/markdown_sourcemap'; import Audio from './audio'; import Blockquote from './blockquote'; import Bold from './bold'; @@ -34,6 +35,8 @@ export default Extension.create({ name: 'sourcemap', addGlobalAttributes() { + const preserveMarkdown = () => gon.features?.preserveMarkdown; + return [ { types: [ @@ -77,10 +80,18 @@ export default Extension.create({ */ sourceMarkdown: { default: null, + parseHTML: (element) => preserveMarkdown() && getMarkdownSource(element), renderHTML: () => '', }, sourceMapKey: { default: null, + parseHTML: (element) => preserveMarkdown() && element.dataset.sourcepos, + renderHTML: () => '', + }, + sourceTagName: { + default: null, + parseHTML: (element) => + preserveMarkdown() && docHasSourceMap(element) && element.tagName.toLowerCase(), renderHTML: () => '', }, }, diff --git a/app/assets/javascripts/content_editor/extensions/task_list.js b/app/assets/javascripts/content_editor/extensions/task_list.js index a6394ac23c8663aec5d89841f81aeec9c3240dec..864ece017bd99df756251d4fb98243650b4758c6 100644 --- a/app/assets/javascripts/content_editor/extensions/task_list.js +++ b/app/assets/javascripts/content_editor/extensions/task_list.js @@ -30,7 +30,7 @@ export default TaskList.extend({ bullet: { default: '*', parseHTML(element) { - const bullet = getMarkdownSource(element)?.charAt(0); + const bullet = getMarkdownSource(element)?.trim().charAt(0); return '*+-'.includes(bullet) ? bullet : '*'; }, }, diff --git a/app/assets/javascripts/content_editor/services/markdown_sourcemap.js b/app/assets/javascripts/content_editor/services/markdown_sourcemap.js index de230c370b1fd104f095cbb45f7ab13182652779..206a9a27a218e0c7030ef32dcbfdc0eff96ac090 100644 --- a/app/assets/javascripts/content_editor/services/markdown_sourcemap.js +++ b/app/assets/javascripts/content_editor/services/markdown_sourcemap.js @@ -1,5 +1,10 @@ import { isString } from 'lodash'; +export const docHasSourceMap = (element) => { + const commentNode = element.ownerDocument.body.lastChild; + return Boolean(commentNode?.nodeName === '#comment' && isString(commentNode.textContent)); +}; + export const getFullSource = (element) => { const commentNode = element.ownerDocument.body.lastChild; @@ -32,14 +37,26 @@ export const getMarkdownSource = (element) => { if (!source.length) return undefined; for (let i = range.start.row; i <= range.end.row; i += 1) { - if (i === range.start.row) { - elSource += source[i].substring(range.start.col); + if (i === range.start.row && i === range.end.row) { + // include leading whitespace in the sourcemap + if (!source[i]?.substring(0, range.start.col).trim()) { + range.start.col = 0; + } + elSource += source[i].substring(range.start.col, range.end.col + 1); + } else if (i === range.start.row) { + // include leading whitespace in the sourcemap + if (!source[i]?.substring(0, range.start.col).trim()) { + range.start.col = 0; + } + elSource += source[i]?.substring(range.start.col) || ''; + } else if (i === range.end.row) { + elSource += `\n${source[i]?.substring(0, range.end.col + 1) || ''}`; } else { elSource += `\n${source[i]}` || ''; } } - return elSource.trim(); + return elSource; } catch { return undefined; } diff --git a/app/assets/javascripts/content_editor/services/serialization_helpers.js b/app/assets/javascripts/content_editor/services/serialization_helpers.js index e81b0c3f0e0917cb3b9801825e4d4925db7e1dcb..c2ec6b911752db8f88dd13008f2c3dcf2bcc3161 100644 --- a/app/assets/javascripts/content_editor/services/serialization_helpers.js +++ b/app/assets/javascripts/content_editor/services/serialization_helpers.js @@ -5,7 +5,7 @@ const defaultAttrs = { th: { colspan: 1, rowspan: 1, colwidth: null, align: 'left' }, }; -const defaultIgnoreAttrs = ['sourceMarkdown', 'sourceMapKey']; +const defaultIgnoreAttrs = ['sourceMarkdown', 'sourceMapKey', 'sourceTagName']; const ignoreAttrs = { dd: ['isTerm'], diff --git a/app/controllers/concerns/wiki_actions.rb b/app/controllers/concerns/wiki_actions.rb index 30c2e516b337b0a33bc5c0ed5f2eb620255db23b..59502ade012cd0db21207c5f615c70b484fc278a 100644 --- a/app/controllers/concerns/wiki_actions.rb +++ b/app/controllers/concerns/wiki_actions.rb @@ -32,6 +32,10 @@ module WikiActions before_action :page, only: [:show, :edit, :update, :history, :destroy, :diff] before_action :load_sidebar, except: [:pages] + before_action do + push_frontend_feature_flag(:preserve_markdown, container) + end + before_action only: [:show, :edit, :update] do @valid_encoding = valid_encoding? end diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index 275bdca6e0518c91a0889d1fce87ce1cdc0606e6..6667945f7b54ee5673b0e71eeafca7be3cede9dc 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -44,6 +44,7 @@ class Projects::IssuesController < Projects::ApplicationController before_action :authorize_read_code!, only: [:related_branches] before_action do + push_frontend_feature_flag(:preserve_markdown, project) push_frontend_feature_flag(:issues_grid_view) push_frontend_feature_flag(:service_desk_ticket) push_frontend_feature_flag(:issues_list_drawer, project) diff --git a/config/feature_flags/development/preserve_markdown.yml b/config/feature_flags/development/preserve_markdown.yml new file mode 100644 index 0000000000000000000000000000000000000000..9483654f8d503e16c49617ed1b89fd7632c7b181 --- /dev/null +++ b/config/feature_flags/development/preserve_markdown.yml @@ -0,0 +1,8 @@ +--- +name: preserve_markdown +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/160709 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/474407 +milestone: '17.3' +type: development +group: group::knowledge +default_enabled: false diff --git a/ee/app/controllers/groups/epics_controller.rb b/ee/app/controllers/groups/epics_controller.rb index 53bef6b1d4427db6d9a0d3a5c53b2d6c716d5a4e..a66d87e03e6c6d8b29cc1872c25c634405bf94bd 100644 --- a/ee/app/controllers/groups/epics_controller.rb +++ b/ee/app/controllers/groups/epics_controller.rb @@ -18,6 +18,7 @@ class Groups::EpicsController < Groups::ApplicationController after_action :log_epic_show, only: :show before_action do + push_frontend_feature_flag(:preserve_markdown, @group) push_frontend_feature_flag(:notifications_todos_buttons, current_user) push_frontend_feature_flag(:namespace_level_work_items, @group) push_force_frontend_feature_flag(:namespace_level_work_items, epic_work_items_enabled?) diff --git a/spec/frontend/content_editor/services/markdown_sourcemap_spec.js b/spec/frontend/content_editor/services/markdown_sourcemap_spec.js index f1eb361782f549335271b049e490eb19f77dcd5b..96663881eccabd4398d8494bc83d1f4835ed97e2 100644 --- a/spec/frontend/content_editor/services/markdown_sourcemap_spec.js +++ b/spec/frontend/content_editor/services/markdown_sourcemap_spec.js @@ -1,87 +1,55 @@ import { builders } from 'prosemirror-test-builder'; -import { Extension } from '@tiptap/core'; +import Bold from '~/content_editor/extensions/bold'; import BulletList from '~/content_editor/extensions/bullet_list'; +import Code from '~/content_editor/extensions/code'; +import Italic from '~/content_editor/extensions/italic'; import ListItem from '~/content_editor/extensions/list_item'; import TaskList from '~/content_editor/extensions/task_list'; import TaskItem from '~/content_editor/extensions/task_item'; -import Paragraph from '~/content_editor/extensions/paragraph'; +import Sourcemap from '~/content_editor/extensions/sourcemap'; +import Strike from '~/content_editor/extensions/strike'; import markdownDeserializer from '~/content_editor/services/gl_api_markdown_deserializer'; -import { getMarkdownSource, getFullSource } from '~/content_editor/services/markdown_sourcemap'; +import { getFullSource } from '~/content_editor/services/markdown_sourcemap'; import { createTestEditor } from '../test_utils'; - -const BULLET_LIST_MARKDOWN = `+ list item 1 -+ list item 2 - - embedded list item 3`; -const BULLET_LIST_HTML = `<ul data-sourcepos="1:1-3:24" dir="auto"> - <li data-sourcepos="1:1-1:13">list item 1</li> - <li data-sourcepos="2:1-3:24">list item 2 - <ul data-sourcepos="3:3-3:24"> - <li data-sourcepos="3:3-3:24">embedded list item 3</li> - </ul> - </li> -</ul>`; - -const MALFORMED_BULLET_LIST_HTML = - `<ul data-sourcepos="1:1-3:24" dir="auto"> - <li data-sourcepos="1:1-1:13">list item 1</li>` + - // below line has malformed sourcepos - `<li data-sourcepos="5:1-5:24">list item 2 - <ul data-sourcepos="3:3-3:24"> - <li data-sourcepos="3:3-3:24">embedded list item 3</li> - </ul> - </li> -</ul>`; - -const BULLET_TASK_LIST_MARKDOWN = `- [ ] list item 1 -+ [x] checked list item 2 - + [ ] embedded list item 1 - - [x] checked embedded list item 2`; -const BULLET_TASK_LIST_HTML = `<ul data-sourcepos="1:1-4:36" class="task-list" dir="auto"> - <li data-sourcepos="1:1-1:17" class="task-list-item"><input type="checkbox" class="task-list-item-checkbox"> list item 1</li> - <li data-sourcepos="2:1-4:36" class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" checked> checked list item 2 - <ul data-sourcepos="3:3-4:36" class="task-list"> - <li data-sourcepos="3:3-3:28" class="task-list-item"><input type="checkbox" class="task-list-item-checkbox"> embedded list item 1</li> - <li data-sourcepos="4:3-4:36" class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" checked> checked embedded list item 2</li> - </ul> - </li> -</ul>`; - -const SourcemapExtension = Extension.create({ - // lets add `source` attribute to every element using `getMarkdownSource` - addGlobalAttributes() { - return [ - { - types: [Paragraph.name, BulletList.name, ListItem.name], - attributes: { - source: { - parseHTML: (element) => { - const source = getMarkdownSource(element); - return source; - }, - }, - }, - }, - ]; - }, -}); +import { + BULLET_LIST_MARKDOWN, + BULLET_LIST_HTML, + MALFORMED_BULLET_LIST_HTML, + BULLET_TASK_LIST_MARKDOWN, + BULLET_TASK_LIST_HTML, + PARAGRAPHS_MARKDOWN, + PARAGRAPHS_HTML, +} from '../test_constants'; const tiptapEditor = createTestEditor({ - extensions: [BulletList, ListItem, TaskList, TaskItem, SourcemapExtension], + extensions: [BulletList, ListItem, TaskList, TaskItem, Sourcemap, Bold, Italic, Strike, Code], }); -const { doc, bulletList, listItem, taskList, taskItem, paragraph } = builders(tiptapEditor.schema); +const { doc, bulletList, listItem, taskList, taskItem, paragraph, bold, italic, strike, code } = + builders(tiptapEditor.schema); + +const text = (val) => tiptapEditor.state.schema.text(val); + +const sourceAttrs = jest.fn().mockImplementation((sourceTagName, sourceMapKey, sourceMarkdown) => ({ + sourceTagName, + sourceMapKey, + sourceMarkdown, +})); const bulletListDoc = () => doc( bulletList( - { bullet: '+', source: '+ list item 1\n+ list item 2\n - embedded list item 3' }, - listItem({ source: '+ list item 1' }, paragraph('list item 1')), + { bullet: '+', ...sourceAttrs('ul', '1:1-3:24', BULLET_LIST_MARKDOWN) }, + listItem(sourceAttrs('li', '1:1-1:13', '+ list item 1'), paragraph('list item 1')), listItem( - { source: '+ list item 2\n - embedded list item 3' }, + sourceAttrs('li', '2:1-3:24', '+ list item 2\n - embedded list item 3'), paragraph('list item 2'), bulletList( - { bullet: '-', source: '- embedded list item 3' }, - listItem({ source: '- embedded list item 3' }, paragraph('embedded list item 3')), + { bullet: '-', ...sourceAttrs('ul', '3:3-3:24', ' - embedded list item 3') }, + listItem( + sourceAttrs('li', '3:3-3:24', ' - embedded list item 3'), + paragraph('embedded list item 3'), + ), ), ), ), @@ -90,13 +58,18 @@ const bulletListDoc = () => const bulletListDocWithMalformedSourcepos = () => doc( bulletList( - { bullet: '+', source: '+ list item 1\n+ list item 2\n - embedded list item 3' }, - listItem({ source: '+ list item 1' }, paragraph('list item 1')), + { bullet: '+', ...sourceAttrs('ul', '1:1-3:24', BULLET_LIST_MARKDOWN) }, + listItem(sourceAttrs('li', '1:1-1:13', '+ list item 1'), paragraph('list item 1')), listItem( + // source not included for out of bounds list item here + sourceAttrs('li', '5:1-5:24'), paragraph('list item 2'), bulletList( - { bullet: '-', source: '- embedded list item 3' }, - listItem({ source: '- embedded list item 3' }, paragraph('embedded list item 3')), + { bullet: '-', ...sourceAttrs('ul', '3:3-3:24', ' - embedded list item 3') }, + listItem( + sourceAttrs('li', '3:3-3:24', ' - embedded list item 3'), + paragraph('embedded list item 3'), + ), ), ), ), @@ -105,27 +78,36 @@ const bulletListDocWithMalformedSourcepos = () => const bulletTaskListDoc = () => doc( taskList( - { - bullet: '-', - source: - '- [ ] list item 1\n+ [x] checked list item 2\n + [ ] embedded list item 1\n - [x] checked embedded list item 2', - }, - taskItem({ source: '- [ ] list item 1' }, paragraph('list item 1')), + { bullet: '-', ...sourceAttrs('ul', '1:1-4:36', BULLET_TASK_LIST_MARKDOWN) }, + taskItem(sourceAttrs('li', '1:1-1:17', '- [ ] list item 1'), paragraph('list item 1')), taskItem( { - source: + ...sourceAttrs( + 'li', + '2:1-4:36', '+ [x] checked list item 2\n + [ ] embedded list item 1\n - [x] checked embedded list item 2', + ), checked: true, }, paragraph('checked list item 2'), taskList( { bullet: '+', - source: '+ [ ] embedded list item 1\n - [x] checked embedded list item 2', + ...sourceAttrs( + 'ul', + '3:3-4:36', + ' + [ ] embedded list item 1\n - [x] checked embedded list item 2', + ), }, - taskItem({ source: '+ [ ] embedded list item 1' }, paragraph('embedded list item 1')), taskItem( - { source: '- [x] checked embedded list item 2', checked: true }, + sourceAttrs('li', '3:3-3:28', ' + [ ] embedded list item 1'), + paragraph('embedded list item 1'), + ), + taskItem( + { + ...sourceAttrs('li', '4:3-4:36', ' - [x] checked embedded list item 2'), + checked: true, + }, paragraph('checked embedded list item 2'), ), ), @@ -133,6 +115,62 @@ const bulletTaskListDoc = () => ), ); +const paragraphsDoc = () => + doc( + paragraph( + sourceAttrs( + 'p', + '1:1-1:233', + 'You could bold with **asterisks** or you could bold with __underscores__. You could even bold with <strong>strong</strong> or <b>b</b> html tags. You could add newlines in your paragraph, or `code` tags with `` nested `backticks` ``.', + ), + text('You could bold with '), + bold(sourceAttrs('strong', '1:21-1:33', '**asterisks**'), 'asterisks'), + text(' or you could bold with '), + bold(sourceAttrs('strong', '1:58-1:72', '__underscores__'), 'underscores'), + text('. You could even bold with '), + bold(sourceAttrs('strong'), 'strong'), + text(' or '), + bold(sourceAttrs('b'), 'b'), + text(' html tags. You could add newlines in your paragraph, or '), + code(sourceAttrs('code', '1:193-1:196', 'code'), 'code'), + text(' tags with '), + code(sourceAttrs('code', '1:211-1:230', ' nested `backticks` '), 'nested `backticks`'), + text('.'), + ), + paragraph( + sourceAttrs( + 'p', + '3:1-3:144', + 'You could italicise with *asterisks* or you could italicise with _underscores_. You could even italicise with <em>em</em> or <i>i</i> html tags.', + ), + text('You could italicise with '), + italic(sourceAttrs('em', '3:26-3:36', '*asterisks*'), 'asterisks'), + text(' or you could italicise with '), + italic(sourceAttrs('em', '3:66-3:78', '_underscores_'), 'underscores'), + text('. You could even italicise with '), + italic(sourceAttrs('em'), 'em'), + text(' or '), + italic(sourceAttrs('i'), 'i'), + text(' html tags.'), + ), + paragraph( + sourceAttrs( + 'p', + '5:1-5:154', + "As long as you don't touch a paragraph, it will ~~discard~~ <s>destroy</s> <del>delete</del> <strike>remove</strike> preserve the original markdown style.", + ), + text("As long as you don't touch a paragraph, it will "), + strike(sourceAttrs('del', '5:49-5:59', '~~discard~~'), 'discard'), + text(' '), + strike({ ...sourceAttrs('s'), htmlTag: 's' }, 'destroy'), + text(' '), + strike(sourceAttrs('del'), 'delete'), + text(' '), + strike({ ...sourceAttrs('strike'), htmlTag: 'strike' }, 'remove'), + text(' preserve the original markdown style.'), + ), + ); + describe('content_editor/services/markdown_sourcemap', () => { describe('getFullSource', () => { it.each` @@ -154,24 +192,63 @@ describe('content_editor/services/markdown_sourcemap', () => { }); }); - it.each` - description | sourceMarkdown | sourceHTML | expectedDoc - ${'bullet list'} | ${BULLET_LIST_MARKDOWN} | ${BULLET_LIST_HTML} | ${bulletListDoc} - ${'bullet list with malformed sourcepos'} | ${BULLET_LIST_MARKDOWN} | ${MALFORMED_BULLET_LIST_HTML} | ${bulletListDocWithMalformedSourcepos} - ${'bullet task list'} | ${BULLET_TASK_LIST_MARKDOWN} | ${BULLET_TASK_LIST_HTML} | ${bulletTaskListDoc} - `( - 'gets markdown source for a rendered $description', - async ({ sourceMarkdown, sourceHTML, expectedDoc }) => { - const { document } = await markdownDeserializer({ - render: () => ({ - body: sourceHTML, - }), - }).deserialize({ - schema: tiptapEditor.schema, - markdown: sourceMarkdown, - }); - - expect(document.toJSON()).toEqual(expectedDoc().toJSON()); - }, - ); + describe('when preserveMarkdown feature is enabled', () => { + beforeEach(() => { + gon.features = { preserveMarkdown: true }; + }); + + afterEach(() => { + gon.features = {}; + }); + + it.each` + description | sourceMarkdown | sourceHTML | expectedDoc + ${'bullet list'} | ${BULLET_LIST_MARKDOWN} | ${BULLET_LIST_HTML} | ${bulletListDoc} + ${'bullet list with malformed sourcepos'} | ${BULLET_LIST_MARKDOWN} | ${MALFORMED_BULLET_LIST_HTML} | ${bulletListDocWithMalformedSourcepos} + ${'bullet task list'} | ${BULLET_TASK_LIST_MARKDOWN} | ${BULLET_TASK_LIST_HTML} | ${bulletTaskListDoc} + ${'paragraphs with inline elements'} | ${PARAGRAPHS_MARKDOWN} | ${PARAGRAPHS_HTML} | ${paragraphsDoc} + `( + 'gets markdown source for a rendered $description', + async ({ sourceMarkdown, sourceHTML, expectedDoc }) => { + const { document } = await markdownDeserializer({ + render: () => ({ + body: sourceHTML, + }), + }).deserialize({ + schema: tiptapEditor.schema, + markdown: sourceMarkdown, + }); + + expect(document.content.toJSON()).toEqual(expectedDoc().content.toJSON()); + }, + ); + }); + + describe('when preserveMarkdown feature is disabled', () => { + beforeEach(() => { + sourceAttrs.mockImplementation(() => ({})); + }); + + it.each` + description | sourceMarkdown | sourceHTML | expectedDoc + ${'bullet list'} | ${BULLET_LIST_MARKDOWN} | ${BULLET_LIST_HTML} | ${bulletListDoc} + ${'bullet list with malformed sourcepos'} | ${BULLET_LIST_MARKDOWN} | ${MALFORMED_BULLET_LIST_HTML} | ${bulletListDocWithMalformedSourcepos} + ${'bullet task list'} | ${BULLET_TASK_LIST_MARKDOWN} | ${BULLET_TASK_LIST_HTML} | ${bulletTaskListDoc} + ${'paragraphs with inline elements'} | ${PARAGRAPHS_MARKDOWN} | ${PARAGRAPHS_HTML} | ${paragraphsDoc} + `( + 'does not include any source information for $description', + async ({ sourceMarkdown, sourceHTML, expectedDoc }) => { + const { document } = await markdownDeserializer({ + render: () => ({ + body: sourceHTML, + }), + }).deserialize({ + schema: tiptapEditor.schema, + markdown: sourceMarkdown, + }); + + expect(document.content.toJSON()).toEqual(expectedDoc().content.toJSON()); + }, + ); + }); }); diff --git a/spec/frontend/content_editor/test_constants.js b/spec/frontend/content_editor/test_constants.js index b56a6272de665716e3a3858ad2ed090e08c9b911..fbbe3a3feae40cd8e9a4a7bbaea8df7c28ee7bd1 100644 --- a/spec/frontend/content_editor/test_constants.js +++ b/spec/frontend/content_editor/test_constants.js @@ -59,3 +59,66 @@ export const RESOLVED_USER_HTML = export const RESOLVED_VULNERABILITY_HTML = '<p data-sourcepos="1:1-1:56" dir="auto"><a href="/gitlab-org/gitlab-shell/-/security/vulnerabilities/1" data-reference-type="vulnerability" data-original="[vulnerability:1]" data-link="false" data-link-reference="false" data-project="2" data-vulnerability="1" data-container="body" data-placement="top" title="oh no!" class="gfm gfm-vulnerability has-tooltip">[vulnerability:1]</a> <a href="/gitlab-org/gitlab-shell/-/security/vulnerabilities/1" data-reference-type="vulnerability" data-original="[vulnerability:1]" data-link="false" data-link-reference="false" data-project="2" data-vulnerability="1" data-container="body" data-placement="top" title="oh no!" class="gfm gfm-vulnerability has-tooltip">[vulnerability:1]</a>+ <a href="/gitlab-org/gitlab-shell/-/security/vulnerabilities/1" data-reference-type="vulnerability" data-original="[vulnerability:1]" data-link="false" data-link-reference="false" data-project="2" data-vulnerability="1" data-container="body" data-placement="top" title="oh no!" class="gfm gfm-vulnerability has-tooltip">[vulnerability:1]</a>+s</p>'; + +export const BULLET_LIST_MARKDOWN = `+ list item 1 ++ list item 2 + - embedded list item 3`; + +export const BULLET_LIST_HTML = `<ul data-sourcepos="1:1-3:24" dir="auto"> + <li data-sourcepos="1:1-1:13">list item 1</li> + <li data-sourcepos="2:1-3:24">list item 2 + <ul data-sourcepos="3:3-3:24"> + <li data-sourcepos="3:3-3:24">embedded list item 3</li> + </ul> + </li> +</ul>`; + +export const MALFORMED_BULLET_LIST_HTML = + `<ul data-sourcepos="1:1-3:24" dir="auto"> + <li data-sourcepos="1:1-1:13">list item 1</li>` + + // below line has malformed sourcepos + `<li data-sourcepos="5:1-5:24">list item 2 + <ul data-sourcepos="3:3-3:24"> + <li data-sourcepos="3:3-3:24">embedded list item 3</li> + </ul> + </li> +</ul>`; + +export const BULLET_TASK_LIST_MARKDOWN = `- [ ] list item 1 ++ [x] checked list item 2 + + [ ] embedded list item 1 + - [x] checked embedded list item 2`; + +export const BULLET_TASK_LIST_HTML = `<ul data-sourcepos="1:1-4:36" class="task-list" dir="auto"> + <li data-sourcepos="1:1-1:17" class="task-list-item"><input type="checkbox" class="task-list-item-checkbox"> list item 1</li> + <li data-sourcepos="2:1-4:36" class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" checked> checked list item 2 + <ul data-sourcepos="3:3-4:36" class="task-list"> + <li data-sourcepos="3:3-3:28" class="task-list-item"><input type="checkbox" class="task-list-item-checkbox"> embedded list item 1</li> + <li data-sourcepos="4:3-4:36" class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" checked> checked embedded list item 2</li> + </ul> + </li> +</ul>`; + +export const PARAGRAPHS_MARKDOWN = `You could bold with **asterisks** or you could bold with __underscores__. You could even bold with <strong>strong</strong> or <b>b</b> html tags. You could add newlines in your paragraph, or \`code\` tags with \`\` nested \`backticks\` \`\`. + +You could italicise with *asterisks* or you could italicise with _underscores_. You could even italicise with <em>em</em> or <i>i</i> html tags. + +As long as you don't touch a paragraph, it will ~~discard~~ <s>destroy</s> <del>delete</del> <strike>remove</strike> preserve the original markdown style.`; + +export const PARAGRAPHS_HTML = `<p data-sourcepos="1:1-1:233" dir="auto"> + You could bold with <strong data-sourcepos="1:21-1:33">asterisks</strong> or you could bold with + <strong data-sourcepos="1:58-1:72">underscores</strong>. You could even bold with + <strong>strong</strong> or <b>b</b> html tags. You could add newlines in your paragraph, + or <code data-sourcepos="1:193-1:196">code</code> tags with + <code data-sourcepos="1:211-1:230">nested \`backticks\`</code>. +</p> +<p data-sourcepos="3:1-3:144" dir="auto"> + You could italicise with <em data-sourcepos="3:26-3:36">asterisks</em> or + you could italicise with <em data-sourcepos="3:66-3:78">underscores</em>. + You could even italicise with <em>em</em> or <i>i</i> html tags. +</p> +<p data-sourcepos="5:1-5:154" dir="auto"> + As long as you don't touch a paragraph, it will + <del data-sourcepos="5:49-5:59">discard</del> <s>destroy</s> <del>delete</del> + <strike>remove</strike> preserve the original markdown style. +</p>`;