diff --git a/app/assets/javascripts/content_editor/components/wrappers/reference_label.vue b/app/assets/javascripts/content_editor/components/wrappers/reference_label.vue index 08efcd643679cfd07fed780f66c7cf26bb2cb229..c67e699cf954d5b73406c0a165dfd9893835468a 100644 --- a/app/assets/javascripts/content_editor/components/wrappers/reference_label.vue +++ b/app/assets/javascripts/content_editor/components/wrappers/reference_label.vue @@ -22,9 +22,10 @@ export default { }, computed: { isScopedLabel() { - return isScopedLabel({ title: this.node.attrs.originalText }); + return isScopedLabel({ title: this.node.attrs.originalText || this.node.attrs.text }); }, }, + fallbackLabelBackgroundColor: '#ccc', }; </script> <template> @@ -32,7 +33,7 @@ export default { <gl-label size="sm" :scoped="isScopedLabel" - :background-color="node.attrs.color" + :background-color="node.attrs.color || $options.fallbackLabelBackgroundColor" :title="node.attrs.text" class="gl-pointer-events-none" /> diff --git a/app/assets/javascripts/content_editor/extensions/link.js b/app/assets/javascripts/content_editor/extensions/link.js index b83814103d153398b8931d540f9050a3bc34a480..584e7b9e4f79a6f5cc26b690c78ce8d0e6378fea 100644 --- a/app/assets/javascripts/content_editor/extensions/link.js +++ b/app/assets/javascripts/content_editor/extensions/link.js @@ -40,7 +40,6 @@ export default Link.extend({ }, addAttributes() { return { - ...this.parent?.(), uploading: { default: false, renderHTML: ({ uploading }) => (uploading ? { class: 'with-attachment-icon' } : {}), diff --git a/app/assets/javascripts/content_editor/extensions/paste_markdown.js b/app/assets/javascripts/content_editor/extensions/paste_markdown.js index 82fa5ce6c1d9f0bb749da8bef447dc7df3340543..db13438de5e0d746564d41d5468c8ca1e2d4f1bd 100644 --- a/app/assets/javascripts/content_editor/extensions/paste_markdown.js +++ b/app/assets/javascripts/content_editor/extensions/paste_markdown.js @@ -1,5 +1,7 @@ +import OrderedMap from 'orderedmap'; import { Extension } from '@tiptap/core'; import { Plugin, PluginKey } from '@tiptap/pm/state'; +import { Schema, DOMParser as ProseMirrorDOMParser, DOMSerializer } from '@tiptap/pm/model'; import { __ } from '~/locale'; import { VARIANT_DANGER } from '~/alert'; import createMarkdownDeserializer from '../services/gl_api_markdown_deserializer'; @@ -9,47 +11,55 @@ import Diagram from './diagram'; import Frontmatter from './frontmatter'; const TEXT_FORMAT = 'text/plain'; +const GFM_FORMAT = 'text/x-gfm'; const HTML_FORMAT = 'text/html'; const VS_CODE_FORMAT = 'vscode-editor-data'; const CODE_BLOCK_NODE_TYPES = [CodeBlockHighlight.name, Diagram.name, Frontmatter.name]; +function parseHTML(schema, html) { + const parser = new DOMParser(); + const startTag = '<body>'; + const endTag = '</body>'; + const { body } = parser.parseFromString(startTag + html + endTag, 'text/html'); + return { document: ProseMirrorDOMParser.fromSchema(schema).parse(body) }; +} + export default Extension.create({ name: 'pasteMarkdown', priority: EXTENSION_PRIORITY_HIGHEST, addOptions() { return { renderMarkdown: null, + serializer: null, }; }, addCommands() { return { - pasteMarkdown: (markdown) => () => { + pasteContent: (content = '', processMarkdown = true) => async () => { const { editor, options } = this; const { renderMarkdown, eventHub } = options; const deserializer = createMarkdownDeserializer({ render: renderMarkdown }); - deserializer - .deserialize({ schema: editor.schema, markdown }) + const pasteSchemaSpec = { ...editor.schema.spec }; + pasteSchemaSpec.marks = OrderedMap.from(pasteSchemaSpec.marks).remove('span'); + pasteSchemaSpec.nodes = OrderedMap.from(pasteSchemaSpec.nodes).remove('div').remove('pre'); + const schema = new Schema(pasteSchemaSpec); + + const promise = processMarkdown + ? deserializer.deserialize({ schema, markdown: content }) + : Promise.resolve(parseHTML(schema, content)); + + promise .then(({ document }) => { - if (!document) { - return; - } + if (!document) return; - const { state, view } = editor; - const { tr, selection } = state; const { firstChild } = document.content; - const content = + const toPaste = document.content.childCount === 1 && firstChild.type.name === 'paragraph' ? firstChild.content : document.content; - if (selection.to - selection.from > 0) { - tr.replaceWith(selection.from, selection.to, content); - } else { - tr.insert(selection.from, content); - } - - view.dispatch(tr); + editor.commands.insertContent(toPaste.toJSON()); }) .catch(() => { eventHub.$emit(ALERT_EVENT, { @@ -65,24 +75,57 @@ export default Extension.create({ addProseMirrorPlugins() { let pasteRaw = false; + const handleCutAndCopy = (view, event) => { + const slice = view.state.selection.content(); + const gfmContent = this.options.serializer.serialize({ doc: slice.content }); + const documentFragment = DOMSerializer.fromSchema(view.state.schema).serializeFragment( + slice.content, + ); + const div = document.createElement('div'); + div.appendChild(documentFragment); + + event.clipboardData.setData(TEXT_FORMAT, div.innerText); + event.clipboardData.setData(HTML_FORMAT, div.innerHTML); + event.clipboardData.setData(GFM_FORMAT, gfmContent); + + event.preventDefault(); + event.stopPropagation(); + }; + return [ new Plugin({ key: new PluginKey('pasteMarkdown'), props: { + handleDOMEvents: { + copy: handleCutAndCopy, + cut: (view, event) => { + handleCutAndCopy(view, event); + this.editor.commands.deleteSelection(); + }, + }, handleKeyDown: (_, event) => { pasteRaw = event.key === 'v' && (event.metaKey || event.ctrlKey) && event.shiftKey; }, handlePaste: (view, event) => { const { clipboardData } = event; - const content = clipboardData.getData(TEXT_FORMAT); - const { state } = view; - const { tr, selection } = state; - const { from, to } = selection; + + const gfmContent = clipboardData.getData(GFM_FORMAT); + + if (gfmContent) { + return this.editor.commands.pasteContent(gfmContent, true); + } + + const textContent = clipboardData.getData(TEXT_FORMAT); + const htmlContent = clipboardData.getData(HTML_FORMAT); + + const { from, to } = view.state.selection; if (pasteRaw) { - tr.insertText(content.replace(/^\s+|\s+$/gm, ''), from, to); - view.dispatch(tr); + this.editor.commands.insertContentAt( + { from, to }, + textContent.replace(/^\s+|\s+$/gm, ''), + ); return true; } @@ -91,18 +134,19 @@ export default Extension.create({ const vsCodeMeta = hasVsCode ? JSON.parse(clipboardData.getData(VS_CODE_FORMAT)) : {}; const language = vsCodeMeta.mode; - if (!content || (hasHTML && !hasVsCode) || (hasVsCode && language !== 'markdown')) { - return false; - } - // if a code block is active, paste as plain text - if (CODE_BLOCK_NODE_TYPES.some((type) => this.editor.isActive(type))) { + if (!textContent || CODE_BLOCK_NODE_TYPES.some((type) => this.editor.isActive(type))) { return false; } - this.editor.commands.pasteMarkdown(content); + if (hasVsCode) { + return this.editor.commands.pasteContent( + language === 'markdown' ? textContent : `\`\`\`${language}\n${textContent}\n\`\`\``, + true, + ); + } - return true; + return this.editor.commands.pasteContent(hasHTML ? htmlContent : textContent, !hasHTML); }, }, }), diff --git a/app/assets/javascripts/content_editor/extensions/reference_label.js b/app/assets/javascripts/content_editor/extensions/reference_label.js index 6fe904ed787c799198786784030c2a6e36b2cd12..9cd55a0f87c280d6acad67a0bf6d13dd2990a6e4 100644 --- a/app/assets/javascripts/content_editor/extensions/reference_label.js +++ b/app/assets/javascripts/content_editor/extensions/reference_label.js @@ -20,7 +20,13 @@ export default Reference.extend({ }, color: { default: null, - parseHTML: (element) => element.querySelector('.gl-label-text').style.backgroundColor, + parseHTML: (element) => { + let color = element.querySelector('.gl-label-text').style.backgroundColor; + if (!color || color.startsWith('var')) + color = element.style.getPropertyValue('--label-background-color'); + + return color; + }, }, }; }, diff --git a/app/assets/javascripts/content_editor/services/create_content_editor.js b/app/assets/javascripts/content_editor/services/create_content_editor.js index 834fb72daba1c1eea99913ea1958c3461083ec1c..ee1f706ec7e335df4cc25aab9e3e82caafa190a8 100644 --- a/app/assets/javascripts/content_editor/services/create_content_editor.js +++ b/app/assets/javascripts/content_editor/services/create_content_editor.js @@ -64,7 +64,7 @@ import Text from '../extensions/text'; import Video from '../extensions/video'; import WordBreak from '../extensions/word_break'; import { ContentEditor } from './content_editor'; -import createMarkdownSerializer from './markdown_serializer'; +import MarkdownSerializer from './markdown_serializer'; import createGlApiMarkdownDeserializer from './gl_api_markdown_deserializer'; import createRemarkMarkdownDeserializer from './remark_markdown_deserializer'; import AssetResolver from './asset_resolver'; @@ -97,6 +97,12 @@ export const createContentEditor = ({ const eventHub = eventHubFactory(); const assetResolver = new AssetResolver({ renderMarkdown }); + const serializer = new MarkdownSerializer({ serializerConfig }); + const deserializer = window.gon?.features?.preserveUnchangedMarkdown + ? createRemarkMarkdownDeserializer() + : createGlApiMarkdownDeserializer({ + render: renderMarkdown, + }); const builtInContentEditorExtensions = [ Attachment.configure({ uploadsPath, renderMarkdown, eventHub }), @@ -139,7 +145,7 @@ export const createContentEditor = ({ MathInline, OrderedList, Paragraph, - PasteMarkdown.configure({ eventHub, renderMarkdown }), + PasteMarkdown.configure({ eventHub, renderMarkdown, serializer }), Reference.configure({ assetResolver }), ReferenceLabel, ReferenceDefinition, @@ -167,12 +173,6 @@ export const createContentEditor = ({ const trackedExtensions = allExtensions.map(trackInputRulesAndShortcuts); const tiptapEditor = createTiptapEditor({ extensions: trackedExtensions, ...tiptapOptions }); - const serializer = createMarkdownSerializer({ serializerConfig }); - const deserializer = window.gon?.features?.preserveUnchangedMarkdown - ? createRemarkMarkdownDeserializer() - : createGlApiMarkdownDeserializer({ - render: renderMarkdown, - }); return new ContentEditor({ tiptapEditor, diff --git a/app/assets/javascripts/content_editor/services/markdown_serializer.js b/app/assets/javascripts/content_editor/services/markdown_serializer.js index 3b77064e903df6a6fc193bcf76f6ec2e52e9ffe2..20899dcf5bdc457f5edbc790de429c6949bcc50f 100644 --- a/app/assets/javascripts/content_editor/services/markdown_serializer.js +++ b/app/assets/javascripts/content_editor/services/markdown_serializer.js @@ -67,6 +67,7 @@ import { renderContent, renderBulletList, renderReference, + renderReferenceLabel, preserveUnchanged, bold, italic, @@ -197,7 +198,7 @@ const defaultSerializerConfig = { [OrderedList.name]: preserveUnchanged(renderOrderedList), [Paragraph.name]: preserveUnchanged(defaultMarkdownSerializer.nodes.paragraph), [Reference.name]: renderReference, - [ReferenceLabel.name]: renderReference, + [ReferenceLabel.name]: renderReferenceLabel, [ReferenceDefinition.name]: preserveUnchanged({ render: (state, node, parent, index, same, sourceMarkdown) => { const nextSibling = parent.maybeChild(index + 1); @@ -273,19 +274,22 @@ const createChangeTracker = (doc, pristineDoc) => { return changeTracker; }; -/** - * Converts a ProseMirror document to Markdown. See the - * following documentation to learn how to implement - * custom node and mark serializer functions. - * - * https://github.com/prosemirror/prosemirror-markdown - * - * @param {Object} params.nodes ProseMirror node serializer functions - * @param {Object} params.marks ProseMirror marks serializer config - * - * @returns a markdown serializer - */ -export default ({ serializerConfig = {} } = {}) => ({ +export default class MarkdownSerializer { + /** + * Converts a ProseMirror document to Markdown. See the + * following documentation to learn how to implement + * custom node and mark serializer functions. + * + * https://github.com/prosemirror/prosemirror-markdown + * + * @param {Object} params.nodes ProseMirror node serializer functions + * @param {Object} params.marks ProseMirror marks serializer config + * + * @returns a markdown serializer + */ + constructor({ serializerConfig = {} } = {}) { + this.serializerConfig = serializerConfig; + } /** * Serializes a ProseMirror document as Markdown. If a node contains * sourcemap metadata, the serializer is capable of restoring the @@ -301,16 +305,16 @@ export default ({ serializerConfig = {} } = {}) => ({ * changed. * @returns A String that represents the serialized document as Markdown */ - serialize: ({ doc, pristineDoc }) => { + serialize({ doc, pristineDoc }) { const changeTracker = createChangeTracker(doc, pristineDoc); const serializer = new ProseMirrorMarkdownSerializer( { ...defaultSerializerConfig.nodes, - ...serializerConfig.nodes, + ...this.serializerConfig.nodes, }, { ...defaultSerializerConfig.marks, - ...serializerConfig.marks, + ...this.serializerConfig.marks, }, ); @@ -318,5 +322,5 @@ export default ({ serializerConfig = {} } = {}) => ({ tightLists: true, changeTracker, }); - }, -}); + } +} diff --git a/app/assets/javascripts/content_editor/services/serialization_helpers.js b/app/assets/javascripts/content_editor/services/serialization_helpers.js index 9d8dec96152f7b7ec8c1ba420c9b28af041bc014..b6bcda8d801c61691aabd73efd36889db3b2f788 100644 --- a/app/assets/javascripts/content_editor/services/serialization_helpers.js +++ b/app/assets/javascripts/content_editor/services/serialization_helpers.js @@ -157,6 +157,10 @@ function setIsInBlockTable(table, value) { }); } +function ensureSpace(state) { + if (!state.atBlank() && !state.out.endsWith(' ')) state.write(' '); +} + function unsetIsInBlockTable(table) { tableMap.delete(table); @@ -457,9 +461,15 @@ export function renderOrderedList(state, node) { } export function renderReference(state, node) { + ensureSpace(state); state.write(node.attrs.originalText || node.attrs.text); } +export function renderReferenceLabel(state, node) { + ensureSpace(state); + state.write(node.attrs.originalText || `~${state.quote(node.attrs.text)}`); +} + const generateBoldTags = (wrapTagName = openTag) => { return (_, mark) => { const type = /^(\*\*|__|<strong|<b).*/.exec(mark.attrs.sourceMarkdown)?.[1]; diff --git a/glfm_specification/output_example_snapshots/prosemirror_json.yml b/glfm_specification/output_example_snapshots/prosemirror_json.yml index 8cce959c8f895505d5762da84cab0044e0a321d5..bc6293b54b228a51a863242eee2cf5ba896c507a 100644 --- a/glfm_specification/output_example_snapshots/prosemirror_json.yml +++ b/glfm_specification/output_example_snapshots/prosemirror_json.yml @@ -3195,10 +3195,8 @@ { "type": "link", "attrs": { - "href": "bar", - "target": "_blank", - "class": null, "uploading": false, + "href": "bar", "title": null, "canonicalSrc": "bar", "isReference": false @@ -3282,10 +3280,8 @@ { "type": "link", "attrs": { - "href": "foo", - "target": "_blank", - "class": null, "uploading": false, + "href": "foo", "title": null, "canonicalSrc": "foo", "isReference": false @@ -3804,10 +3800,8 @@ { "type": "link", "attrs": { - "href": "bar", - "target": "_blank", - "class": null, "uploading": false, + "href": "bar", "title": null, "canonicalSrc": "bar", "isReference": false @@ -3965,10 +3959,8 @@ { "type": "link", "attrs": { - "href": "/url", - "target": "_blank", - "class": null, "uploading": false, + "href": "/url", "title": "title", "canonicalSrc": "foo", "isReference": true @@ -4008,10 +4000,8 @@ { "type": "link", "attrs": { - "href": "/url", - "target": "_blank", - "class": null, "uploading": false, + "href": "/url", "title": "the title", "canonicalSrc": "foo", "isReference": true @@ -4051,10 +4041,8 @@ { "type": "link", "attrs": { - "href": "my_(url)", - "target": "_blank", - "class": null, "uploading": false, + "href": "my_(url)", "title": "title (with parens)", "canonicalSrc": "foo*bar\\]", "isReference": true @@ -4094,10 +4082,8 @@ { "type": "link", "attrs": { - "href": "my%20url", - "target": "_blank", - "class": null, "uploading": false, + "href": "my%20url", "title": "title", "canonicalSrc": "foo bar", "isReference": true @@ -4137,10 +4123,8 @@ { "type": "link", "attrs": { - "href": "/url", - "target": "_blank", - "class": null, "uploading": false, + "href": "/url", "title": "\ntitle\nline1\nline2\n", "canonicalSrc": "foo", "isReference": true @@ -4213,10 +4197,8 @@ { "type": "link", "attrs": { - "href": "/url", - "target": "_blank", - "class": null, "uploading": false, + "href": "/url", "title": null, "canonicalSrc": "foo", "isReference": true @@ -4280,10 +4262,8 @@ { "type": "link", "attrs": { - "href": "", - "target": "_blank", - "class": null, "uploading": false, + "href": "", "title": null, "canonicalSrc": "foo", "isReference": true @@ -4347,10 +4327,8 @@ { "type": "link", "attrs": { - "href": "/url%5Cbar*baz", - "target": "_blank", - "class": null, "uploading": false, + "href": "/url%5Cbar*baz", "title": "foo\"bar\\baz", "canonicalSrc": "foo", "isReference": true @@ -4376,10 +4354,8 @@ { "type": "link", "attrs": { - "href": "url", - "target": "_blank", - "class": null, "uploading": false, + "href": "url", "title": null, "canonicalSrc": "foo", "isReference": true @@ -4419,10 +4395,8 @@ { "type": "link", "attrs": { - "href": "first", - "target": "_blank", - "class": null, "uploading": false, + "href": "first", "title": null, "canonicalSrc": "foo", "isReference": true @@ -4490,10 +4464,8 @@ { "type": "link", "attrs": { - "href": "/url", - "target": "_blank", - "class": null, "uploading": false, + "href": "/url", "title": null, "canonicalSrc": "foo", "isReference": true @@ -4533,10 +4505,8 @@ { "type": "link", "attrs": { - "href": "/%CF%86%CE%BF%CF%85", - "target": "_blank", - "class": null, "uploading": false, + "href": "/%CF%86%CE%BF%CF%85", "title": null, "canonicalSrc": "αγω", "isReference": true @@ -4740,10 +4710,8 @@ { "type": "link", "attrs": { - "href": "/url", - "target": "_blank", - "class": null, "uploading": false, + "href": "/url", "title": null, "canonicalSrc": "foo", "isReference": true @@ -4826,10 +4794,8 @@ { "type": "link", "attrs": { - "href": "/url", - "target": "_blank", - "class": null, "uploading": false, + "href": "/url", "title": null, "canonicalSrc": "foo", "isReference": true @@ -4873,10 +4839,8 @@ { "type": "link", "attrs": { - "href": "/url", - "target": "_blank", - "class": null, "uploading": false, + "href": "/url", "title": null, "canonicalSrc": "foo", "isReference": true @@ -4944,10 +4908,8 @@ { "type": "link", "attrs": { - "href": "/foo-url", - "target": "_blank", - "class": null, "uploading": false, + "href": "/foo-url", "title": "foo", "canonicalSrc": "foo", "isReference": true @@ -4966,10 +4928,8 @@ { "type": "link", "attrs": { - "href": "/bar-url", - "target": "_blank", - "class": null, "uploading": false, + "href": "/bar-url", "title": "bar", "canonicalSrc": "bar", "isReference": true @@ -4988,10 +4948,8 @@ { "type": "link", "attrs": { - "href": "/baz-url", - "target": "_blank", - "class": null, "uploading": false, + "href": "/baz-url", "title": null, "canonicalSrc": "baz", "isReference": true @@ -5017,10 +4975,8 @@ { "type": "link", "attrs": { - "href": "/url", - "target": "_blank", - "class": null, "uploading": false, + "href": "/url", "title": null, "canonicalSrc": "foo", "isReference": true @@ -10816,10 +10772,8 @@ { "type": "link", "attrs": { - "href": "http://example.com?find=%5C*", - "target": "_blank", - "class": null, "uploading": false, + "href": "http://example.com?find=%5C*", "title": null, "canonicalSrc": "http://example.com?find=%5C*", "isReference": false @@ -10854,10 +10808,8 @@ { "type": "link", "attrs": { - "href": "/bar*", - "target": "_blank", - "class": null, "uploading": false, + "href": "/bar*", "title": "ti*tle", "canonicalSrc": "/bar*", "isReference": false @@ -10883,10 +10835,8 @@ { "type": "link", "attrs": { - "href": "/bar*", - "target": "_blank", - "class": null, "uploading": false, + "href": "/bar*", "title": "ti*tle", "canonicalSrc": "foo", "isReference": true @@ -11045,10 +10995,8 @@ { "type": "link", "attrs": { - "href": "/f%C3%B6%C3%B6", - "target": "_blank", - "class": null, "uploading": false, + "href": "/f%C3%B6%C3%B6", "title": "föö", "canonicalSrc": "/f%C3%B6%C3%B6", "isReference": false @@ -11074,10 +11022,8 @@ { "type": "link", "attrs": { - "href": "/f%C3%B6%C3%B6", - "target": "_blank", - "class": null, "uploading": false, + "href": "/f%C3%B6%C3%B6", "title": "föö", "canonicalSrc": "foo", "isReference": true @@ -11612,10 +11558,8 @@ { "type": "link", "attrs": { - "href": "`", - "target": "_blank", - "class": null, "uploading": false, + "href": "`", "title": null, "canonicalSrc": "`", "isReference": false @@ -11665,10 +11609,8 @@ { "type": "link", "attrs": { - "href": "http://foo.bar.%60baz", - "target": "_blank", - "class": null, "uploading": false, + "href": "http://foo.bar.%60baz", "title": null, "canonicalSrc": "http://foo.bar.%60baz", "isReference": false @@ -12823,10 +12765,8 @@ { "type": "link", "attrs": { - "href": "/url", - "target": "_blank", - "class": null, "uploading": false, + "href": "/url", "title": null, "canonicalSrc": "/url", "isReference": false @@ -13295,10 +13235,8 @@ { "type": "link", "attrs": { - "href": "/url", - "target": "_blank", - "class": null, "uploading": false, + "href": "/url", "title": null, "canonicalSrc": "/url", "isReference": false @@ -13366,10 +13304,8 @@ { "type": "link", "attrs": { - "href": "/url", - "target": "_blank", - "class": null, "uploading": false, + "href": "/url", "title": null, "canonicalSrc": "/url", "isReference": false @@ -13727,10 +13663,8 @@ { "type": "link", "attrs": { - "href": "/url", - "target": "_blank", - "class": null, "uploading": false, + "href": "/url", "title": null, "canonicalSrc": "/url", "isReference": false @@ -14635,10 +14569,8 @@ { "type": "link", "attrs": { - "href": "/url", - "target": "_blank", - "class": null, "uploading": false, + "href": "/url", "title": null, "canonicalSrc": "/url", "isReference": false @@ -14668,10 +14600,8 @@ { "type": "link", "attrs": { - "href": "/url", - "target": "_blank", - "class": null, "uploading": false, + "href": "/url", "title": null, "canonicalSrc": "/url", "isReference": false @@ -14823,10 +14753,8 @@ { "type": "link", "attrs": { - "href": "http://foo.bar/?q=**", - "target": "_blank", - "class": null, "uploading": false, + "href": "http://foo.bar/?q=**", "title": null, "canonicalSrc": "http://foo.bar/?q=**", "isReference": false @@ -14856,10 +14784,8 @@ { "type": "link", "attrs": { - "href": "http://foo.bar/?q=__", - "target": "_blank", - "class": null, "uploading": false, + "href": "http://foo.bar/?q=__", "title": null, "canonicalSrc": "http://foo.bar/?q=__", "isReference": false @@ -14933,10 +14859,8 @@ { "type": "link", "attrs": { - "href": "/uri", - "target": "_blank", - "class": null, "uploading": false, + "href": "/uri", "title": "title", "canonicalSrc": "/uri", "isReference": false @@ -14962,10 +14886,8 @@ { "type": "link", "attrs": { - "href": "/uri", - "target": "_blank", - "class": null, "uploading": false, + "href": "/uri", "title": null, "canonicalSrc": "/uri", "isReference": false @@ -14991,10 +14913,8 @@ { "type": "link", "attrs": { - "href": "", - "target": "_blank", - "class": null, "uploading": false, + "href": "", "title": null, "canonicalSrc": "", "isReference": false @@ -15020,10 +14940,8 @@ { "type": "link", "attrs": { - "href": "", - "target": "_blank", - "class": null, "uploading": false, + "href": "", "title": null, "canonicalSrc": "", "isReference": false @@ -15064,10 +14982,8 @@ { "type": "link", "attrs": { - "href": "/my%20uri", - "target": "_blank", - "class": null, "uploading": false, + "href": "/my%20uri", "title": null, "canonicalSrc": "/my%20uri", "isReference": false @@ -15123,10 +15039,8 @@ { "type": "link", "attrs": { - "href": "b)c", - "target": "_blank", - "class": null, "uploading": false, + "href": "b)c", "title": null, "canonicalSrc": "b)c", "isReference": false @@ -15191,10 +15105,8 @@ { "type": "link", "attrs": { - "href": "(foo)", - "target": "_blank", - "class": null, "uploading": false, + "href": "(foo)", "title": null, "canonicalSrc": "(foo)", "isReference": false @@ -15220,10 +15132,8 @@ { "type": "link", "attrs": { - "href": "foo(and(bar))", - "target": "_blank", - "class": null, "uploading": false, + "href": "foo(and(bar))", "title": null, "canonicalSrc": "foo(and(bar))", "isReference": false @@ -15249,10 +15159,8 @@ { "type": "link", "attrs": { - "href": "foo(and(bar)", - "target": "_blank", - "class": null, "uploading": false, + "href": "foo(and(bar)", "title": null, "canonicalSrc": "foo(and(bar)", "isReference": false @@ -15278,10 +15186,8 @@ { "type": "link", "attrs": { - "href": "foo(and(bar)", - "target": "_blank", - "class": null, "uploading": false, + "href": "foo(and(bar)", "title": null, "canonicalSrc": "foo(and(bar)", "isReference": false @@ -15307,10 +15213,8 @@ { "type": "link", "attrs": { - "href": "foo):", - "target": "_blank", - "class": null, "uploading": false, + "href": "foo):", "title": null, "canonicalSrc": "foo):", "isReference": false @@ -15336,10 +15240,8 @@ { "type": "link", "attrs": { - "href": "#fragment", - "target": "_blank", - "class": null, "uploading": false, + "href": "#fragment", "title": null, "canonicalSrc": "#fragment", "isReference": false @@ -15359,10 +15261,8 @@ { "type": "link", "attrs": { - "href": "http://example.com#fragment", - "target": "_blank", - "class": null, "uploading": false, + "href": "http://example.com#fragment", "title": null, "canonicalSrc": "http://example.com#fragment", "isReference": false @@ -15382,10 +15282,8 @@ { "type": "link", "attrs": { - "href": "http://example.com?foo=3#frag", - "target": "_blank", - "class": null, "uploading": false, + "href": "http://example.com?foo=3#frag", "title": null, "canonicalSrc": "http://example.com?foo=3#frag", "isReference": false @@ -15411,10 +15309,8 @@ { "type": "link", "attrs": { - "href": "foo%5Cbar", - "target": "_blank", - "class": null, "uploading": false, + "href": "foo%5Cbar", "title": null, "canonicalSrc": "foo%5Cbar", "isReference": false @@ -15440,10 +15336,8 @@ { "type": "link", "attrs": { - "href": "foo%20b%C3%A4", - "target": "_blank", - "class": null, "uploading": false, + "href": "foo%20b%C3%A4", "title": null, "canonicalSrc": "foo%20b%C3%A4", "isReference": false @@ -15469,10 +15363,8 @@ { "type": "link", "attrs": { - "href": "%22title%22", - "target": "_blank", - "class": null, "uploading": false, + "href": "%22title%22", "title": null, "canonicalSrc": "%22title%22", "isReference": false @@ -15498,10 +15390,8 @@ { "type": "link", "attrs": { - "href": "/url", - "target": "_blank", - "class": null, "uploading": false, + "href": "/url", "title": "title", "canonicalSrc": "/url", "isReference": false @@ -15527,10 +15417,8 @@ { "type": "link", "attrs": { - "href": "/url", - "target": "_blank", - "class": null, "uploading": false, + "href": "/url", "title": "title \"\"", "canonicalSrc": "/url", "isReference": false @@ -15556,10 +15444,8 @@ { "type": "link", "attrs": { - "href": "/url%C2%A0%22title%22", - "target": "_blank", - "class": null, "uploading": false, + "href": "/url%C2%A0%22title%22", "title": null, "canonicalSrc": "/url%C2%A0%22title%22", "isReference": false @@ -15600,10 +15486,8 @@ { "type": "link", "attrs": { - "href": "/url", - "target": "_blank", - "class": null, "uploading": false, + "href": "/url", "title": "title \"and\" title", "canonicalSrc": "/url", "isReference": false @@ -15629,10 +15513,8 @@ { "type": "link", "attrs": { - "href": "/uri", - "target": "_blank", - "class": null, "uploading": false, + "href": "/uri", "title": "title", "canonicalSrc": "/uri", "isReference": false @@ -15673,10 +15555,8 @@ { "type": "link", "attrs": { - "href": "/uri", - "target": "_blank", - "class": null, "uploading": false, + "href": "/uri", "title": null, "canonicalSrc": "/uri", "isReference": false @@ -15721,10 +15601,8 @@ { "type": "link", "attrs": { - "href": "/uri", - "target": "_blank", - "class": null, "uploading": false, + "href": "/uri", "title": null, "canonicalSrc": "/uri", "isReference": false @@ -15750,10 +15628,8 @@ { "type": "link", "attrs": { - "href": "/uri", - "target": "_blank", - "class": null, "uploading": false, + "href": "/uri", "title": null, "canonicalSrc": "/uri", "isReference": false @@ -15779,10 +15655,8 @@ { "type": "link", "attrs": { - "href": "/uri", - "target": "_blank", - "class": null, "uploading": false, + "href": "/uri", "title": null, "canonicalSrc": "/uri", "isReference": false @@ -15797,10 +15671,8 @@ { "type": "link", "attrs": { - "href": "/uri", - "target": "_blank", - "class": null, "uploading": false, + "href": "/uri", "title": null, "canonicalSrc": "/uri", "isReference": false @@ -15818,10 +15690,8 @@ { "type": "link", "attrs": { + "uploading": false, "href": "/uri", - "target": "_blank", - "class": null, - "uploading": false, "title": null, "canonicalSrc": "/uri", "isReference": false @@ -15842,10 +15712,8 @@ { "type": "link", "attrs": { - "href": "/uri", - "target": "_blank", - "class": null, "uploading": false, + "href": "/uri", "title": null, "canonicalSrc": "/uri", "isReference": false @@ -15890,10 +15758,8 @@ { "type": "link", "attrs": { - "href": "/uri", - "target": "_blank", - "class": null, "uploading": false, + "href": "/uri", "title": null, "canonicalSrc": "/uri", "isReference": false @@ -15922,10 +15788,8 @@ { "type": "link", "attrs": { - "href": "/uri", - "target": "_blank", - "class": null, "uploading": false, + "href": "/uri", "title": null, "canonicalSrc": "/uri", "isReference": false @@ -15968,10 +15832,8 @@ { "type": "link", "attrs": { - "href": "/uri", - "target": "_blank", - "class": null, "uploading": false, + "href": "/uri", "title": null, "canonicalSrc": "/uri", "isReference": false @@ -16041,10 +15903,8 @@ { "type": "link", "attrs": { - "href": "/uri", - "target": "_blank", - "class": null, "uploading": false, + "href": "/uri", "title": null, "canonicalSrc": "/uri", "isReference": false @@ -16070,10 +15930,8 @@ { "type": "link", "attrs": { - "href": "baz*", - "target": "_blank", - "class": null, "uploading": false, + "href": "baz*", "title": null, "canonicalSrc": "baz*", "isReference": false @@ -16166,10 +16024,8 @@ { "type": "link", "attrs": { - "href": "http://example.com/?search=%5D(uri)", - "target": "_blank", - "class": null, "uploading": false, + "href": "http://example.com/?search=%5D(uri)", "title": null, "canonicalSrc": "http://example.com/?search=%5D(uri)", "isReference": false @@ -16195,10 +16051,8 @@ { "type": "link", "attrs": { - "href": "/url", - "target": "_blank", - "class": null, "uploading": false, + "href": "/url", "title": "title", "canonicalSrc": "bar", "isReference": true @@ -16238,10 +16092,8 @@ { "type": "link", "attrs": { - "href": "/uri", - "target": "_blank", - "class": null, "uploading": false, + "href": "/uri", "title": null, "canonicalSrc": "ref", "isReference": true @@ -16281,10 +16133,8 @@ { "type": "link", "attrs": { - "href": "/uri", - "target": "_blank", - "class": null, "uploading": false, + "href": "/uri", "title": null, "canonicalSrc": "ref", "isReference": true @@ -16324,10 +16174,8 @@ { "type": "link", "attrs": { - "href": "/uri", - "target": "_blank", - "class": null, "uploading": false, + "href": "/uri", "title": null, "canonicalSrc": "ref", "isReference": true @@ -16342,10 +16190,8 @@ { "type": "link", "attrs": { - "href": "/uri", - "target": "_blank", - "class": null, "uploading": false, + "href": "/uri", "title": null, "canonicalSrc": "ref", "isReference": true @@ -16363,10 +16209,8 @@ { "type": "link", "attrs": { - "href": "/uri", - "target": "_blank", - "class": null, "uploading": false, + "href": "/uri", "title": null, "canonicalSrc": "ref", "isReference": true @@ -16387,10 +16231,8 @@ { "type": "link", "attrs": { - "href": "/uri", - "target": "_blank", - "class": null, "uploading": false, + "href": "/uri", "title": null, "canonicalSrc": "ref", "isReference": true @@ -16449,10 +16291,8 @@ { "type": "link", "attrs": { - "href": "/uri", - "target": "_blank", - "class": null, "uploading": false, + "href": "/uri", "title": null, "canonicalSrc": "ref", "isReference": true @@ -16495,10 +16335,8 @@ { "type": "link", "attrs": { - "href": "/uri", - "target": "_blank", - "class": null, "uploading": false, + "href": "/uri", "title": null, "canonicalSrc": "/uri", "isReference": false @@ -16517,10 +16355,8 @@ { "type": "link", "attrs": { - "href": "/uri", - "target": "_blank", - "class": null, "uploading": false, + "href": "/uri", "title": null, "canonicalSrc": "ref", "isReference": true @@ -16573,10 +16409,8 @@ { "type": "link", "attrs": { - "href": "/uri", - "target": "_blank", - "class": null, "uploading": false, + "href": "/uri", "title": null, "canonicalSrc": "ref", "isReference": true @@ -16598,10 +16432,8 @@ { "type": "link", "attrs": { - "href": "/uri", - "target": "_blank", - "class": null, "uploading": false, + "href": "/uri", "title": null, "canonicalSrc": "ref", "isReference": true @@ -16645,10 +16477,8 @@ { "type": "link", "attrs": { - "href": "/uri", - "target": "_blank", - "class": null, "uploading": false, + "href": "/uri", "title": null, "canonicalSrc": "ref", "isReference": true @@ -16688,10 +16518,8 @@ { "type": "link", "attrs": { - "href": "/uri", - "target": "_blank", - "class": null, "uploading": false, + "href": "/uri", "title": null, "canonicalSrc": "ref", "isReference": true @@ -16802,10 +16630,8 @@ { "type": "link", "attrs": { - "href": "http://example.com/?search=%5D%5Bref%5D", - "target": "_blank", - "class": null, "uploading": false, + "href": "http://example.com/?search=%5D%5Bref%5D", "title": null, "canonicalSrc": "http://example.com/?search=%5D%5Bref%5D", "isReference": false @@ -16845,10 +16671,8 @@ { "type": "link", "attrs": { - "href": "/url", - "target": "_blank", - "class": null, "uploading": false, + "href": "/url", "title": "title", "canonicalSrc": "bar", "isReference": true @@ -16888,10 +16712,8 @@ { "type": "link", "attrs": { - "href": "/url", - "target": "_blank", - "class": null, "uploading": false, + "href": "/url", "title": null, "canonicalSrc": "толпой", "isReference": true @@ -16949,10 +16771,8 @@ { "type": "link", "attrs": { - "href": "/url", - "target": "_blank", - "class": null, "uploading": false, + "href": "/url", "title": null, "canonicalSrc": "foo bar", "isReference": true @@ -16982,10 +16802,8 @@ { "type": "link", "attrs": { - "href": "/url", - "target": "_blank", - "class": null, "uploading": false, + "href": "/url", "title": "title", "canonicalSrc": "bar", "isReference": true @@ -17029,10 +16847,8 @@ { "type": "link", "attrs": { - "href": "/url", - "target": "_blank", - "class": null, "uploading": false, + "href": "/url", "title": "title", "canonicalSrc": "bar", "isReference": true @@ -17100,10 +16916,8 @@ { "type": "link", "attrs": { - "href": "/url1", - "target": "_blank", - "class": null, "uploading": false, + "href": "/url1", "title": null, "canonicalSrc": "foo", "isReference": true @@ -17230,10 +17044,8 @@ { "type": "link", "attrs": { - "href": "/uri", - "target": "_blank", - "class": null, "uploading": false, + "href": "/uri", "title": null, "canonicalSrc": "ref\\[", "isReference": true @@ -17287,10 +17099,8 @@ { "type": "link", "attrs": { - "href": "/uri", - "target": "_blank", - "class": null, "uploading": false, + "href": "/uri", "title": null, "canonicalSrc": "bar\\\\", "isReference": true @@ -17364,10 +17174,8 @@ { "type": "link", "attrs": { - "href": "/url", - "target": "_blank", - "class": null, "uploading": false, + "href": "/url", "title": "title", "canonicalSrc": "foo", "isReference": true @@ -17407,10 +17215,8 @@ { "type": "link", "attrs": { - "href": "/url", - "target": "_blank", - "class": null, "uploading": false, + "href": "/url", "title": "title", "canonicalSrc": "*foo* bar", "isReference": true @@ -17428,10 +17234,8 @@ { "type": "link", "attrs": { - "href": "/url", - "target": "_blank", - "class": null, "uploading": false, + "href": "/url", "title": "title", "canonicalSrc": "*foo* bar", "isReference": true @@ -17471,10 +17275,8 @@ { "type": "link", "attrs": { - "href": "/url", - "target": "_blank", - "class": null, "uploading": false, + "href": "/url", "title": "title", "canonicalSrc": "foo", "isReference": true @@ -17514,10 +17316,8 @@ { "type": "link", "attrs": { - "href": "/url", - "target": "_blank", - "class": null, "uploading": false, + "href": "/url", "title": "title", "canonicalSrc": "foo", "isReference": true @@ -17561,10 +17361,8 @@ { "type": "link", "attrs": { - "href": "/url", - "target": "_blank", - "class": null, "uploading": false, + "href": "/url", "title": "title", "canonicalSrc": "foo", "isReference": true @@ -17604,10 +17402,8 @@ { "type": "link", "attrs": { - "href": "/url", - "target": "_blank", - "class": null, "uploading": false, + "href": "/url", "title": "title", "canonicalSrc": "*foo* bar", "isReference": true @@ -17625,10 +17421,8 @@ { "type": "link", "attrs": { - "href": "/url", - "target": "_blank", - "class": null, "uploading": false, + "href": "/url", "title": "title", "canonicalSrc": "*foo* bar", "isReference": true @@ -17672,10 +17466,8 @@ { "type": "link", "attrs": { - "href": "/url", - "target": "_blank", - "class": null, "uploading": false, + "href": "/url", "title": "title", "canonicalSrc": "*foo* bar", "isReference": true @@ -17693,10 +17485,8 @@ { "type": "link", "attrs": { - "href": "/url", - "target": "_blank", - "class": null, "uploading": false, + "href": "/url", "title": "title", "canonicalSrc": "*foo* bar", "isReference": true @@ -17744,10 +17534,8 @@ { "type": "link", "attrs": { - "href": "/url", - "target": "_blank", - "class": null, "uploading": false, + "href": "/url", "title": null, "canonicalSrc": "foo", "isReference": true @@ -17787,10 +17575,8 @@ { "type": "link", "attrs": { - "href": "/url", - "target": "_blank", - "class": null, "uploading": false, + "href": "/url", "title": "title", "canonicalSrc": "foo", "isReference": true @@ -17830,10 +17616,8 @@ { "type": "link", "attrs": { - "href": "/url", - "target": "_blank", - "class": null, "uploading": false, + "href": "/url", "title": null, "canonicalSrc": "foo", "isReference": true @@ -17924,10 +17708,8 @@ { "type": "link", "attrs": { - "href": "/url", - "target": "_blank", - "class": null, "uploading": false, + "href": "/url", "title": null, "canonicalSrc": "foo*", "isReference": true @@ -17953,10 +17735,8 @@ { "type": "link", "attrs": { - "href": "/url2", - "target": "_blank", - "class": null, "uploading": false, + "href": "/url2", "title": null, "canonicalSrc": "bar", "isReference": true @@ -18010,10 +17790,8 @@ { "type": "link", "attrs": { - "href": "/url1", - "target": "_blank", - "class": null, "uploading": false, + "href": "/url1", "title": null, "canonicalSrc": "foo", "isReference": true @@ -18053,10 +17831,8 @@ { "type": "link", "attrs": { - "href": "", - "target": "_blank", - "class": null, "uploading": false, + "href": "", "title": null, "canonicalSrc": "", "isReference": false @@ -18096,10 +17872,8 @@ { "type": "link", "attrs": { - "href": "/url1", - "target": "_blank", - "class": null, "uploading": false, + "href": "/url1", "title": null, "canonicalSrc": "foo", "isReference": true @@ -18147,10 +17921,8 @@ { "type": "link", "attrs": { - "href": "/url", - "target": "_blank", - "class": null, "uploading": false, + "href": "/url", "title": null, "canonicalSrc": "baz", "isReference": true @@ -18190,10 +17962,8 @@ { "type": "link", "attrs": { - "href": "/url2", - "target": "_blank", - "class": null, "uploading": false, + "href": "/url2", "title": null, "canonicalSrc": "bar", "isReference": true @@ -18208,10 +17978,8 @@ { "type": "link", "attrs": { - "href": "/url1", - "target": "_blank", - "class": null, "uploading": false, + "href": "/url1", "title": null, "canonicalSrc": "baz", "isReference": true @@ -18269,10 +18037,8 @@ { "type": "link", "attrs": { - "href": "/url1", - "target": "_blank", - "class": null, "uploading": false, + "href": "/url1", "title": null, "canonicalSrc": "baz", "isReference": true @@ -19015,10 +18781,8 @@ { "type": "link", "attrs": { - "href": "/url", - "target": "_blank", - "class": null, "uploading": false, + "href": "/url", "title": "title", "canonicalSrc": "foo", "isReference": true @@ -19058,10 +18822,8 @@ { "type": "link", "attrs": { - "href": "http://foo.bar.baz", - "target": "_blank", - "class": null, "uploading": false, + "href": "http://foo.bar.baz", "title": null, "canonicalSrc": "http://foo.bar.baz", "isReference": false @@ -19087,10 +18849,8 @@ { "type": "link", "attrs": { - "href": "http://foo.bar.baz/test?q=hello&id=22&boolean", - "target": "_blank", - "class": null, "uploading": false, + "href": "http://foo.bar.baz/test?q=hello&id=22&boolean", "title": null, "canonicalSrc": "http://foo.bar.baz/test?q=hello&id=22&boolean", "isReference": false @@ -19116,10 +18876,8 @@ { "type": "link", "attrs": { - "href": null, - "target": "_blank", - "class": null, "uploading": false, + "href": null, "title": null, "canonicalSrc": null, "isReference": false @@ -19145,10 +18903,8 @@ { "type": "link", "attrs": { - "href": "MAILTO:FOO@BAR.BAZ", - "target": "_blank", - "class": null, "uploading": false, + "href": "MAILTO:FOO@BAR.BAZ", "title": null, "canonicalSrc": "MAILTO:FOO@BAR.BAZ", "isReference": false @@ -19174,10 +18930,8 @@ { "type": "link", "attrs": { - "href": null, - "target": "_blank", - "class": null, "uploading": false, + "href": null, "title": null, "canonicalSrc": null, "isReference": false @@ -19203,10 +18957,8 @@ { "type": "link", "attrs": { - "href": null, - "target": "_blank", - "class": null, "uploading": false, + "href": null, "title": null, "canonicalSrc": null, "isReference": false @@ -19232,10 +18984,8 @@ { "type": "link", "attrs": { - "href": "http://../", - "target": "_blank", - "class": null, "uploading": false, + "href": "http://../", "title": null, "canonicalSrc": "http://../", "isReference": false @@ -19261,10 +19011,8 @@ { "type": "link", "attrs": { - "href": null, - "target": "_blank", - "class": null, "uploading": false, + "href": null, "title": null, "canonicalSrc": null, "isReference": false @@ -19294,10 +19042,8 @@ { "type": "link", "attrs": { - "href": "http://foo.bar/baz", - "target": "_blank", - "class": null, "uploading": false, + "href": "http://foo.bar/baz", "title": null, "canonicalSrc": "http://foo.bar/baz", "isReference": false @@ -19327,10 +19073,8 @@ { "type": "link", "attrs": { - "href": "http://example.com/%5C%5B%5C", - "target": "_blank", - "class": null, "uploading": false, + "href": "http://example.com/%5C%5B%5C", "title": null, "canonicalSrc": "http://example.com/%5C%5B%5C", "isReference": false @@ -19356,10 +19100,8 @@ { "type": "link", "attrs": { - "href": "mailto:foo@bar.example.com", - "target": "_blank", - "class": null, "uploading": false, + "href": "mailto:foo@bar.example.com", "title": null, "canonicalSrc": "mailto:foo@bar.example.com", "isReference": false @@ -19385,10 +19127,8 @@ { "type": "link", "attrs": { - "href": "mailto:foo+special@Bar.baz-bar0.com", - "target": "_blank", - "class": null, "uploading": false, + "href": "mailto:foo+special@Bar.baz-bar0.com", "title": null, "canonicalSrc": "mailto:foo+special@Bar.baz-bar0.com", "isReference": false @@ -19418,10 +19158,8 @@ { "type": "link", "attrs": { - "href": "mailto:foo+@bar.example.com", - "target": "_blank", - "class": null, "uploading": false, + "href": "mailto:foo+@bar.example.com", "title": null, "canonicalSrc": "mailto:foo+@bar.example.com", "isReference": false @@ -19470,10 +19208,8 @@ { "type": "link", "attrs": { - "href": "http://foo.bar", - "target": "_blank", - "class": null, "uploading": false, + "href": "http://foo.bar", "title": null, "canonicalSrc": "http://foo.bar", "isReference": false @@ -19533,10 +19269,8 @@ { "type": "link", "attrs": { - "href": "http://example.com", - "target": "_blank", - "class": null, "uploading": false, + "href": "http://example.com", "title": null, "canonicalSrc": "http://example.com", "isReference": false @@ -19562,10 +19296,8 @@ { "type": "link", "attrs": { - "href": "mailto:foo@bar.example.com", - "target": "_blank", - "class": null, "uploading": false, + "href": "mailto:foo@bar.example.com", "title": null, "canonicalSrc": "mailto:foo@bar.example.com", "isReference": false @@ -19591,10 +19323,8 @@ { "type": "link", "attrs": { - "href": "http://www.commonmark.org", - "target": "_blank", - "class": null, "uploading": false, + "href": "http://www.commonmark.org", "title": null, "canonicalSrc": "http://www.commonmark.org", "isReference": false @@ -19624,10 +19354,8 @@ { "type": "link", "attrs": { - "href": "http://www.commonmark.org/help", - "target": "_blank", - "class": null, "uploading": false, + "href": "http://www.commonmark.org/help", "title": null, "canonicalSrc": "http://www.commonmark.org/help", "isReference": false @@ -19661,10 +19389,8 @@ { "type": "link", "attrs": { - "href": "http://www.commonmark.org", - "target": "_blank", - "class": null, "uploading": false, + "href": "http://www.commonmark.org", "title": null, "canonicalSrc": "http://www.commonmark.org", "isReference": false @@ -19692,10 +19418,8 @@ { "type": "link", "attrs": { - "href": "http://www.commonmark.org/a.b", - "target": "_blank", - "class": null, "uploading": false, + "href": "http://www.commonmark.org/a.b", "title": null, "canonicalSrc": "http://www.commonmark.org/a.b", "isReference": false @@ -19725,10 +19449,8 @@ { "type": "link", "attrs": { - "href": "http://www.google.com/search?q=Markup+(business)", - "target": "_blank", - "class": null, "uploading": false, + "href": "http://www.google.com/search?q=Markup+(business)", "title": null, "canonicalSrc": "http://www.google.com/search?q=Markup+(business)", "isReference": false @@ -19748,10 +19470,8 @@ { "type": "link", "attrs": { - "href": "http://www.google.com/search?q=Markup+(business)", - "target": "_blank", - "class": null, "uploading": false, + "href": "http://www.google.com/search?q=Markup+(business)", "title": null, "canonicalSrc": "http://www.google.com/search?q=Markup+(business)", "isReference": false @@ -19779,10 +19499,8 @@ { "type": "link", "attrs": { - "href": "http://www.google.com/search?q=Markup+(business)", - "target": "_blank", - "class": null, "uploading": false, + "href": "http://www.google.com/search?q=Markup+(business)", "title": null, "canonicalSrc": "http://www.google.com/search?q=Markup+(business)", "isReference": false @@ -19810,10 +19528,8 @@ { "type": "link", "attrs": { - "href": "http://www.google.com/search?q=Markup+(business)", - "target": "_blank", - "class": null, "uploading": false, + "href": "http://www.google.com/search?q=Markup+(business)", "title": null, "canonicalSrc": "http://www.google.com/search?q=Markup+(business)", "isReference": false @@ -19839,10 +19555,8 @@ { "type": "link", "attrs": { - "href": "http://www.google.com/search?q=(business))+ok", - "target": "_blank", - "class": null, "uploading": false, + "href": "http://www.google.com/search?q=(business))+ok", "title": null, "canonicalSrc": "http://www.google.com/search?q=(business))+ok", "isReference": false @@ -19868,10 +19582,8 @@ { "type": "link", "attrs": { - "href": "http://www.google.com/search?q=commonmark&hl=en", - "target": "_blank", - "class": null, "uploading": false, + "href": "http://www.google.com/search?q=commonmark&hl=en", "title": null, "canonicalSrc": "http://www.google.com/search?q=commonmark&hl=en", "isReference": false @@ -19891,10 +19603,8 @@ { "type": "link", "attrs": { - "href": "http://www.google.com/search?q=commonmark", - "target": "_blank", - "class": null, "uploading": false, + "href": "http://www.google.com/search?q=commonmark", "title": null, "canonicalSrc": "http://www.google.com/search?q=commonmark", "isReference": false @@ -19924,10 +19634,8 @@ { "type": "link", "attrs": { - "href": "http://www.commonmark.org/he", - "target": "_blank", - "class": null, "uploading": false, + "href": "http://www.commonmark.org/he", "title": null, "canonicalSrc": "http://www.commonmark.org/he", "isReference": false @@ -19957,10 +19665,8 @@ { "type": "link", "attrs": { - "href": "http://commonmark.org", - "target": "_blank", - "class": null, "uploading": false, + "href": "http://commonmark.org", "title": null, "canonicalSrc": "http://commonmark.org", "isReference": false @@ -19984,10 +19690,8 @@ { "type": "link", "attrs": { - "href": "https://encrypted.google.com/search?q=Markup+(business)", - "target": "_blank", - "class": null, "uploading": false, + "href": "https://encrypted.google.com/search?q=Markup+(business)", "title": null, "canonicalSrc": "https://encrypted.google.com/search?q=Markup+(business)", "isReference": false @@ -20026,10 +19730,8 @@ { "type": "link", "attrs": { - "href": "mailto:foo@bar.baz", - "target": "_blank", - "class": null, "uploading": false, + "href": "mailto:foo@bar.baz", "title": null, "canonicalSrc": "mailto:foo@bar.baz", "isReference": false @@ -20059,10 +19761,8 @@ { "type": "link", "attrs": { - "href": "mailto:hello+xyz@mail.example", - "target": "_blank", - "class": null, "uploading": false, + "href": "mailto:hello+xyz@mail.example", "title": null, "canonicalSrc": "mailto:hello+xyz@mail.example", "isReference": false @@ -20092,10 +19792,8 @@ { "type": "link", "attrs": { - "href": "mailto:a.b-c_d@a.b", - "target": "_blank", - "class": null, "uploading": false, + "href": "mailto:a.b-c_d@a.b", "title": null, "canonicalSrc": "mailto:a.b-c_d@a.b", "isReference": false @@ -20115,10 +19813,8 @@ { "type": "link", "attrs": { - "href": "mailto:a.b-c_d@a.b", - "target": "_blank", - "class": null, "uploading": false, + "href": "mailto:a.b-c_d@a.b", "title": null, "canonicalSrc": "mailto:a.b-c_d@a.b", "isReference": false @@ -21347,10 +21043,8 @@ { "type": "link", "attrs": { - "href": "/uploads/aa45a38ec2cfe97433281b10bbff042c/test-file.zip", - "target": "_blank", - "class": null, "uploading": false, + "href": "/uploads/aa45a38ec2cfe97433281b10bbff042c/test-file.zip", "title": null, "canonicalSrc": "/uploads/aa45a38ec2cfe97433281b10bbff042c/test-file.zip", "isReference": false @@ -21376,10 +21070,8 @@ { "type": "link", "attrs": { - "href": "/uploads/aa45a38ec2cfe97433281b10bbff042c/test-file.zip", - "target": "_blank", - "class": null, "uploading": false, + "href": "/uploads/aa45a38ec2cfe97433281b10bbff042c/test-file.zip", "title": null, "canonicalSrc": "/uploads/aa45a38ec2cfe97433281b10bbff042c/test-file.zip", "isReference": false @@ -21433,10 +21125,8 @@ { "type": "link", "attrs": { - "href": "test-file.zip", - "target": "_blank", - "class": null, "uploading": false, + "href": "test-file.zip", "title": null, "canonicalSrc": "test-file.zip", "isReference": false @@ -23118,10 +22808,8 @@ { "type": "link", "attrs": { - "href": "https://gitlab.com", - "target": "_blank", - "class": null, "uploading": false, + "href": "https://gitlab.com", "title": null, "canonicalSrc": "https://gitlab.com", "isReference": false diff --git a/package.json b/package.json index 71c2c49000ca968bb49a9d913a5052b0f97b2e1a..f02d0a1f8646270adb06d2dea6a85da9ea2dbcbc 100644 --- a/package.json +++ b/package.json @@ -169,6 +169,7 @@ "monaco-editor-webpack-plugin": "^6.0.0", "monaco-yaml": "4.0.0", "mousetrap": "1.6.5", + "orderedmap": "^2.1.1", "papaparse": "^5.3.1", "patch-package": "^6.4.7", "pdfjs-dist": "^2.16.105", diff --git a/spec/frontend/content_editor/extensions/paste_markdown_spec.js b/spec/frontend/content_editor/extensions/paste_markdown_spec.js index c9997e3c58fb83b97bb104cefc4729609bdd0201..baf0919fec80f7eb75da1fde4b1b9a7e4103ec89 100644 --- a/spec/frontend/content_editor/extensions/paste_markdown_spec.js +++ b/spec/frontend/content_editor/extensions/paste_markdown_spec.js @@ -4,24 +4,28 @@ import Diagram from '~/content_editor/extensions/diagram'; import Frontmatter from '~/content_editor/extensions/frontmatter'; import Heading from '~/content_editor/extensions/heading'; import Bold from '~/content_editor/extensions/bold'; +import Italic from '~/content_editor/extensions/italic'; import { VARIANT_DANGER } from '~/alert'; import eventHubFactory from '~/helpers/event_hub_factory'; import { ALERT_EVENT } from '~/content_editor/constants'; import waitForPromises from 'helpers/wait_for_promises'; +import MarkdownSerializer from '~/content_editor/services/markdown_serializer'; import { createTestEditor, createDocBuilder, waitUntilNextDocTransaction } from '../test_utils'; const CODE_BLOCK_HTML = '<pre class="js-syntax-highlight" lang="javascript">var a = 2;</pre>'; const DIAGRAM_HTML = '<img data-diagram="nomnoml" data-diagram-src="data:text/plain;base64,WzxmcmFtZT5EZWNvcmF0b3IgcGF0dGVybl0=">'; const FRONTMATTER_HTML = '<pre lang="yaml" data-lang-params="frontmatter">key: value</pre>'; -const PARAGRAPH_HTML = '<p>Just a regular paragraph</p>'; +const PARAGRAPH_HTML = '<p>Some text with <strong>bold</strong> and <em>italic</em> text.</p>'; describe('content_editor/extensions/paste_markdown', () => { let tiptapEditor; let doc; let p; let bold; + let italic; let heading; + let codeBlock; let renderMarkdown; let eventHub; const defaultData = { 'text/plain': '**bold text**' }; @@ -35,28 +39,36 @@ describe('content_editor/extensions/paste_markdown', () => { tiptapEditor = createTestEditor({ extensions: [ Bold, + Italic, CodeBlockHighlight, Diagram, Frontmatter, Heading, - PasteMarkdown.configure({ renderMarkdown, eventHub }), + PasteMarkdown.configure({ renderMarkdown, eventHub, serializer: new MarkdownSerializer() }), ], }); ({ - builders: { doc, p, bold, heading }, + builders: { doc, p, bold, italic, heading, codeBlock }, } = createDocBuilder({ tiptapEditor, names: { bold: { markType: Bold.name }, + italic: { markType: Italic.name }, heading: { nodeType: Heading.name }, + codeBlock: { nodeType: CodeBlockHighlight.name }, }, })); }); - const buildClipboardEvent = ({ data = {}, types = ['text/plain'] } = {}) => { - return Object.assign(new Event('paste'), { - clipboardData: { types, getData: jest.fn((type) => data[type] || defaultData[type]) }, + const buildClipboardEvent = ({ eventName = 'paste', data = {}, types = ['text/plain'] } = {}) => { + return Object.assign(new Event(eventName), { + clipboardData: { + types, + getData: jest.fn((type) => data[type] || defaultData[type]), + setData: jest.fn(), + clearData: jest.fn(), + }, }); }; @@ -80,13 +92,13 @@ describe('content_editor/extensions/paste_markdown', () => { }; it.each` - types | data | handled | desc - ${['text/plain']} | ${{}} | ${true} | ${'handles plain text'} - ${['text/plain', 'text/html']} | ${{}} | ${false} | ${'doesn’t handle html format'} - ${['text/plain', 'text/html', 'vscode-editor-data']} | ${{ 'vscode-editor-data': '{ "mode": "markdown" }' }} | ${true} | ${'handles vscode markdown'} - ${['text/plain', 'text/html', 'vscode-editor-data']} | ${{ 'vscode-editor-data': '{ "mode": "ruby" }' }} | ${false} | ${'doesn’t vscode code snippet'} - `('$desc', async ({ types, handled, data }) => { - expect(await triggerPasteEventHandler(buildClipboardEvent({ types, data }))).toBe(handled); + types | data | formatDesc + ${['text/plain']} | ${{}} | ${'plain text'} + ${['text/plain', 'text/html']} | ${{}} | ${'html format'} + ${['text/plain', 'text/html', 'vscode-editor-data']} | ${{ 'vscode-editor-data': '{ "mode": "markdown" }' }} | ${'vscode markdown'} + ${['text/plain', 'text/html', 'vscode-editor-data']} | ${{ 'vscode-editor-data': '{ "mode": "ruby" }' }} | ${'vscode snippet'} + `('handles $formatDesc', async ({ types, data }) => { + expect(await triggerPasteEventHandler(buildClipboardEvent({ types, data }))).toBe(true); }); it.each` @@ -101,6 +113,45 @@ describe('content_editor/extensions/paste_markdown', () => { expect(await triggerPasteEventHandler(buildClipboardEvent())).toBe(handled); }); + describe.each` + eventName | expectedDoc + ${'cut'} | ${() => doc(p())} + ${'copy'} | ${() => doc(p('Some text with ', bold('bold'), ' and ', italic('italic'), ' text.'))} + `('when $eventName event is triggered', ({ eventName, expectedDoc }) => { + let event; + beforeEach(() => { + event = buildClipboardEvent({ eventName }); + + jest.spyOn(event, 'preventDefault'); + jest.spyOn(event, 'stopPropagation'); + + tiptapEditor.commands.insertContent(PARAGRAPH_HTML); + tiptapEditor.commands.selectAll(); + tiptapEditor.view.dispatchEvent(event); + }); + + it('prevents default', () => { + expect(event.preventDefault).toHaveBeenCalled(); + expect(event.stopPropagation).toHaveBeenCalled(); + }); + + it('sets the clipboard data', () => { + expect(event.clipboardData.setData).toHaveBeenCalledWith( + 'text/plain', + 'Some text with bold and italic text.', + ); + expect(event.clipboardData.setData).toHaveBeenCalledWith('text/html', PARAGRAPH_HTML); + expect(event.clipboardData.setData).toHaveBeenCalledWith( + 'text/x-gfm', + 'Some text with **bold** and _italic_ text.', + ); + }); + + it('modifies the document', () => { + expect(tiptapEditor.state.doc.toJSON()).toEqual(expectedDoc().toJSON()); + }); + }); + describe('when pasting raw markdown source', () => { describe('when rendering markdown succeeds', () => { beforeEach(() => { @@ -162,6 +213,97 @@ describe('content_editor/extensions/paste_markdown', () => { }); }); + describe('when pasting html content', () => { + it('strips out any stray div, pre, span tags', async () => { + renderMarkdown.mockResolvedValueOnce( + '<div><span dir="auto"><strong>bold text</strong></span></div><pre><code>some code</code></pre>', + ); + + const expectedDoc = doc(p(bold('bold text')), p('some code')); + + await triggerPasteEventHandlerAndWaitForTransaction( + buildClipboardEvent({ + types: ['text/html'], + data: { + 'text/html': + '<div><span dir="auto"><strong>bold text</strong></span></div><pre><code>some code</code></pre>', + }, + }), + ); + + expect(tiptapEditor.state.doc.toJSON()).toEqual(expectedDoc.toJSON()); + }); + }); + + describe('when pasting text/x-gfm', () => { + it('processes the content as markdown, even if html content exists', async () => { + renderMarkdown.mockResolvedValueOnce('<strong>bold text</strong>'); + + const expectedDoc = doc(p(bold('bold text'))); + + await triggerPasteEventHandlerAndWaitForTransaction( + buildClipboardEvent({ + types: ['text/x-gfm'], + data: { + 'text/x-gfm': '**bold text**', + 'text/plain': 'irrelevant text', + 'text/html': '<div>some random irrelevant html</div>', + }, + }), + ); + + expect(tiptapEditor.state.doc.toJSON()).toEqual(expectedDoc.toJSON()); + }); + }); + + describe('when pasting vscode-editor-data', () => { + it('pastes the content as a code block', async () => { + renderMarkdown.mockResolvedValueOnce( + '<div class="gl-relative markdown-code-block js-markdown-code">
<pre data-sourcepos="1:1-3:3" data-canonical-lang="ruby" class="code highlight js-syntax-highlight language-ruby" lang="ruby" v-pre="true"><code><span id="LC1" class="line" lang="ruby"><span class="nb">puts</span> <span class="s2">"Hello World"</span></span></code></pre>
<copy-code></copy-code>
</div>', + ); + + const expectedDoc = doc( + codeBlock( + { language: 'ruby', class: 'code highlight js-syntax-highlight language-ruby' }, + 'puts "Hello World"', + ), + ); + + await triggerPasteEventHandlerAndWaitForTransaction( + buildClipboardEvent({ + types: ['vscode-editor-data', 'text/plain', 'text/html'], + data: { + 'vscode-editor-data': '{ "version": 1, "mode": "ruby" }', + 'text/plain': 'puts "Hello World"', + 'text/html': + '<meta charset=\'utf-8\'><div style="color: #d4d4d4;background-color: #1e1e1e;font-family: \'Fira Code\', Menlo, Monaco, \'Courier New\', monospace, Menlo, Monaco, \'Courier New\', monospace;font-weight: normal;font-size: 14px;line-height: 21px;white-space: pre;"><div><span style="color: #dcdcaa;">puts</span><span style="color: #d4d4d4;"> </span><span style="color: #ce9178;">"Hello world"</span></div></div>', + }, + }), + ); + + expect(tiptapEditor.state.doc.toJSON()).toEqual(expectedDoc.toJSON()); + }); + + it('pastes as regular markdown if language is markdown', async () => { + renderMarkdown.mockResolvedValueOnce('<p><strong>bold text</strong></p>'); + + const expectedDoc = doc(p(bold('bold text'))); + + await triggerPasteEventHandlerAndWaitForTransaction( + buildClipboardEvent({ + types: ['vscode-editor-data', 'text/plain', 'text/html'], + data: { + 'vscode-editor-data': '{ "version": 1, "mode": "markdown" }', + 'text/plain': '**bold text**', + 'text/html': '<p><strong>bold text</strong></p>', + }, + }), + ); + + expect(tiptapEditor.state.doc.toJSON()).toEqual(expectedDoc.toJSON()); + }); + }); + describe('when rendering markdown fails', () => { beforeEach(() => { renderMarkdown.mockRejectedValueOnce(); diff --git a/spec/frontend/content_editor/remark_markdown_processing_spec.js b/spec/frontend/content_editor/remark_markdown_processing_spec.js index 359e69c083a8f4ce802c01ea548da7b0d431d943..927a7d59899e680ff06f20d2ec0a56601082a96b 100644 --- a/spec/frontend/content_editor/remark_markdown_processing_spec.js +++ b/spec/frontend/content_editor/remark_markdown_processing_spec.js @@ -30,7 +30,7 @@ import TaskList from '~/content_editor/extensions/task_list'; import TaskItem from '~/content_editor/extensions/task_item'; import Video from '~/content_editor/extensions/video'; import remarkMarkdownDeserializer from '~/content_editor/services/remark_markdown_deserializer'; -import markdownSerializer from '~/content_editor/services/markdown_serializer'; +import MarkdownSerializer from '~/content_editor/services/markdown_serializer'; import { SAFE_VIDEO_EXT, SAFE_AUDIO_EXT, DIAGRAM_LANGUAGES } from '~/content_editor/constants'; import { createTestEditor, createDocBuilder } from './test_utils'; @@ -158,7 +158,7 @@ describe('Client side Markdown processing', () => { }; const serialize = (document) => - markdownSerializer({}).serialize({ + new MarkdownSerializer().serialize({ doc: document, pristineDoc: document, }); diff --git a/spec/frontend/content_editor/services/markdown_serializer_spec.js b/spec/frontend/content_editor/services/markdown_serializer_spec.js index 71885003b6ca434d00e773bd2e42709f2eed0bf6..0771eab201278aec54e7aeaeba253843a967b034 100644 --- a/spec/frontend/content_editor/services/markdown_serializer_spec.js +++ b/spec/frontend/content_editor/services/markdown_serializer_spec.js @@ -26,6 +26,8 @@ import Link from '~/content_editor/extensions/link'; import ListItem from '~/content_editor/extensions/list_item'; import OrderedList from '~/content_editor/extensions/ordered_list'; import Paragraph from '~/content_editor/extensions/paragraph'; +import Reference from '~/content_editor/extensions/reference'; +import ReferenceLabel from '~/content_editor/extensions/reference_label'; import ReferenceDefinition from '~/content_editor/extensions/reference_definition'; import Sourcemap from '~/content_editor/extensions/sourcemap'; import Strike from '~/content_editor/extensions/strike'; @@ -35,7 +37,7 @@ import TableHeader from '~/content_editor/extensions/table_header'; import TableRow from '~/content_editor/extensions/table_row'; import TaskItem from '~/content_editor/extensions/task_item'; import TaskList from '~/content_editor/extensions/task_list'; -import markdownSerializer from '~/content_editor/services/markdown_serializer'; +import MarkdownSerializer from '~/content_editor/services/markdown_serializer'; import remarkMarkdownDeserializer from '~/content_editor/services/remark_markdown_deserializer'; import { createTiptapEditor, createDocBuilder } from '../test_utils'; @@ -76,6 +78,8 @@ const { orderedList, paragraph, referenceDefinition, + reference, + referenceLabel, strike, table, tableCell, @@ -116,6 +120,8 @@ const { orderedList: { nodeType: OrderedList.name }, paragraph: { nodeType: Paragraph.name }, referenceDefinition: { nodeType: ReferenceDefinition.name }, + reference: { nodeType: Reference.name }, + referenceLabel: { nodeType: ReferenceLabel.name }, strike: { markType: Strike.name }, table: { nodeType: Table.name }, tableCell: { nodeType: TableCell.name }, @@ -134,7 +140,7 @@ const { }); const serialize = (...content) => - markdownSerializer({}).serialize({ + new MarkdownSerializer().serialize({ doc: doc(...content), }); @@ -281,6 +287,77 @@ hi ).toBe('![GitLab][gitlab-url]'); }); + it('correctly serializes references', () => { + expect( + serialize( + paragraph( + reference({ + referenceType: 'issue', + originalText: '#123', + href: '/gitlab-org/gitlab-test/-/issues/123', + text: '#123', + }), + ), + ), + ).toBe('#123'); + }); + + it('correctly renders a reference label', () => { + expect( + serialize( + paragraph( + referenceLabel({ + referenceType: 'label', + originalText: '~foo', + href: '/gitlab-org/gitlab-test/-/labels/foo', + text: '~foo', + }), + ), + ), + ).toBe('~foo'); + }); + + it('correctly renders a reference label without originalText', () => { + expect( + serialize( + paragraph( + referenceLabel({ + referenceType: 'label', + href: '/gitlab-org/gitlab-test/-/labels/foo', + text: 'Foo Bar', + }), + ), + ), + ).toBe('~"Foo Bar"'); + }); + + it('ensures spaces between multiple references', () => { + expect( + serialize( + paragraph( + reference({ + referenceType: 'issue', + originalText: '#123', + href: '/gitlab-org/gitlab-test/-/issues/123', + text: '#123', + }), + referenceLabel({ + referenceType: 'label', + originalText: '~foo', + href: '/gitlab-org/gitlab-test/-/labels/foo', + text: '~foo', + }), + reference({ + referenceType: 'issue', + originalText: '#456', + href: '/gitlab-org/gitlab-test/-/issues/456', + text: '#456', + }), + ), + ), + ).toBe('#123 ~foo #456'); + }); + it.each` src ${'data:image/png;base64,iVBORw0KGgoAAAAN'} @@ -1485,7 +1562,7 @@ paragraph editAction(document); - const serialized = markdownSerializer({}).serialize({ + const serialized = new MarkdownSerializer().serialize({ pristineDoc: document, doc: tiptapEditor.state.doc, }); diff --git a/spec/frontend/content_editor/test_utils.js b/spec/frontend/content_editor/test_utils.js index 9357381c0535ac1741e8fea89682437c5d8343f9..2184a829cf0cc93d41645035fb6c1af47efabe1b 100644 --- a/spec/frontend/content_editor/test_utils.js +++ b/spec/frontend/content_editor/test_utils.js @@ -37,6 +37,8 @@ import Link from '~/content_editor/extensions/link'; import ListItem from '~/content_editor/extensions/list_item'; import OrderedList from '~/content_editor/extensions/ordered_list'; import ReferenceDefinition from '~/content_editor/extensions/reference_definition'; +import Reference from '~/content_editor/extensions/reference'; +import ReferenceLabel from '~/content_editor/extensions/reference_label'; import Strike from '~/content_editor/extensions/strike'; import Table from '~/content_editor/extensions/table'; import TableCell from '~/content_editor/extensions/table_cell'; @@ -291,6 +293,8 @@ export const createTiptapEditor = (extensions = []) => ListItem, OrderedList, ReferenceDefinition, + Reference, + ReferenceLabel, Strike, Table, TableCell, diff --git a/spec/scripts/lib/glfm/update_example_snapshots_spec.rb b/spec/scripts/lib/glfm/update_example_snapshots_spec.rb index f2194f46ab411fe48f5ca096d65d841d184ec486..87b2c42c5b8b9d943b1b3f2e07bd01df5caf15de 100644 --- a/spec/scripts/lib/glfm/update_example_snapshots_spec.rb +++ b/spec/scripts/lib/glfm/update_example_snapshots_spec.rb @@ -815,10 +815,8 @@ { "type": "link", "attrs": { - "href": "/uploads/groups-test-file", - "target": "_blank", - "class": null, "uploading": false, + "href": "/uploads/groups-test-file", "title": null, "canonicalSrc": "/uploads/groups-test-file", "isReference": false @@ -844,10 +842,8 @@ { "type": "link", "attrs": { - "href": "projects-test-file", - "target": "_blank", - "class": null, "uploading": false, + "href": "projects-test-file", "title": null, "canonicalSrc": "projects-test-file", "isReference": false @@ -903,10 +899,8 @@ { "type": "link", "attrs": { - "href": "project-wikis-test-file", - "target": "_blank", - "class": null, "uploading": false, + "href": "project-wikis-test-file", "title": null, "canonicalSrc": "project-wikis-test-file", "isReference": false diff --git a/yarn.lock b/yarn.lock index 2f04b2236d4b6e11b135c2d44bfa56a031931341..cb4fe862d0fb046d72afd452bf0f8fccf93bd6c5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9872,10 +9872,10 @@ optionator@^0.9.1: type-check "^0.4.0" word-wrap "^1.2.3" -orderedmap@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/orderedmap/-/orderedmap-2.0.0.tgz#12ff5ef6ea9d12d6430b80c701b35475e1c9ff34" - integrity sha512-buf4PoAMlh45b8a8gsGy/X6w279TSqkyAS0C0wdTSJwFSU+ljQFJON5I8NfjLHoCXwpSROIo2wr0g33T+kQshQ== +orderedmap@^2.0.0, orderedmap@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/orderedmap/-/orderedmap-2.1.1.tgz#61481269c44031c449915497bf5a4ad273c512d2" + integrity sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g== os-browserify@^0.3.0: version "0.3.0"