Skip to content
代码片段 群组 项目
未验证 提交 15d68480 编辑于 作者: Himanshu Kapoor's avatar Himanshu Kapoor 提交者: GitLab
浏览文件

Improve functionality of sourcemaps in RTE

In rich text editor, enhance the sourcemap extension and service to
include source positions of elements as attributes with the element itself.
上级 dd97ceca
No related branches found
No related tags found
无相关合并请求
显示
289 个添加107 个删除
...@@ -18,7 +18,7 @@ export default BulletList.extend({ ...@@ -18,7 +18,7 @@ export default BulletList.extend({
bullet: { bullet: {
default: '*', default: '*',
parseHTML(element) { parseHTML(element) {
const bullet = getMarkdownSource(element)?.charAt(0); const bullet = getMarkdownSource(element)?.trim().charAt(0);
return '*+-'.includes(bullet) ? bullet : '*'; return '*+-'.includes(bullet) ? bullet : '*';
}, },
......
...@@ -17,7 +17,7 @@ export default OrderedList.extend({ ...@@ -17,7 +17,7 @@ export default OrderedList.extend({
parens: { parens: {
default: false, default: false,
parseHTML: (element) => /^[0-9]+\)/.test(getMarkdownSource(element)), parseHTML: (element) => /^[0-9]+\)/.test(getMarkdownSource(element)?.trim()),
}, },
}; };
}, },
......
import { Extension } from '@tiptap/core'; import { Extension } from '@tiptap/core';
import { getMarkdownSource, docHasSourceMap } from '../services/markdown_sourcemap';
import Audio from './audio'; import Audio from './audio';
import Blockquote from './blockquote'; import Blockquote from './blockquote';
import Bold from './bold'; import Bold from './bold';
...@@ -34,6 +35,8 @@ export default Extension.create({ ...@@ -34,6 +35,8 @@ export default Extension.create({
name: 'sourcemap', name: 'sourcemap',
addGlobalAttributes() { addGlobalAttributes() {
const preserveMarkdown = () => gon.features?.preserveMarkdown;
return [ return [
{ {
types: [ types: [
...@@ -77,10 +80,18 @@ export default Extension.create({ ...@@ -77,10 +80,18 @@ export default Extension.create({
*/ */
sourceMarkdown: { sourceMarkdown: {
default: null, default: null,
parseHTML: (element) => preserveMarkdown() && getMarkdownSource(element),
renderHTML: () => '', renderHTML: () => '',
}, },
sourceMapKey: { sourceMapKey: {
default: null, default: null,
parseHTML: (element) => preserveMarkdown() && element.dataset.sourcepos,
renderHTML: () => '',
},
sourceTagName: {
default: null,
parseHTML: (element) =>
preserveMarkdown() && docHasSourceMap(element) && element.tagName.toLowerCase(),
renderHTML: () => '', renderHTML: () => '',
}, },
}, },
......
...@@ -30,7 +30,7 @@ export default TaskList.extend({ ...@@ -30,7 +30,7 @@ export default TaskList.extend({
bullet: { bullet: {
default: '*', default: '*',
parseHTML(element) { parseHTML(element) {
const bullet = getMarkdownSource(element)?.charAt(0); const bullet = getMarkdownSource(element)?.trim().charAt(0);
return '*+-'.includes(bullet) ? bullet : '*'; return '*+-'.includes(bullet) ? bullet : '*';
}, },
}, },
......
import { isString } from 'lodash'; 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) => { export const getFullSource = (element) => {
const commentNode = element.ownerDocument.body.lastChild; const commentNode = element.ownerDocument.body.lastChild;
...@@ -32,14 +37,26 @@ export const getMarkdownSource = (element) => { ...@@ -32,14 +37,26 @@ export const getMarkdownSource = (element) => {
if (!source.length) return undefined; if (!source.length) return undefined;
for (let i = range.start.row; i <= range.end.row; i += 1) { for (let i = range.start.row; i <= range.end.row; i += 1) {
if (i === range.start.row) { if (i === range.start.row && i === range.end.row) {
elSource += source[i].substring(range.start.col); // 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 { } else {
elSource += `\n${source[i]}` || ''; elSource += `\n${source[i]}` || '';
} }
} }
return elSource.trim(); return elSource;
} catch { } catch {
return undefined; return undefined;
} }
......
...@@ -5,7 +5,7 @@ const defaultAttrs = { ...@@ -5,7 +5,7 @@ const defaultAttrs = {
th: { colspan: 1, rowspan: 1, colwidth: null, align: 'left' }, th: { colspan: 1, rowspan: 1, colwidth: null, align: 'left' },
}; };
const defaultIgnoreAttrs = ['sourceMarkdown', 'sourceMapKey']; const defaultIgnoreAttrs = ['sourceMarkdown', 'sourceMapKey', 'sourceTagName'];
const ignoreAttrs = { const ignoreAttrs = {
dd: ['isTerm'], dd: ['isTerm'],
......
...@@ -32,6 +32,10 @@ module WikiActions ...@@ -32,6 +32,10 @@ module WikiActions
before_action :page, only: [:show, :edit, :update, :history, :destroy, :diff] before_action :page, only: [:show, :edit, :update, :history, :destroy, :diff]
before_action :load_sidebar, except: [:pages] before_action :load_sidebar, except: [:pages]
before_action do
push_frontend_feature_flag(:preserve_markdown, container)
end
before_action only: [:show, :edit, :update] do before_action only: [:show, :edit, :update] do
@valid_encoding = valid_encoding? @valid_encoding = valid_encoding?
end end
......
...@@ -44,6 +44,7 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -44,6 +44,7 @@ class Projects::IssuesController < Projects::ApplicationController
before_action :authorize_read_code!, only: [:related_branches] before_action :authorize_read_code!, only: [:related_branches]
before_action do before_action do
push_frontend_feature_flag(:preserve_markdown, project)
push_frontend_feature_flag(:issues_grid_view) push_frontend_feature_flag(:issues_grid_view)
push_frontend_feature_flag(:service_desk_ticket) push_frontend_feature_flag(:service_desk_ticket)
push_frontend_feature_flag(:issues_list_drawer, project) push_frontend_feature_flag(:issues_list_drawer, project)
......
---
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
...@@ -18,6 +18,7 @@ class Groups::EpicsController < Groups::ApplicationController ...@@ -18,6 +18,7 @@ class Groups::EpicsController < Groups::ApplicationController
after_action :log_epic_show, only: :show after_action :log_epic_show, only: :show
before_action do before_action do
push_frontend_feature_flag(:preserve_markdown, @group)
push_frontend_feature_flag(:notifications_todos_buttons, current_user) push_frontend_feature_flag(:notifications_todos_buttons, current_user)
push_frontend_feature_flag(:namespace_level_work_items, @group) push_frontend_feature_flag(:namespace_level_work_items, @group)
push_force_frontend_feature_flag(:namespace_level_work_items, epic_work_items_enabled?) push_force_frontend_feature_flag(:namespace_level_work_items, epic_work_items_enabled?)
......
import { builders } from 'prosemirror-test-builder'; 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 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 ListItem from '~/content_editor/extensions/list_item';
import TaskList from '~/content_editor/extensions/task_list'; import TaskList from '~/content_editor/extensions/task_list';
import TaskItem from '~/content_editor/extensions/task_item'; 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 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'; import { createTestEditor } from '../test_utils';
import {
const BULLET_LIST_MARKDOWN = `+ list item 1 BULLET_LIST_MARKDOWN,
+ list item 2 BULLET_LIST_HTML,
- embedded list item 3`; MALFORMED_BULLET_LIST_HTML,
const BULLET_LIST_HTML = `<ul data-sourcepos="1:1-3:24" dir="auto"> BULLET_TASK_LIST_MARKDOWN,
<li data-sourcepos="1:1-1:13">list item 1</li> BULLET_TASK_LIST_HTML,
<li data-sourcepos="2:1-3:24">list item 2 PARAGRAPHS_MARKDOWN,
<ul data-sourcepos="3:3-3:24"> PARAGRAPHS_HTML,
<li data-sourcepos="3:3-3:24">embedded list item 3</li> } from '../test_constants';
</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;
},
},
},
},
];
},
});
const tiptapEditor = createTestEditor({ 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 = () => const bulletListDoc = () =>
doc( doc(
bulletList( bulletList(
{ bullet: '+', source: '+ list item 1\n+ list item 2\n - embedded list item 3' }, { bullet: '+', ...sourceAttrs('ul', '1:1-3:24', BULLET_LIST_MARKDOWN) },
listItem({ source: '+ list item 1' }, paragraph('list item 1')), listItem(sourceAttrs('li', '1:1-1:13', '+ list item 1'), paragraph('list item 1')),
listItem( 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'), paragraph('list item 2'),
bulletList( bulletList(
{ bullet: '-', source: '- embedded list item 3' }, { bullet: '-', ...sourceAttrs('ul', '3:3-3:24', ' - embedded list item 3') },
listItem({ source: '- embedded list item 3' }, paragraph('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 = () => ...@@ -90,13 +58,18 @@ const bulletListDoc = () =>
const bulletListDocWithMalformedSourcepos = () => const bulletListDocWithMalformedSourcepos = () =>
doc( doc(
bulletList( bulletList(
{ bullet: '+', source: '+ list item 1\n+ list item 2\n - embedded list item 3' }, { bullet: '+', ...sourceAttrs('ul', '1:1-3:24', BULLET_LIST_MARKDOWN) },
listItem({ source: '+ list item 1' }, paragraph('list item 1')), listItem(sourceAttrs('li', '1:1-1:13', '+ list item 1'), paragraph('list item 1')),
listItem( listItem(
// source not included for out of bounds list item here
sourceAttrs('li', '5:1-5:24'),
paragraph('list item 2'), paragraph('list item 2'),
bulletList( bulletList(
{ bullet: '-', source: '- embedded list item 3' }, { bullet: '-', ...sourceAttrs('ul', '3:3-3:24', ' - embedded list item 3') },
listItem({ source: '- embedded list item 3' }, paragraph('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 = () => ...@@ -105,27 +78,36 @@ const bulletListDocWithMalformedSourcepos = () =>
const bulletTaskListDoc = () => const bulletTaskListDoc = () =>
doc( doc(
taskList( taskList(
{ { bullet: '-', ...sourceAttrs('ul', '1:1-4:36', BULLET_TASK_LIST_MARKDOWN) },
bullet: '-', taskItem(sourceAttrs('li', '1:1-1:17', '- [ ] list item 1'), paragraph('list item 1')),
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')),
taskItem( 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', '+ [x] checked list item 2\n + [ ] embedded list item 1\n - [x] checked embedded list item 2',
),
checked: true, checked: true,
}, },
paragraph('checked list item 2'), paragraph('checked list item 2'),
taskList( taskList(
{ {
bullet: '+', 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( 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'), paragraph('checked embedded list item 2'),
), ),
), ),
...@@ -133,6 +115,62 @@ const bulletTaskListDoc = () => ...@@ -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('content_editor/services/markdown_sourcemap', () => {
describe('getFullSource', () => { describe('getFullSource', () => {
it.each` it.each`
...@@ -154,24 +192,63 @@ describe('content_editor/services/markdown_sourcemap', () => { ...@@ -154,24 +192,63 @@ describe('content_editor/services/markdown_sourcemap', () => {
}); });
}); });
it.each` describe('when preserveMarkdown feature is enabled', () => {
description | sourceMarkdown | sourceHTML | expectedDoc beforeEach(() => {
${'bullet list'} | ${BULLET_LIST_MARKDOWN} | ${BULLET_LIST_HTML} | ${bulletListDoc} gon.features = { preserveMarkdown: true };
${'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}
`( afterEach(() => {
'gets markdown source for a rendered $description', gon.features = {};
async ({ sourceMarkdown, sourceHTML, expectedDoc }) => { });
const { document } = await markdownDeserializer({
render: () => ({ it.each`
body: sourceHTML, description | sourceMarkdown | sourceHTML | expectedDoc
}), ${'bullet list'} | ${BULLET_LIST_MARKDOWN} | ${BULLET_LIST_HTML} | ${bulletListDoc}
}).deserialize({ ${'bullet list with malformed sourcepos'} | ${BULLET_LIST_MARKDOWN} | ${MALFORMED_BULLET_LIST_HTML} | ${bulletListDocWithMalformedSourcepos}
schema: tiptapEditor.schema, ${'bullet task list'} | ${BULLET_TASK_LIST_MARKDOWN} | ${BULLET_TASK_LIST_HTML} | ${bulletTaskListDoc}
markdown: sourceMarkdown, ${'paragraphs with inline elements'} | ${PARAGRAPHS_MARKDOWN} | ${PARAGRAPHS_HTML} | ${paragraphsDoc}
}); `(
'gets markdown source for a rendered $description',
expect(document.toJSON()).toEqual(expectedDoc().toJSON()); 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());
},
);
});
}); });
...@@ -59,3 +59,66 @@ export const RESOLVED_USER_HTML = ...@@ -59,3 +59,66 @@ export const RESOLVED_USER_HTML =
export const RESOLVED_VULNERABILITY_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>'; '<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>`;
0% 加载中 .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册