diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-02-23 06:09:23 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-02-23 06:09:23 +0000 |
commit | 8b4276f873461953ee5a1fc46f084779f5847e3a (patch) | |
tree | cc3435570e15234453e711c2ddcc9b0895d87eb4 | |
parent | f34b26bb882947bcc1126de19fa55eb8763af32e (diff) | |
download | gitlab-ce-8b4276f873461953ee5a1fc46f084779f5847e3a.tar.gz |
Add latest changes from gitlab-org/gitlab@master
47 files changed, 1933 insertions, 374 deletions
diff --git a/app/assets/javascripts/behaviors/markdown/copy_as_gfm.js b/app/assets/javascripts/behaviors/markdown/copy_as_gfm.js index 19ebab36481..36317444af9 100644 --- a/app/assets/javascripts/behaviors/markdown/copy_as_gfm.js +++ b/app/assets/javascripts/behaviors/markdown/copy_as_gfm.js @@ -164,7 +164,7 @@ export class CopyAsGFM { static nodeToGFM(node) { return Promise.all([ - import(/* webpackChunkName: 'gfm_copy_extra' */ 'prosemirror-model'), + import(/* webpackChunkName: 'gfm_copy_extra' */ '@tiptap/pm/model'), import(/* webpackChunkName: 'gfm_copy_extra' */ './schema'), import(/* webpackChunkName: 'gfm_copy_extra' */ './serializer'), ]) diff --git a/app/assets/javascripts/behaviors/markdown/schema.js b/app/assets/javascripts/behaviors/markdown/schema.js index 1b0f46ff4cb..31bab23c8b0 100644 --- a/app/assets/javascripts/behaviors/markdown/schema.js +++ b/app/assets/javascripts/behaviors/markdown/schema.js @@ -1,4 +1,4 @@ -import { Schema } from 'prosemirror-model'; +import { Schema } from '@tiptap/pm/model'; import editorExtensions from './editor_extensions'; const nodes = editorExtensions.nodes.reduce( diff --git a/app/assets/javascripts/content_editor/components/wrappers/table_cell_base.vue b/app/assets/javascripts/content_editor/components/wrappers/table_cell_base.vue index 6456540a0dd..4d948f4ec05 100644 --- a/app/assets/javascripts/content_editor/components/wrappers/table_cell_base.vue +++ b/app/assets/javascripts/content_editor/components/wrappers/table_cell_base.vue @@ -1,7 +1,7 @@ <script> import { GlDropdown, GlDropdownItem, GlDropdownDivider } from '@gitlab/ui'; import { NodeViewWrapper, NodeViewContent } from '@tiptap/vue-2'; -import { selectedRect as getSelectedRect } from '@_ueberdosis/prosemirror-tables'; +import { selectedRect as getSelectedRect } from '@tiptap/pm/tables'; import { __ } from '~/locale'; const TABLE_CELL_HEADER = 'th'; diff --git a/app/assets/javascripts/content_editor/extensions/attachment.js b/app/assets/javascripts/content_editor/extensions/attachment.js index 9634730f637..0d5b8e56a6c 100644 --- a/app/assets/javascripts/content_editor/extensions/attachment.js +++ b/app/assets/javascripts/content_editor/extensions/attachment.js @@ -1,5 +1,5 @@ import { Extension } from '@tiptap/core'; -import { Plugin, PluginKey } from 'prosemirror-state'; +import { Plugin, PluginKey } from '@tiptap/pm/state'; import { handleFileEvent } from '../services/upload_helpers'; export default Extension.create({ diff --git a/app/assets/javascripts/content_editor/extensions/color_chip.js b/app/assets/javascripts/content_editor/extensions/color_chip.js index deb5029a1f0..c49b541bbaf 100644 --- a/app/assets/javascripts/content_editor/extensions/color_chip.js +++ b/app/assets/javascripts/content_editor/extensions/color_chip.js @@ -1,6 +1,6 @@ import { Node } from '@tiptap/core'; -import { Plugin, PluginKey } from 'prosemirror-state'; -import { Decoration, DecorationSet } from 'prosemirror-view'; +import { Plugin, PluginKey } from '@tiptap/pm/state'; +import { Decoration, DecorationSet } from '@tiptap/pm/view'; import { isValidColorExpression } from '~/lib/utils/color_utils'; import { PARSE_HTML_PRIORITY_HIGHEST } from '../constants'; diff --git a/app/assets/javascripts/content_editor/extensions/external_keydown_handler.js b/app/assets/javascripts/content_editor/extensions/external_keydown_handler.js index e940614083e..e48100c15a7 100644 --- a/app/assets/javascripts/content_editor/extensions/external_keydown_handler.js +++ b/app/assets/javascripts/content_editor/extensions/external_keydown_handler.js @@ -1,5 +1,5 @@ import { Extension } from '@tiptap/core'; -import { Plugin, PluginKey } from 'prosemirror-state'; +import { Plugin, PluginKey } from '@tiptap/pm/state'; import { KEYDOWN_EVENT } from '../constants'; /** diff --git a/app/assets/javascripts/content_editor/extensions/paste_markdown.js b/app/assets/javascripts/content_editor/extensions/paste_markdown.js index 848c4c12a9a..fa465160598 100644 --- a/app/assets/javascripts/content_editor/extensions/paste_markdown.js +++ b/app/assets/javascripts/content_editor/extensions/paste_markdown.js @@ -1,5 +1,5 @@ import { Extension } from '@tiptap/core'; -import { Plugin, PluginKey } from 'prosemirror-state'; +import { Plugin, PluginKey } from '@tiptap/pm/state'; import { __ } from '~/locale'; import { VARIANT_DANGER } from '~/flash'; import createMarkdownDeserializer from '../services/gl_api_markdown_deserializer'; diff --git a/app/assets/javascripts/content_editor/extensions/suggestions.js b/app/assets/javascripts/content_editor/extensions/suggestions.js index a9628c78add..eb53a3a61b3 100644 --- a/app/assets/javascripts/content_editor/extensions/suggestions.js +++ b/app/assets/javascripts/content_editor/extensions/suggestions.js @@ -2,7 +2,7 @@ import { Node } from '@tiptap/core'; import { VueRenderer } from '@tiptap/vue-2'; import tippy from 'tippy.js'; import Suggestion from '@tiptap/suggestion'; -import { PluginKey } from 'prosemirror-state'; +import { PluginKey } from '@tiptap/pm/state'; import { isFunction, uniqueId, memoize } from 'lodash'; import axios from '~/lib/utils/axios_utils'; import { initEmojiMap, getAllEmoji } from '~/emoji'; diff --git a/app/assets/javascripts/content_editor/services/gl_api_markdown_deserializer.js b/app/assets/javascripts/content_editor/services/gl_api_markdown_deserializer.js index 796dc06ad93..e65644d2c5c 100644 --- a/app/assets/javascripts/content_editor/services/gl_api_markdown_deserializer.js +++ b/app/assets/javascripts/content_editor/services/gl_api_markdown_deserializer.js @@ -1,4 +1,4 @@ -import { DOMParser as ProseMirrorDOMParser } from 'prosemirror-model'; +import { DOMParser as ProseMirrorDOMParser } from '@tiptap/pm/model'; import { replaceCommentsWith } from '~/lib/utils/dom_utils'; export default ({ render }) => { diff --git a/app/assets/javascripts/content_editor/services/hast_to_prosemirror_converter.js b/app/assets/javascripts/content_editor/services/hast_to_prosemirror_converter.js index 28a50adca6b..c8972515c25 100644 --- a/app/assets/javascripts/content_editor/services/hast_to_prosemirror_converter.js +++ b/app/assets/javascripts/content_editor/services/hast_to_prosemirror_converter.js @@ -19,7 +19,7 @@ * visit-parents documentation: https://github.com/syntax-tree/unist-util-visit-parents */ -import { Mark } from 'prosemirror-model'; +import { Mark } from '@tiptap/pm/model'; import { visitParents, SKIP } from 'unist-util-visit-parents'; import { isFunction, isString, noop, mapValues } from 'lodash'; diff --git a/app/assets/javascripts/drawio/constants.js b/app/assets/javascripts/drawio/constants.js new file mode 100644 index 00000000000..a5f1d1e71d2 --- /dev/null +++ b/app/assets/javascripts/drawio/constants.js @@ -0,0 +1,13 @@ +/* + * TODO: Make this URL configurable + */ +export const DRAWIO_EDITOR_URL = + 'https://embed.diagrams.net/?ui=sketch&noSaveBtn=1&saveAndExit=1&keepmodified=1&spin=1&embed=1&libraries=1&configure=1&proto=json&toSvg=1'; // TODO Make it configurable + +export const DRAWIO_FRAME_ID = 'drawio-frame'; + +export const DARK_BACKGROUND_COLOR = '#202020'; + +export const DIAGRAM_BACKGROUND_COLOR = '#ffffff'; + +export const DRAWIO_IFRAME_TIMEOUT = 4000; diff --git a/app/assets/javascripts/drawio/drawio_editor.js b/app/assets/javascripts/drawio/drawio_editor.js new file mode 100644 index 00000000000..06e7f536426 --- /dev/null +++ b/app/assets/javascripts/drawio/drawio_editor.js @@ -0,0 +1,274 @@ +import _ from 'lodash'; +import { createAlert, VARIANT_SUCCESS } from '~/flash'; +import { darkModeEnabled } from '~/lib/utils/color_utils'; +import { __ } from '~/locale'; +import { setAttributes } from '~/lib/utils/dom_utils'; +import { + DARK_BACKGROUND_COLOR, + DRAWIO_EDITOR_URL, + DRAWIO_FRAME_ID, + DIAGRAM_BACKGROUND_COLOR, + DRAWIO_IFRAME_TIMEOUT, +} from './constants'; + +function updateDrawioEditorState(drawIOEditorState, data) { + Object.assign(drawIOEditorState, data); +} + +function postMessageToDrawioEditor(drawIOEditorState, message) { + const { origin } = new URL(DRAWIO_EDITOR_URL); + + drawIOEditorState.iframe.contentWindow.postMessage(JSON.stringify(message), origin); +} + +function disposeDrawioEditor(drawIOEditorState) { + drawIOEditorState.disposeEventListener(); + drawIOEditorState.iframe.remove(); +} + +function getSvg(data) { + const svgPath = atob(data.substring(data.indexOf(',') + 1)); + + return `<?xml version="1.0" encoding="UTF-8"?>\n\ + <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\n\ + ${svgPath}`; +} + +async function saveDiagram(drawIOEditorState, editorFacade) { + const { newDiagram, diagramMarkdown, filename, diagramSvg } = drawIOEditorState; + const filenameWithExt = filename.endsWith('.drawio.svg') ? filename : `${filename}.drawio.svg`; + + postMessageToDrawioEditor(drawIOEditorState, { + action: 'spinner', + show: true, + messageKey: 'saving', + }); + + try { + const uploadResults = await editorFacade.uploadDiagram({ + filename: filenameWithExt, + diagramSvg, + }); + + if (newDiagram) { + editorFacade.insertDiagram({ uploadResults }); + } else { + editorFacade.updateDiagram({ diagramMarkdown, uploadResults }); + } + + createAlert({ + message: __('Diagram saved successfully.'), + variant: VARIANT_SUCCESS, + fadeTransition: true, + }); + setTimeout(() => disposeDrawioEditor(drawIOEditorState), 10); + } catch { + postMessageToDrawioEditor(drawIOEditorState, { action: 'spinner', show: false }); + postMessageToDrawioEditor(drawIOEditorState, { + action: 'dialog', + titleKey: 'error', + modified: true, + buttonKey: 'close', + messageKey: 'errorSavingFile', + }); + } +} + +function promptName(drawIOEditorState, name, errKey) { + postMessageToDrawioEditor(drawIOEditorState, { + action: 'prompt', + titleKey: 'filename', + okKey: 'save', + defaultValue: name || '', + }); + + if (errKey !== null) { + postMessageToDrawioEditor(drawIOEditorState, { + action: 'dialog', + titleKey: 'error', + messageKey: errKey, + buttonKey: 'ok', + }); + } +} + +function sendLoadDiagramMessage(drawIOEditorState) { + postMessageToDrawioEditor(drawIOEditorState, { + action: 'load', + xml: drawIOEditorState.diagramSvg, + border: 8, + background: DIAGRAM_BACKGROUND_COLOR, + dark: drawIOEditorState.dark, + title: drawIOEditorState.filename, + }); +} + +async function loadExistingDiagram(drawIOEditorState, editorFacade) { + let diagram = null; + + try { + diagram = await editorFacade.getDiagram(); + } catch (e) { + throw new Error(__('Cannot load the diagram into the draw.io editor')); + } + + if (diagram) { + const { diagramMarkdown, filename, diagramSvg, contentType } = diagram; + + if (contentType !== 'image/svg+xml') { + throw new Error(__('The selected image is not a diagram')); + } + + updateDrawioEditorState(drawIOEditorState, { + newDiagram: false, + filename, + diagramMarkdown, + diagramSvg, + }); + } else { + updateDrawioEditorState(drawIOEditorState, { + newDiagram: true, + }); + } + + sendLoadDiagramMessage(drawIOEditorState); +} + +async function prepareEditor(drawIOEditorState, editorFacade) { + const { iframe } = drawIOEditorState; + + iframe.style.cursor = 'wait'; + + try { + await loadExistingDiagram(drawIOEditorState, editorFacade); + + iframe.style.visibility = ''; + iframe.style.cursor = ''; + window.scrollTo(0, 0); + } catch (e) { + createAlert({ + message: e.message, + error: e, + }); + disposeDrawioEditor(drawIOEditorState); + } +} + +function configureDrawIOEditor(drawIOEditorState) { + postMessageToDrawioEditor(drawIOEditorState, { + action: 'configure', + config: { + darkColor: DARK_BACKGROUND_COLOR, + settingsName: 'gitlab', + }, + colorSchemeMeta: drawIOEditorState.dark, // For transparent iframe background in dark mode + }); + updateDrawioEditorState(drawIOEditorState, { + initialized: true, + }); +} + +function onDrawIOEditorMessage(drawIOEditorState, editorFacade, evt) { + if (_.isNil(evt) || evt.source !== drawIOEditorState.iframe.contentWindow) { + return; + } + + const msg = JSON.parse(evt.data); + + if (msg.event === 'configure') { + configureDrawIOEditor(drawIOEditorState); + } else if (msg.event === 'init') { + prepareEditor(drawIOEditorState, editorFacade); + } else if (msg.event === 'exit') { + disposeDrawioEditor(drawIOEditorState); + } else if (msg.event === 'prompt') { + updateDrawioEditorState(drawIOEditorState, { + filename: msg.value, + }); + + if (!drawIOEditorState.filename) { + promptName(drawIOEditorState, 'diagram.drawio.svg', 'filenameShort'); + } else { + saveDiagram(drawIOEditorState, editorFacade); + } + } else if (msg.event === 'export') { + updateDrawioEditorState(drawIOEditorState, { + diagramSvg: getSvg(msg.data), + }); + // TODO Add this to draw.io editor configuration + sendLoadDiagramMessage(drawIOEditorState); // Save removes diagram from the editor, so we need to reload it. + postMessageToDrawioEditor(drawIOEditorState, { action: 'status', modified: true }); // And set editor modified flag to true. + if (!drawIOEditorState.filename) { + promptName(drawIOEditorState, 'diagram.drawio.svg', null); + } else { + saveDiagram(drawIOEditorState, editorFacade); + } + } +} + +function createEditorIFrame(drawIOEditorState) { + const iframe = document.createElement('iframe'); + + setAttributes(iframe, { + id: DRAWIO_FRAME_ID, + src: DRAWIO_EDITOR_URL, + }); + + iframe.style.position = 'absolute'; + iframe.style.border = '0'; + iframe.style.top = '0px'; + iframe.style.left = '0px'; + iframe.style.width = '100%'; + iframe.style.height = '100%'; + iframe.style.zIndex = '1100'; + iframe.style.visibility = 'hidden'; + + document.body.appendChild(iframe); + + setTimeout(() => { + if (drawIOEditorState.initialized === false) { + disposeDrawioEditor(drawIOEditorState); + createAlert({ message: __('The draw.io editor could not be loaded.') }); + } + }, DRAWIO_IFRAME_TIMEOUT); + + updateDrawioEditorState(drawIOEditorState, { + iframe, + }); +} + +function attachDrawioIFrameMessageListener(drawIOEditorState, editorFacade) { + const evtHandler = (evt) => { + onDrawIOEditorMessage(drawIOEditorState, editorFacade, evt); + }; + + window.addEventListener('message', evtHandler); + + // Stores a function in the editor state object that allows disposing + // the message event listener when the editor exits. + updateDrawioEditorState(drawIOEditorState, { + disposeEventListener: () => { + window.removeEventListener('message', evtHandler); + }, + }); +} + +const createDrawioEditorState = ({ filename = null }) => ({ + newDiagram: true, + filename, + diagramSvg: null, + diagramMarkdown: null, + iframe: null, + isBusy: false, + initialized: false, + dark: darkModeEnabled(), + disposeEventListener: null, +}); + +export function launchDrawioEditor({ editorFacade, filename }) { + const drawIOEditorState = createDrawioEditorState({ filename }); + + // The execution order of these two functions matter + attachDrawioIFrameMessageListener(drawIOEditorState, editorFacade); + createEditorIFrame(drawIOEditorState); +} diff --git a/app/assets/javascripts/drawio/markdown_field_editor_facade.js b/app/assets/javascripts/drawio/markdown_field_editor_facade.js new file mode 100644 index 00000000000..b2506ce6bf8 --- /dev/null +++ b/app/assets/javascripts/drawio/markdown_field_editor_facade.js @@ -0,0 +1,71 @@ +import { insertMarkdownText, resolveSelectedImage } from '~/lib/utils/text_markdown'; +import axios from '~/lib/utils/axios_utils'; + +/** + * A set of functions to decouple the markdown_field component from + * the draw.io editor. + * It allows the draw.io editor to obtain a selected drawio_diagram + * and replace it or insert a new drawio_diagram node without coupling + * the drawio_editor to the Markdown Field implementation details + * + * @param {Object} params Factory function parameters + * @param {Object} params.textArea Textarea used to edit and display markdown source + * @param {String} params.markdownPreviewPath API endpoint to render Markdown + * @param {String} params.uploadsPath API endpoint to upload files + * + * @returns A markdown_field_facade object with operations + * with operations to get a selected diagram, upload a diagram, + * insert a new one in the Markdown Field, and update + * an existing’s diagram URL. + */ +export const create = ({ textArea, markdownPreviewPath, uploadsPath }) => ({ + getDiagram: async () => { + const image = await resolveSelectedImage(textArea, markdownPreviewPath); + + if (!image) { + return null; + } + + const { imageURL, imageMarkdown, filename } = image; + const response = await axios.get(imageURL, { responseType: 'text' }); + const diagramSvg = response.data; + const contentType = response.headers['content-type']; + + return { + diagramMarkdown: imageMarkdown, + filename, + diagramSvg, + contentType, + }; + }, + updateDiagram: ({ uploadResults, diagramMarkdown }) => { + textArea.focus(); + + // eslint-disable-next-line no-param-reassign + textArea.value = textArea.value.replace(diagramMarkdown, uploadResults.link.markdown); + textArea.dispatchEvent(new Event('input')); + }, + insertDiagram: ({ uploadResults }) => { + textArea.focus(); + const markdown = textArea.value; + const selectedMD = markdown.substring(textArea.selectionStart, textArea.selectionEnd); + + // This method dispatches the input event. + insertMarkdownText({ + textArea, + text: markdown, + tag: uploadResults.link.markdown, + selected: selectedMD, + }); + }, + uploadDiagram: async ({ filename, diagramSvg }) => { + const blob = new Blob([diagramSvg], { type: 'image/svg+xml' }); + const formData = new FormData(); + + formData.append('file', blob, filename); + + const response = await axios.post(uploadsPath, formData); + + return response.data; + }, +}); diff --git a/app/assets/javascripts/lib/utils/text_markdown.js b/app/assets/javascripts/lib/utils/text_markdown.js index 05ed08931bb..ebc118c0d87 100644 --- a/app/assets/javascripts/lib/utils/text_markdown.js +++ b/app/assets/javascripts/lib/utils/text_markdown.js @@ -2,6 +2,7 @@ import $ from 'jquery'; import Shortcuts from '~/behaviors/shortcuts/shortcuts'; import { insertText } from '~/lib/utils/common_utils'; +import axios from '~/lib/utils/axios_utils'; const LINK_TAG_PATTERN = '[{text}](url)'; const INDENT_CHAR = ' '; @@ -669,3 +670,50 @@ export function removeMarkdownListeners(form) { // eslint-disable-next-line @gitlab/no-global-event-off return $('.js-md', form).off('click'); } + +/** + * If the textarea cursor is positioned in a Markdown image declaration, + * it uses the Markdown API to resolve the image’s absolute URL. + * @param {Object} textarea Textarea DOM element + * @param {String} markdownPreviewPath Markdown API path + * @returns {Object} an object containing the image’s absolute URL, filename, + * and the markdown declaration. If the textarea cursor is not positioned + * in an image, it returns null. + */ +export const resolveSelectedImage = async (textArea, markdownPreviewPath = '') => { + const { lines, startPos } = linesFromSelection(textArea); + + // image declarations can’t span more than one line in Markdown + if (lines > 0) { + return null; + } + + const selectedLine = lines[0]; + + if (!/!\[.+?\]\(.+?\)/.test(selectedLine)) return null; + + const lineSelectionStart = textArea.selectionStart - startPos; + const preExlm = selectedLine.substring(0, lineSelectionStart).lastIndexOf('!'); + const postClose = selectedLine.substring(lineSelectionStart).indexOf(')'); + + if (preExlm >= 0 && postClose >= 0) { + const imageMarkdown = selectedLine.substring(preExlm, lineSelectionStart + postClose + 1); + const { data } = await axios.post(markdownPreviewPath, { text: imageMarkdown }); + const parser = new DOMParser(); + + const dom = parser.parseFromString(data.body, 'text/html'); + const imageURL = dom.body.querySelector('a').getAttribute('href'); + + if (imageURL) { + const filename = imageURL.substring(imageURL.lastIndexOf('/') + 1); + + return { + imageMarkdown, + imageURL, + filename, + }; + } + } + + return null; +}; diff --git a/app/assets/javascripts/pages/shared/wikis/components/wiki_form.vue b/app/assets/javascripts/pages/shared/wikis/components/wiki_form.vue index 0d2bbfbbc43..549c964cce4 100644 --- a/app/assets/javascripts/pages/shared/wikis/components/wiki_form.vue +++ b/app/assets/javascripts/pages/shared/wikis/components/wiki_form.vue @@ -351,6 +351,7 @@ export default { :enable-content-editor="isMarkdownFormat" :enable-preview="isMarkdownFormat" :autofocus="pageInfo.persisted" + :drawio-enabled="true" @contentEditor="notifyContentEditorActive" @markdownField="notifyContentEditorInactive" @keydown.ctrl.enter="submitFormShortcut" diff --git a/app/assets/javascripts/vue_shared/components/markdown/drawio_toolbar_button.vue b/app/assets/javascripts/vue_shared/components/markdown/drawio_toolbar_button.vue new file mode 100644 index 00000000000..a66becb5c92 --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/markdown/drawio_toolbar_button.vue @@ -0,0 +1,48 @@ +<script> +import { GlButton, GlTooltipDirective } from '@gitlab/ui'; +import { launchDrawioEditor } from '~/drawio/drawio_editor'; +import { create } from '~/drawio/markdown_field_editor_facade'; + +export default { + components: { + GlButton, + }, + directives: { + GlTooltip: GlTooltipDirective, + }, + props: { + uploadsPath: { + type: String, + required: true, + }, + markdownPreviewPath: { + type: String, + required: true, + }, + }, + methods: { + getTextArea() { + return document.querySelector('.js-gfm-input'); + }, + launchDrawioEditor() { + launchDrawioEditor({ + editorFacade: create({ + uploadsPath: this.uploadsPath, + textArea: this.getTextArea(), + markdownPreviewPath: this.markdownPreviewPath, + }), + }); + }, + }, +}; +</script> +<template> + <gl-button + v-gl-tooltip + :title="__('Insert or edit diagram')" + :aria-label="__('Insert or edit diagram')" + category="tertiary" + icon="diagram" + @click="launchDrawioEditor" + /> +</template> diff --git a/app/assets/javascripts/vue_shared/components/markdown/field.vue b/app/assets/javascripts/vue_shared/components/markdown/field.vue index 6f4cddbdfa2..bca7d90135e 100644 --- a/app/assets/javascripts/vue_shared/components/markdown/field.vue +++ b/app/assets/javascripts/vue_shared/components/markdown/field.vue @@ -132,6 +132,11 @@ export default { required: false, default: false, }, + drawioEnabled: { + type: Boolean, + required: false, + default: false, + }, }, data() { return { @@ -355,6 +360,10 @@ export default { :enable-preview="enablePreview" :show-suggest-popover="showSuggestPopover" :suggestion-start-index="suggestionsStartIndex" + :uploads-path="uploadsPath" + :markdown-preview-path="markdownPreviewPath" + :drawio-enabled="drawioEnabled" + data-testid="markdownHeader" :restricted-tool-bar-items="restrictedToolBarItems" @preview-markdown="showPreviewTab" @write-markdown="showWriteTab" diff --git a/app/assets/javascripts/vue_shared/components/markdown/header.vue b/app/assets/javascripts/vue_shared/components/markdown/header.vue index e83441e59a2..5d692c69fc0 100644 --- a/app/assets/javascripts/vue_shared/components/markdown/header.vue +++ b/app/assets/javascripts/vue_shared/components/markdown/header.vue @@ -15,6 +15,7 @@ import { getSelectedFragment } from '~/lib/utils/common_utils'; import { s__, __ } from '~/locale'; import { CopyAsGFM } from '~/behaviors/markdown/copy_as_gfm'; import ToolbarButton from './toolbar_button.vue'; +import DrawioToolbarButton from './drawio_toolbar_button.vue'; export default { components: { @@ -23,6 +24,7 @@ export default { GlButton, GlTabs, GlTab, + DrawioToolbarButton, }, directives: { GlTooltip: GlTooltipDirective, @@ -62,6 +64,21 @@ export default { required: false, default: () => [], }, + uploadsPath: { + type: String, + required: false, + default: '', + }, + markdownPreviewPath: { + type: String, + required: false, + default: '', + }, + drawioEnabled: { + type: Boolean, + required: false, + default: false, + }, }, data() { return { @@ -369,8 +386,12 @@ export default { icon="paperclip" @click="handleAttachFile" /> + <drawio-toolbar-button + v-if="drawioEnabled" + :uploads-path="uploadsPath" + :markdown-preview-path="markdownPreviewPath" + /> <toolbar-button - v-if="!restrictedToolBarItems.includes('full-screen')" class="js-zen-enter" :prepend="true" :button-title="__('Go full screen')" diff --git a/app/assets/javascripts/vue_shared/components/markdown/markdown_editor.vue b/app/assets/javascripts/vue_shared/components/markdown/markdown_editor.vue index 7e6b0e4a63b..2a82986e90b 100644 --- a/app/assets/javascripts/vue_shared/components/markdown/markdown_editor.vue +++ b/app/assets/javascripts/vue_shared/components/markdown/markdown_editor.vue @@ -76,6 +76,11 @@ export default { required: false, default: false, }, + drawioEnabled: { + type: Boolean, + required: false, + default: false, + }, }, data() { return { @@ -147,6 +152,7 @@ export default { :autocomplete-data-sources="autocompleteDataSources" :uploads-path="uploadsPath" :enable-preview="enablePreview" + :drawio-enabled="drawioEnabled" show-content-editor-switcher class="bordered-box" @enableContentEditor="onEditingModeChange('contentEditor')" diff --git a/app/assets/javascripts/work_items/components/work_item_links/work_item_link_child_metadata.vue b/app/assets/javascripts/work_items/components/work_item_links/work_item_link_child_metadata.vue index 6974804523a..80802cb3858 100644 --- a/app/assets/javascripts/work_items/components/work_item_links/work_item_link_child_metadata.vue +++ b/app/assets/javascripts/work_items/components/work_item_links/work_item_link_child_metadata.vue @@ -70,7 +70,7 @@ export default { <item-milestone v-if="milestone" :milestone="milestone" - class="gl-display-flex gl-align-items-center gl-mr-5 gl-max-w-15 gl-text-secondary! gl-cursor-help! gl-text-decoration-none!" + class="gl-display-flex gl-align-items-center gl-mr-5 gl-max-w-15 gl-line-height-normal gl-text-secondary! gl-cursor-help! gl-text-decoration-none!" /> <gl-avatars-inline v-if="assignees.length" @@ -97,7 +97,7 @@ export default { :background-color="label.color" :description="label.description" :scoped="showScopedLabel(label)" - class="gl-mt-3 gl-sm-mt-0 gl-mr-2 gl-mb-auto gl-label-sm" + class="gl-my-2 gl-mr-2 gl-mb-auto gl-label-sm" tooltip-placement="top" /> </div> diff --git a/app/assets/javascripts/work_items/components/work_item_links/work_item_links.vue b/app/assets/javascripts/work_items/components/work_item_links/work_item_links.vue index acd2792a806..e2956c18678 100644 --- a/app/assets/javascripts/work_items/components/work_item_links/work_item_links.vue +++ b/app/assets/javascripts/work_items/components/work_item_links/work_item_links.vue @@ -63,6 +63,9 @@ export default { id: this.issuableGid, }; }, + context: { + isSingleRequest: true, + }, skip() { return !this.issuableId; }, diff --git a/app/assets/javascripts/work_items/graphql/work_item_links.query.graphql b/app/assets/javascripts/work_items/graphql/work_item_links.query.graphql index 7d7bb9c7fc5..b9d4bb29bbf 100644 --- a/app/assets/javascripts/work_items/graphql/work_item_links.query.graphql +++ b/app/assets/javascripts/work_items/graphql/work_item_links.query.graphql @@ -1,40 +1,7 @@ +#import "./work_item.fragment.graphql" + query workItemLinksQuery($id: WorkItemID!) { workItem(id: $id) { - id - workItemType { - id - name - } - title - userPermissions { - deleteWorkItem - updateWorkItem - } - confidential - widgets { - type - ... on WorkItemWidgetHierarchy { - type - parent { - id - } - children { - nodes { - id - iid - confidential - workItemType { - id - name - iconName - } - title - state - createdAt - closedAt - } - } - } - } + ...WorkItem } } diff --git a/app/controllers/concerns/wiki_actions.rb b/app/controllers/concerns/wiki_actions.rb index 46303cefb60..0e160ce2ea0 100644 --- a/app/controllers/concerns/wiki_actions.rb +++ b/app/controllers/concerns/wiki_actions.rb @@ -11,6 +11,15 @@ module WikiActions RESCUE_GIT_TIMEOUTS_IN = %w[show edit history diff pages].freeze included do + content_security_policy do |p| + next if p.directives.blank? + + default_frame_src = p.directives['frame-src'] || p.directives['default-src'] + frame_src_values = Array.wrap(default_frame_src) | ['https://embed.diagrams.net'].compact + + p.frame_src(*frame_src_values) + end + before_action { respond_to :html } before_action :authorize_read_wiki! diff --git a/doc/user/application_security/container_scanning/index.md b/doc/user/application_security/container_scanning/index.md index 0a586a14cc4..b37aa53e48a 100644 --- a/doc/user/application_security/container_scanning/index.md +++ b/doc/user/application_security/container_scanning/index.md @@ -179,6 +179,8 @@ container_scanning: include: - template: Jobs/Container-Scanning.gitlab-ci.yml + +variables: CS_IMAGE: <aws_account_id>.dkr.ecr.<region>.amazonaws.com/<image>:<tag> CS_REGISTRY_USER: AWS CS_REGISTRY_PASSWORD: "$AWS_ECR_PASSWORD" diff --git a/doc/user/markdown.md b/doc/user/markdown.md index 0b358db97eb..cf749bb5b8b 100644 --- a/doc/user/markdown.md +++ b/doc/user/markdown.md @@ -124,8 +124,13 @@ to see the color chips next to the color code: ### Diagrams and flowcharts -You can generate diagrams and flowcharts from text by using [Mermaid](https://mermaidjs.github.io/) or [PlantUML](https://plantuml.com). -You can also use [Kroki](https://kroki.io) to create a wide variety of diagrams. +You can generate diagrams from text by using: + +- [Mermaid](https://mermaidjs.github.io/) +- [PlantUML](https://plantuml.com) +- [Kroki](https://kroki.io) to create a wide variety of diagrams. + +In wikis you can also add diagrams created by using the [diagrams.net online editor](#create-or-edit-diagrams-by-using-diagramsnet-editor). #### Mermaid @@ -543,6 +548,36 @@ This example links to `<wiki_root>/miscellaneous.md`: [Link to Related Page](/miscellaneous.md) ``` +#### Create or edit diagrams by using diagrams.net editor + +> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/322174) in GitLab 15.10. + +NOTE: +Use of the diagrams.net editor is not available on offline environments. + +On wikis, you can use the [diagrams.net](https://www.diagrams.net/) editor +to create diagrams. You can also edit diagrams previously created with the +editor. + +To create a diagram: + +1. Select **Insert or edit diagram** in the Markdown editor. +1. Use the diagrams.net editor to build the diagram. +1. Select **Save & exit**. + +A Markdown image declaration pointing to the diagram is inserted in the wiki content. + +To edit a diagram: + +1. Place the Markdown editor’s text field cursor in a Markdown image declaration +that contains the diagram. +1. Select **Insert or edit diagram** in the Markdown editor. +1. Use the diagrams.net editor to edit the diagram. +1. Select **Save & exit**. + +A Markdown image declaration pointing to the diagram is inserted in the wiki content, +replacing the previous diagram. + ## GitLab-specific references GitLab Flavored Markdown renders GitLab-specific references. For example, you can reference diff --git a/lib/bulk_imports/clients/http.rb b/lib/bulk_imports/clients/http.rb index 61577ab0c4e..6efee83a0dd 100644 --- a/lib/bulk_imports/clients/http.rb +++ b/lib/bulk_imports/clients/http.rb @@ -85,6 +85,8 @@ module BulkImports end def validate_instance_version! + raise ::BulkImports::Error.invalid_url unless instance_version.valid? + return true unless instance_version.major < BulkImport::MIN_MAJOR_VERSION raise ::BulkImports::Error.unsupported_gitlab_version diff --git a/lib/bulk_imports/error.rb b/lib/bulk_imports/error.rb index 4699d5eab5f..009fa02a72a 100644 --- a/lib/bulk_imports/error.rb +++ b/lib/bulk_imports/error.rb @@ -13,7 +13,7 @@ module BulkImports end def self.invalid_url - self.new("Import aborted as it was not possible to connect to the provided GitLab instance URL.") + self.new("Invalid source URL. Enter only the base URL of the source GitLab instance.") end def self.destination_full_path_validation_failure(full_path) diff --git a/lib/gitlab/seeders/ci/runner/runner_fleet_seeder.rb b/lib/gitlab/seeders/ci/runner/runner_fleet_seeder.rb index 082d267442c..71f055ce7e9 100644 --- a/lib/gitlab/seeders/ci/runner/runner_fleet_seeder.rb +++ b/lib/gitlab/seeders/ci/runner/runner_fleet_seeder.rb @@ -66,14 +66,18 @@ module Gitlab plan_limits = Plan.default.actual_limits if plan_limits.ci_registered_group_runners < @runner_count - logger.error('The plan limits for group runners is set to ' \ + warn 'The plan limits for group runners is set to ' \ "#{plan_limits.ci_registered_group_runners} runners. " \ - 'You should raise the plan limits to avoid errors during runner creation') + 'You should raise the plan limits to avoid errors during runner creation by running ' \ + 'the following command in the Rails console:' + warn "Plan.default.actual_limits.update!(ci_registered_group_runners: #{@runner_count})" return false elsif plan_limits.ci_registered_project_runners < @runner_count - logger.error('The plan limits for project runners is set to ' \ + warn 'The plan limits for project runners is set to ' \ "#{plan_limits.ci_registered_project_runners} runners. " \ - 'You should raise the plan limits to avoid errors during runner creation') + 'You should raise the plan limits to avoid errors during runner creation by running ' \ + 'the following command in the Rails console:' + warn "Plan.default.actual_limits.update!(ci_registered_project_runners: #{@runner_count})" return false end diff --git a/lib/tasks/gitlab/seed/runner_fleet.rake b/lib/tasks/gitlab/seed/runner_fleet.rake index c0b79269c75..784451226b7 100644 --- a/lib/tasks/gitlab/seed/runner_fleet.rake +++ b/lib/tasks/gitlab/seed/runner_fleet.rake @@ -28,10 +28,12 @@ namespace :gitlab do runner_count: args.runner_count&.to_i ).seed - Gitlab::Seeders::Ci::Runner::RunnerFleetPipelineSeeder.new( - projects_to_runners: projects_to_runners, - job_count: args.job_count&.to_i - ).seed + if projects_to_runners + Gitlab::Seeders::Ci::Runner::RunnerFleetPipelineSeeder.new( + projects_to_runners: projects_to_runners, + job_count: args.job_count&.to_i + ).seed + end end puts "Seed finished. Timings: #{timings}" diff --git a/locale/gitlab.pot b/locale/gitlab.pot index e087cd913d9..c215985ce9e 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -8124,6 +8124,9 @@ msgstr "" msgid "Cannot import because issues are not available in this project." msgstr "" +msgid "Cannot load the diagram into the draw.io editor" +msgstr "" + msgid "Cannot make the epic confidential if it contains non-confidential child epics" msgstr "" @@ -14711,6 +14714,9 @@ msgstr "" msgid "Diagram (%{language})" msgstr "" +msgid "Diagram saved successfully." +msgstr "" + msgid "Did not delete the source branch." msgstr "" @@ -22720,6 +22726,9 @@ msgstr "" msgid "Insert link" msgstr "" +msgid "Insert or edit diagram" +msgstr "" + msgid "Insert row after" msgstr "" @@ -42962,6 +42971,9 @@ msgstr "" msgid "The download link will expire in 24 hours." msgstr "" +msgid "The draw.io editor could not be loaded." +msgstr "" + msgid "The environment tiers must be from %{environment_tiers}." msgstr "" @@ -43266,6 +43278,9 @@ msgstr "" msgid "The secret is only available when you create the application or renew the secret." msgstr "" +msgid "The selected image is not a diagram" +msgstr "" + msgid "The snippet can be accessed without any authentication." msgstr "" diff --git a/package.json b/package.json index 3a502402912..3aeff2a4c1b 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,6 @@ "webpack-prod": "NODE_OPTIONS=\"--max-old-space-size=3584\" NODE_ENV=production webpack --config config/webpack.config.js" }, "dependencies": { - "@_ueberdosis/prosemirror-tables": "^1.1.3", "@apollo/client": "^3.5.10", "@babel/core": "^7.18.5", "@babel/preset-env": "^7.18.2", @@ -63,40 +62,41 @@ "@rails/actioncable": "6.1.4-7", "@rails/ujs": "6.1.4-7", "@sourcegraph/code-host-integration": "0.0.84", - "@tiptap/core": "^2.0.0-beta.202", - "@tiptap/extension-blockquote": "^2.0.0-beta.202", - "@tiptap/extension-bold": "^2.0.0-beta.202", - "@tiptap/extension-bubble-menu": "2.0.0-beta.200", - "@tiptap/extension-bullet-list": "^2.0.0-beta.202", - "@tiptap/extension-code": "^2.0.0-beta.202", - "@tiptap/extension-code-block": "^2.0.0-beta.202", - "@tiptap/extension-code-block-lowlight": "2.0.0-beta.202", - "@tiptap/extension-document": "^2.0.0-beta.202", - "@tiptap/extension-dropcursor": "^2.0.0-beta.202", - "@tiptap/extension-gapcursor": "^2.0.0-beta.202", - "@tiptap/extension-hard-break": "^2.0.0-beta.202", - "@tiptap/extension-heading": "^2.0.0-beta.202", - "@tiptap/extension-highlight": "^2.0.0-beta.209", - "@tiptap/extension-history": "^2.0.0-beta.202", - "@tiptap/extension-horizontal-rule": "^2.0.0-beta.202", - "@tiptap/extension-image": "^2.0.0-beta.202", - "@tiptap/extension-italic": "^2.0.0-beta.202", - "@tiptap/extension-link": "^2.0.0-beta.202", - "@tiptap/extension-list-item": "^2.0.0-beta.202", - "@tiptap/extension-ordered-list": "^2.0.0-beta.202", - "@tiptap/extension-paragraph": "^2.0.0-beta.202", - "@tiptap/extension-strike": "^2.0.0-beta.202", - "@tiptap/extension-subscript": "^2.0.0-beta.202", - "@tiptap/extension-superscript": "^2.0.0-beta.202", - "@tiptap/extension-table": "^2.0.0-beta.202", - "@tiptap/extension-table-cell": "^2.0.0-beta.202", - "@tiptap/extension-table-header": "^2.0.0-beta.202", - "@tiptap/extension-table-row": "^2.0.0-beta.202", - "@tiptap/extension-task-item": "^2.0.0-beta.202", - "@tiptap/extension-task-list": "^2.0.0-beta.202", - "@tiptap/extension-text": "^2.0.0-beta.202", - "@tiptap/suggestion": "^2.0.0-beta.202", - "@tiptap/vue-2": "2.0.0-beta.200", + "@tiptap/core": "^2.0.0-beta.217", + "@tiptap/extension-blockquote": "^2.0.0-beta.217", + "@tiptap/extension-bold": "^2.0.0-beta.217", + "@tiptap/extension-bubble-menu": "2.0.0-beta.217", + "@tiptap/extension-bullet-list": "^2.0.0-beta.217", + "@tiptap/extension-code": "^2.0.0-beta.217", + "@tiptap/extension-code-block": "^2.0.0-beta.217", + "@tiptap/extension-code-block-lowlight": "2.0.0-beta.217", + "@tiptap/extension-document": "^2.0.0-beta.217", + "@tiptap/extension-dropcursor": "^2.0.0-beta.217", + "@tiptap/extension-gapcursor": "^2.0.0-beta.217", + "@tiptap/extension-hard-break": "^2.0.0-beta.217", + "@tiptap/extension-heading": "^2.0.0-beta.217", + "@tiptap/extension-highlight": "^2.0.0-beta.217", + "@tiptap/extension-history": "^2.0.0-beta.217", + "@tiptap/extension-horizontal-rule": "^2.0.0-beta.217", + "@tiptap/extension-image": "^2.0.0-beta.217", + "@tiptap/extension-italic": "^2.0.0-beta.217", + "@tiptap/extension-link": "^2.0.0-beta.217", + "@tiptap/extension-list-item": "^2.0.0-beta.217", + "@tiptap/extension-ordered-list": "^2.0.0-beta.217", + "@tiptap/extension-paragraph": "^2.0.0-beta.217", + "@tiptap/extension-strike": "^2.0.0-beta.217", + "@tiptap/extension-subscript": "^2.0.0-beta.217", + "@tiptap/extension-superscript": "^2.0.0-beta.217", + "@tiptap/extension-table": "^2.0.0-beta.217", + "@tiptap/extension-table-cell": "^2.0.0-beta.217", + "@tiptap/extension-table-header": "^2.0.0-beta.217", + "@tiptap/extension-table-row": "^2.0.0-beta.217", + "@tiptap/extension-task-item": "^2.0.0-beta.217", + "@tiptap/extension-task-list": "^2.0.0-beta.217", + "@tiptap/extension-text": "^2.0.0-beta.217", + "@tiptap/pm": "^2.0.0-beta.217", + "@tiptap/suggestion": "^2.0.0-beta.217", + "@tiptap/vue-2": "2.0.0-beta.217", "apollo-upload-client": "15.0.0", "apollo3-cache-persist": "^0.14.1", "autosize": "^5.0.1", @@ -163,9 +163,6 @@ "portal-vue": "^2.1.7", "postcss": "8.4.14", "prosemirror-markdown": "1.9.1", - "prosemirror-model": "^1.18.1", - "prosemirror-state": "^1.4.1", - "prosemirror-view": "^1.28.2", "raphael": "^2.2.7", "raw-loader": "^4.0.2", "rehype-raw": "^6.1.1", @@ -249,8 +246,6 @@ "mock-apollo-client": "1.2.0", "nodemon": "^2.0.19", "prettier": "2.2.1", - "prosemirror-schema-basic": "^1.2.0", - "prosemirror-schema-list": "^1.2.2", "prosemirror-test-builder": "^1.1.0", "purgecss": "^4.0.3", "purgecss-from-html": "^4.0.3", diff --git a/qa/spec/specs/helpers/feature_flag_spec.rb b/qa/spec/specs/helpers/feature_flag_spec.rb index 9b6a52ba67c..6d9bdea7f65 100644 --- a/qa/spec/specs/helpers/feature_flag_spec.rb +++ b/qa/spec/specs/helpers/feature_flag_spec.rb @@ -151,7 +151,7 @@ RSpec.describe QA::Specs::Helpers::FeatureFlag do it_behaves_like 'skips with given feature flag metadata', { name: 'global_ff', scope: :global } end - context 'when run on jh production', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/392832' do + context 'when run on jh production', skip: 'https://gitlab.com/gitlab-org/gitlab/-/issues/392832' do before do allow(GitlabEdition).to receive(:jh?).and_return(true) end diff --git a/spec/features/work_items/work_item_children_spec.rb b/spec/features/work_items/work_item_children_spec.rb index f41fb86d13c..43a6b2771f6 100644 --- a/spec/features/work_items/work_item_children_spec.rb +++ b/spec/features/work_items/work_item_children_spec.rb @@ -132,5 +132,48 @@ RSpec.describe 'Work item children', :js, feature_category: :team_planning do end end end + + context 'in work item metadata' do + let_it_be(:label) { create(:label, title: 'Label 1', project: project) } + let_it_be(:milestone) { create(:milestone, project: project, title: 'v1') } + let_it_be(:task) do + create( + :work_item, + :task, + project: project, + labels: [label], + assignees: [user], + milestone: milestone + ) + end + + before do + visit project_issue_path(project, issue) + + wait_for_requests + end + + it 'displays labels, milestone and assignee for work item children', :aggregate_failures do + page.within('[data-testid="work-item-links"]') do + click_button 'Add' + click_button 'Existing task' + + find('[data-testid="work-item-token-select-input"]').set(task.title) + wait_for_all_requests + click_button task.title + + click_button 'Add task' + + wait_for_all_requests + end + + page.within('[data-testid="links-child"]') do + expect(page).to have_content(task.title) + expect(page).to have_content(label.title) + expect(page).to have_link(user.name) + expect(page).to have_content(milestone.title) + end + end + end end end diff --git a/spec/frontend/content_editor/components/wrappers/table_cell_base_spec.js b/spec/frontend/content_editor/components/wrappers/table_cell_base_spec.js index 1fdddce3962..52af3264f33 100644 --- a/spec/frontend/content_editor/components/wrappers/table_cell_base_spec.js +++ b/spec/frontend/content_editor/components/wrappers/table_cell_base_spec.js @@ -1,12 +1,12 @@ import { GlDropdown, GlDropdownItem } from '@gitlab/ui'; import { NodeViewWrapper } from '@tiptap/vue-2'; -import { selectedRect as getSelectedRect } from '@_ueberdosis/prosemirror-tables'; +import { selectedRect as getSelectedRect } from '@tiptap/pm/tables'; import { nextTick } from 'vue'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import TableCellBaseWrapper from '~/content_editor/components/wrappers/table_cell_base.vue'; import { createTestEditor, mockChainedCommands, emitEditorEvent } from '../../test_utils'; -jest.mock('@_ueberdosis/prosemirror-tables'); +jest.mock('@tiptap/pm/tables'); describe('content/components/wrappers/table_cell_base', () => { let wrapper; diff --git a/spec/frontend/content_editor/render_html_and_json_for_all_examples.js b/spec/frontend/content_editor/render_html_and_json_for_all_examples.js index 5df901e0f15..bf29d4bdf23 100644 --- a/spec/frontend/content_editor/render_html_and_json_for_all_examples.js +++ b/spec/frontend/content_editor/render_html_and_json_for_all_examples.js @@ -1,4 +1,4 @@ -import { DOMSerializer } from 'prosemirror-model'; +import { DOMSerializer } from '@tiptap/pm/model'; import createMarkdownDeserializer from '~/content_editor/services/remark_markdown_deserializer'; import { createTiptapEditor } from 'jest/content_editor/test_utils'; diff --git a/spec/frontend/drawio/drawio_editor_spec.js b/spec/frontend/drawio/drawio_editor_spec.js new file mode 100644 index 00000000000..de93d042396 --- /dev/null +++ b/spec/frontend/drawio/drawio_editor_spec.js @@ -0,0 +1,443 @@ +import { launchDrawioEditor } from '~/drawio/drawio_editor'; +import { + DRAWIO_EDITOR_URL, + DRAWIO_FRAME_ID, + DIAGRAM_BACKGROUND_COLOR, + DRAWIO_IFRAME_TIMEOUT, +} from '~/drawio/constants'; +import { createAlert, VARIANT_SUCCESS } from '~/flash'; + +jest.mock('~/flash'); + +jest.useFakeTimers(); + +describe('drawio/drawio_editor', () => { + let editorFacade; + let drawioIFrameReceivedMessages; + const testSvg = '<svg></svg>'; + const testEncodedSvg = `data:image/svg+xml;base64,${btoa(testSvg)}`; + + const findDrawioIframe = () => document.getElementById(DRAWIO_FRAME_ID); + const waitForDrawioIFrameMessage = ({ messageNumber = 1 } = {}) => + new Promise((resolve) => { + let messageCounter = 0; + const iframe = findDrawioIframe(); + + iframe?.contentWindow.addEventListener('message', (event) => { + drawioIFrameReceivedMessages.push(event); + + messageCounter += 1; + + if (messageCounter === messageNumber) { + resolve(); + } + }); + }); + const expectDrawioIframeMessage = ({ expectation, messageNumber = 1 }) => { + expect(drawioIFrameReceivedMessages).toHaveLength(messageNumber); + expect(JSON.parse(drawioIFrameReceivedMessages[messageNumber - 1].data)).toEqual(expectation); + }; + const postMessageToParentWindow = (data) => { + const event = new Event('message'); + + Object.setPrototypeOf(event, { + source: findDrawioIframe().contentWindow, + data: JSON.stringify(data), + }); + + window.dispatchEvent(event); + }; + + beforeEach(() => { + editorFacade = { + getDiagram: jest.fn(), + uploadDiagram: jest.fn(), + insertDiagram: jest.fn(), + updateDiagram: jest.fn(), + }; + drawioIFrameReceivedMessages = []; + }); + + afterEach(() => { + jest.clearAllMocks(); + findDrawioIframe()?.remove(); + }); + + describe('initializing', () => { + beforeEach(() => { + launchDrawioEditor({ editorFacade }); + }); + + it('creates the drawio editor iframe and attaches it to the body', () => { + expect(findDrawioIframe().getAttribute('src')).toBe(DRAWIO_EDITOR_URL); + }); + }); + + describe(`when parent window does not receive configure event after ${DRAWIO_IFRAME_TIMEOUT} ms`, () => { + beforeEach(() => { + launchDrawioEditor({ editorFacade }); + }); + + it('disposes draw.io iframe', () => { + expect(findDrawioIframe()).not.toBe(null); + jest.runAllTimers(); + expect(findDrawioIframe()).toBe(null); + }); + + it('displays an alert indicating that the draw.io editor could not be loaded', () => { + jest.runAllTimers(); + + expect(createAlert).toHaveBeenCalledWith({ + message: 'The draw.io editor could not be loaded.', + }); + }); + }); + + describe('when parent window receives configure event', () => { + beforeEach(async () => { + launchDrawioEditor({ editorFacade }); + postMessageToParentWindow({ event: 'configure' }); + + await waitForDrawioIFrameMessage(); + }); + + it('sends configure action to the draw.io iframe', async () => { + expectDrawioIframeMessage({ + expectation: { + action: 'configure', + config: { + darkColor: '#202020', + settingsName: 'gitlab', + }, + colorSchemeMeta: false, + }, + }); + }); + + it('does not remove the iframe after the load error timeouts run', async () => { + jest.runAllTimers(); + + expect(findDrawioIframe()).not.toBe(null); + }); + }); + + describe('when parent window receives init event', () => { + describe('when there isn’t a diagram selected', () => { + beforeEach(() => { + editorFacade.getDiagram.mockResolvedValueOnce(null); + + launchDrawioEditor({ editorFacade }); + + postMessageToParentWindow({ event: 'init' }); + }); + + it('sends load action to the draw.io iframe with empty svg and title', async () => { + await waitForDrawioIFrameMessage(); + + expectDrawioIframeMessage({ + expectation: { + action: 'load', + xml: null, + border: 8, + background: DIAGRAM_BACKGROUND_COLOR, + dark: false, + title: null, + }, + }); + }); + }); + + describe('when there is a diagram selected', () => { + const diagramSvg = '<svg></svg>'; + const filename = 'diagram.drawio.svg'; + + beforeEach(() => { + editorFacade.getDiagram.mockResolvedValueOnce({ + diagramSvg, + filename, + contentType: 'image/svg+xml', + }); + + launchDrawioEditor({ editorFacade }); + postMessageToParentWindow({ event: 'init' }); + }); + + it('sends load action to the draw.io iframe with the selected diagram svg and filename', async () => { + await waitForDrawioIFrameMessage(); + + // Step 5: The draw.io editor will send the downloaded diagram to the iframe + expectDrawioIframeMessage({ + expectation: { + action: 'load', + xml: diagramSvg, + border: 8, + background: DIAGRAM_BACKGROUND_COLOR, + dark: false, + title: filename, + }, + }); + }); + }); + + describe('when there is an image selected that is not a diagram', () => { + beforeEach(() => { + editorFacade.getDiagram.mockResolvedValueOnce({ + contentType: 'image/png', + filename: 'image.png', + }); + + launchDrawioEditor({ editorFacade }); + + postMessageToParentWindow({ event: 'init' }); + }); + + it('displays an error alert indicating that the image is not a diagram', async () => { + expect(createAlert).toHaveBeenCalledWith({ + message: 'The selected image is not a diagram', + error: expect.any(Error), + }); + }); + + it('disposes the draw.io diagram iframe', () => { + expect(findDrawioIframe()).toBe(null); + }); + }); + + describe('when loading a diagram fails', () => { + beforeEach(() => { + editorFacade.getDiagram.mockRejectedValueOnce(new Error()); + + launchDrawioEditor({ editorFacade }); + + postMessageToParentWindow({ event: 'init' }); + }); + + it('displays an error alert indicating the failure', async () => { + expect(createAlert).toHaveBeenCalledWith({ + message: 'Cannot load the diagram into the draw.io editor', + error: expect.any(Error), + }); + }); + + it('disposes the draw.io diagram iframe', () => { + expect(findDrawioIframe()).toBe(null); + }); + }); + }); + + describe('when parent window receives prompt event', () => { + describe('when the filename is empty', () => { + beforeEach(() => { + launchDrawioEditor({ editorFacade }); + + postMessageToParentWindow({ event: 'prompt', value: '' }); + }); + + it('sends prompt action to the draw.io iframe requesting a filename', async () => { + await waitForDrawioIFrameMessage({ messageNumber: 1 }); + + expectDrawioIframeMessage({ + expectation: { + action: 'prompt', + titleKey: 'filename', + okKey: 'save', + defaultValue: 'diagram.drawio.svg', + }, + messageNumber: 1, + }); + }); + + it('sends dialog action to the draw.io iframe indicating that the filename cannot be empty', async () => { + await waitForDrawioIFrameMessage({ messageNumber: 2 }); + + expectDrawioIframeMessage({ + expectation: { + action: 'dialog', + titleKey: 'error', + messageKey: 'filenameShort', + buttonKey: 'ok', + }, + messageNumber: 2, + }); + }); + }); + + describe('when the event data is not empty', () => { + beforeEach(async () => { + launchDrawioEditor({ editorFacade }); + postMessageToParentWindow({ event: 'prompt', value: 'diagram.drawio.svg' }); + + await waitForDrawioIFrameMessage(); + }); + + it('starts the saving file process', () => { + expectDrawioIframeMessage({ + expectation: { + action: 'spinner', + show: true, + messageKey: 'saving', + }, + }); + }); + }); + }); + + describe('when parent receives export event', () => { + beforeEach(() => { + editorFacade.uploadDiagram.mockResolvedValueOnce({}); + }); + + it('reloads diagram in the draw.io editor', async () => { + launchDrawioEditor({ editorFacade }); + postMessageToParentWindow({ event: 'export', data: testEncodedSvg }); + + await waitForDrawioIFrameMessage(); + + expectDrawioIframeMessage({ + expectation: expect.objectContaining({ + action: 'load', + xml: expect.stringContaining(testSvg), + }), + }); + }); + + it('marks the diagram as modified in the draw.io editor', async () => { + launchDrawioEditor({ editorFacade }); + postMessageToParentWindow({ event: 'export', data: testEncodedSvg }); + + await waitForDrawioIFrameMessage({ messageNumber: 2 }); + + expectDrawioIframeMessage({ + expectation: expect.objectContaining({ + action: 'status', + modified: true, + }), + messageNumber: 2, + }); + }); + + describe('when the diagram filename is set', () => { + const TEST_FILENAME = 'diagram.drawio.svg'; + + beforeEach(() => { + launchDrawioEditor({ editorFacade, filename: TEST_FILENAME }); + }); + + it('displays loading spinner in the draw.io editor', async () => { + postMessageToParentWindow({ event: 'export', data: testEncodedSvg }); + + await waitForDrawioIFrameMessage({ messageNumber: 3 }); + + expectDrawioIframeMessage({ + expectation: { + action: 'spinner', + show: true, + messageKey: 'saving', + }, + messageNumber: 3, + }); + }); + + it('uploads exported diagram', async () => { + postMessageToParentWindow({ event: 'export', data: testEncodedSvg }); + + await waitForDrawioIFrameMessage({ messageNumber: 3 }); + + expect(editorFacade.uploadDiagram).toHaveBeenCalledWith({ + filename: TEST_FILENAME, + diagramSvg: expect.stringContaining(testSvg), + }); + }); + + describe('when uploading the exported diagram succeeds', () => { + it('displays an alert indicating that the diagram was uploaded successfully', async () => { + postMessageToParentWindow({ event: 'export', data: testEncodedSvg }); + + await waitForDrawioIFrameMessage({ messageNumber: 3 }); + + expect(createAlert).toHaveBeenCalledWith({ + message: expect.any(String), + variant: VARIANT_SUCCESS, + fadeTransition: true, + }); + }); + + it('disposes iframe', () => { + jest.runAllTimers(); + + expect(findDrawioIframe()).toBe(null); + }); + }); + + describe('when uploading the exported diagram fails', () => { + const uploadError = new Error(); + + beforeEach(() => { + editorFacade.uploadDiagram.mockReset(); + editorFacade.uploadDiagram.mockRejectedValue(uploadError); + + postMessageToParentWindow({ event: 'export', data: testEncodedSvg }); + }); + + it('hides loading indicator in the draw.io editor', async () => { + await waitForDrawioIFrameMessage({ messageNumber: 4 }); + + expectDrawioIframeMessage({ + expectation: { + action: 'spinner', + show: false, + }, + messageNumber: 4, + }); + }); + + it('displays an error dialog in the draw.io editor', async () => { + await waitForDrawioIFrameMessage({ messageNumber: 5 }); + + expectDrawioIframeMessage({ + expectation: { + action: 'dialog', + titleKey: 'error', + modified: true, + buttonKey: 'close', + messageKey: 'errorSavingFile', + }, + messageNumber: 5, + }); + }); + }); + }); + + describe('when diagram filename is not set', () => { + it('sends prompt action to the draw.io iframe', async () => { + launchDrawioEditor({ editorFacade }); + postMessageToParentWindow({ event: 'export', data: testEncodedSvg }); + + await waitForDrawioIFrameMessage({ messageNumber: 3 }); + + expect(drawioIFrameReceivedMessages[2].data).toEqual( + JSON.stringify({ + action: 'prompt', + titleKey: 'filename', + okKey: 'save', + defaultValue: 'diagram.drawio.svg', + }), + ); + }); + }); + }); + + describe('when parent window receives exit event', () => { + beforeEach(() => { + launchDrawioEditor({ editorFacade }); + }); + + it('disposes the the draw.io iframe', () => { + expect(findDrawioIframe()).not.toBe(null); + + postMessageToParentWindow({ event: 'exit' }); + + expect(findDrawioIframe()).toBe(null); + }); + }); +}); diff --git a/spec/frontend/drawio/markdown_field_editor_facade_spec.js b/spec/frontend/drawio/markdown_field_editor_facade_spec.js new file mode 100644 index 00000000000..992dcf0017c --- /dev/null +++ b/spec/frontend/drawio/markdown_field_editor_facade_spec.js @@ -0,0 +1,147 @@ +import AxiosMockAdapter from 'axios-mock-adapter'; +import { create } from '~/drawio/markdown_field_editor_facade'; +import * as textMarkdown from '~/lib/utils/text_markdown'; +import { HTTP_STATUS_OK } from '~/lib/utils/http_status'; +import axios from '~/lib/utils/axios_utils'; + +jest.mock('~/lib/utils/text_markdown'); + +describe('drawio/textareaMarkdownEditor', () => { + let textArea; + let textareaMarkdownEditor; + let axiosMock; + + const markdownPreviewPath = '/markdown/preview'; + const imageURL = '/assets/image.png'; + const diagramMarkdown = '![](image.png)'; + const diagramSvg = '<svg></svg>'; + const contentType = 'image/svg+xml'; + const filename = 'image.png'; + const newDiagramMarkdown = '![](newdiagram.svg)'; + const uploadsPath = '/uploads'; + + beforeEach(() => { + textArea = document.createElement('textarea'); + textareaMarkdownEditor = create({ textArea, markdownPreviewPath, uploadsPath }); + + document.body.appendChild(textArea); + }); + beforeEach(() => { + axiosMock = new AxiosMockAdapter(axios); + }); + + afterEach(() => { + axiosMock.restore(); + textArea.remove(); + }); + + describe('getDiagram', () => { + describe('when there is a selected diagram', () => { + beforeEach(() => { + textMarkdown.resolveSelectedImage.mockReturnValueOnce({ + imageURL, + imageMarkdown: diagramMarkdown, + filename, + }); + axiosMock + .onGet(imageURL) + .reply(HTTP_STATUS_OK, diagramSvg, { 'content-type': contentType }); + }); + + it('returns diagram information', async () => { + const diagram = await textareaMarkdownEditor.getDiagram(); + + expect(textMarkdown.resolveSelectedImage).toHaveBeenCalledWith( + textArea, + markdownPreviewPath, + ); + + expect(diagram).toEqual({ + diagramMarkdown, + filename, + diagramSvg, + contentType, + }); + }); + }); + + describe('when there is not a selected diagram', () => { + beforeEach(() => { + textMarkdown.resolveSelectedImage.mockReturnValueOnce(null); + }); + + it('returns null', async () => { + const diagram = await textareaMarkdownEditor.getDiagram(); + + expect(textMarkdown.resolveSelectedImage).toHaveBeenCalledWith( + textArea, + markdownPreviewPath, + ); + + expect(diagram).toBe(null); + }); + }); + }); + + describe('updateDiagram', () => { + beforeEach(() => { + jest.spyOn(textArea, 'focus'); + jest.spyOn(textArea, 'dispatchEvent'); + + textArea.value = `diagram ${diagramMarkdown}`; + + textareaMarkdownEditor.updateDiagram({ + diagramMarkdown, + uploadResults: { link: { markdown: newDiagramMarkdown } }, + }); + }); + + it('focuses the textarea', () => { + expect(textArea.focus).toHaveBeenCalled(); + }); + + it('replaces previous diagram markdown with new diagram markdown', () => { + expect(textArea.value).toBe(`diagram ${newDiagramMarkdown}`); + }); + + it('dispatches input event in the textarea', () => { + expect(textArea.dispatchEvent).toHaveBeenCalledWith(new Event('input')); + }); + }); + + describe('insertDiagram', () => { + it('inserts markdown text and replaces any selected markdown in the textarea', () => { + textArea.value = `diagram ${diagramMarkdown}`; + textArea.setSelectionRange(0, 8); + + textareaMarkdownEditor.insertDiagram({ + uploadResults: { link: { markdown: newDiagramMarkdown } }, + }); + + expect(textMarkdown.insertMarkdownText).toHaveBeenCalledWith({ + textArea, + text: textArea.value, + tag: newDiagramMarkdown, + selected: textArea.value.substring(0, 8), + }); + }); + }); + + describe('uploadDiagram', () => { + it('sends a post request to the uploadsPath containing the diagram svg', async () => { + const link = { markdown: '![](diagram.drawio.svg)' }; + const blob = new Blob([diagramSvg], { type: 'image/svg+xml' }); + const formData = new FormData(); + + formData.append('file', blob, filename); + + axiosMock.onPost(uploadsPath, formData).reply(HTTP_STATUS_OK, { + link, + }); + + const response = await textareaMarkdownEditor.uploadDiagram({ diagramSvg, filename }); + + expect(response).toEqual({ link }); + }); + }); +}); diff --git a/spec/frontend/lib/utils/text_markdown_spec.js b/spec/frontend/lib/utils/text_markdown_spec.js index 7aab1013fc0..2180ea7e6c2 100644 --- a/spec/frontend/lib/utils/text_markdown_spec.js +++ b/spec/frontend/lib/utils/text_markdown_spec.js @@ -1,12 +1,16 @@ import $ from 'jquery'; +import AxiosMockAdapter from 'axios-mock-adapter'; import { insertMarkdownText, keypressNoteText, compositionStartNoteText, compositionEndNoteText, updateTextForToolbarBtn, + resolveSelectedImage, } from '~/lib/utils/text_markdown'; +import { HTTP_STATUS_OK } from '~/lib/utils/http_status'; import '~/lib/utils/jquery_at_who'; +import axios from '~/lib/utils/axios_utils'; import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures'; describe('init markdown', () => { @@ -14,6 +18,7 @@ describe('init markdown', () => { let textArea; let indentButton; let outdentButton; + let axiosMock; beforeAll(() => { setHTMLFixture( @@ -34,6 +39,14 @@ describe('init markdown', () => { document.execCommand = jest.fn(() => false); }); + beforeEach(() => { + axiosMock = new AxiosMockAdapter(axios); + }); + + afterEach(() => { + axiosMock.restore(); + }); + afterAll(() => { resetHTMLFixture(); }); @@ -707,6 +720,55 @@ describe('init markdown', () => { }); }); + describe('resolveSelectedImage', () => { + const markdownPreviewPath = '/markdown/preview'; + const imageMarkdown = '![image](/uploads/image.png)'; + const imageAbsoluteUrl = '/abs/uploads/image.png'; + + describe('when textarea cursor is positioned on an image', () => { + beforeEach(() => { + axiosMock.onPost(markdownPreviewPath, { text: imageMarkdown }).reply(HTTP_STATUS_OK, { + body: ` + <p><a href="${imageAbsoluteUrl}"><img src="${imageAbsoluteUrl}"></a></p> + `, + }); + }); + + it('returns the image absolute URL, markdown, and filename', async () => { + textArea.value = `image ${imageMarkdown}`; + textArea.setSelectionRange(8, 8); + expect(await resolveSelectedImage(textArea, markdownPreviewPath)).toEqual({ + imageURL: imageAbsoluteUrl, + imageMarkdown, + filename: 'image.png', + }); + }); + }); + + describe('when textarea cursor is not positioned on an image', () => { + it.each` + markdown | selectionRange + ${`image ${imageMarkdown}`} | ${[4, 4]} + ${`!2 (issue)`} | ${[2, 2]} + `('returns null', async ({ markdown, selectionRange }) => { + textArea.value = markdown; + textArea.setSelectionRange(...selectionRange); + expect(await resolveSelectedImage(textArea, markdownPreviewPath)).toBe(null); + }); + }); + + describe('when textarea cursor is positioned between images', () => { + it('returns null', async () => { + const position = imageMarkdown.length + 1; + + textArea.value = `${imageMarkdown}\n\n${imageMarkdown}`; + textArea.setSelectionRange(position, position); + + expect(await resolveSelectedImage(textArea, markdownPreviewPath)).toBe(null); + }); + }); + }); + describe('Source Editor', () => { let editor; diff --git a/spec/frontend/snippets/components/__snapshots__/snippet_description_edit_spec.js.snap b/spec/frontend/snippets/components/__snapshots__/snippet_description_edit_spec.js.snap index fec300ddd7e..7eb0468c5be 100644 --- a/spec/frontend/snippets/components/__snapshots__/snippet_description_edit_spec.js.snap +++ b/spec/frontend/snippets/components/__snapshots__/snippet_description_edit_spec.js.snap @@ -28,10 +28,13 @@ exports[`Snippet Description Edit component rendering matches the snapshot 1`] = data-uploads-path="" > <markdown-header-stub + data-testid="markdownHeader" enablepreview="true" linecontent="" + markdownpreviewpath="foo/" restrictedtoolbaritems="" suggestionstartindex="0" + uploadspath="" /> <div diff --git a/spec/frontend/vue_shared/components/markdown/drawio_toolbar_button_spec.js b/spec/frontend/vue_shared/components/markdown/drawio_toolbar_button_spec.js new file mode 100644 index 00000000000..4daf26b0509 --- /dev/null +++ b/spec/frontend/vue_shared/components/markdown/drawio_toolbar_button_spec.js @@ -0,0 +1,67 @@ +import { GlButton } from '@gitlab/ui'; +import { shallowMount } from '@vue/test-utils'; +import DrawioToolbarButton from '~/vue_shared/components/markdown/drawio_toolbar_button.vue'; +import { launchDrawioEditor } from '~/drawio/drawio_editor'; +import { create } from '~/drawio/markdown_field_editor_facade'; + +jest.mock('~/drawio/drawio_editor'); +jest.mock('~/drawio/markdown_field_editor_facade'); + +describe('vue_shared/components/markdown/drawio_toolbar_button', () => { + let wrapper; + let textArea; + const uploadsPath = '/uploads'; + const markdownPreviewPath = '/markdown/preview'; + + const buildWrapper = (props = { uploadsPath, markdownPreviewPath }) => { + wrapper = shallowMount(DrawioToolbarButton, { + propsData: { + ...props, + }, + }); + }; + + beforeEach(() => { + textArea = document.createElement('textarea'); + textArea.classList.add('js-gfm-input'); + + document.body.appendChild(textArea); + }); + + afterEach(() => { + textArea.remove(); + wrapper.destroy(); + }); + + describe('default', () => { + it('renders button that launches draw.io editor', () => { + buildWrapper(); + + expect(wrapper.findComponent(GlButton).props()).toMatchObject({ + icon: 'diagram', + category: 'tertiary', + }); + }); + }); + + describe('when clicking button', () => { + it('launches draw.io editor', async () => { + const editorFacadeStub = {}; + + create.mockReturnValueOnce(editorFacadeStub); + + buildWrapper(); + + await wrapper.findComponent(GlButton).vm.$emit('click'); + + expect(create).toHaveBeenCalledWith({ + markdownPreviewPath, + textArea, + uploadsPath, + }); + expect(launchDrawioEditor).toHaveBeenCalledWith({ + editorFacade: editorFacadeStub, + }); + }); + }); +}); diff --git a/spec/frontend/vue_shared/components/markdown/header_spec.js b/spec/frontend/vue_shared/components/markdown/header_spec.js index ed417097e1e..988d9780505 100644 --- a/spec/frontend/vue_shared/components/markdown/header_spec.js +++ b/spec/frontend/vue_shared/components/markdown/header_spec.js @@ -3,6 +3,7 @@ import { nextTick } from 'vue'; import { GlTabs } from '@gitlab/ui'; import HeaderComponent from '~/vue_shared/components/markdown/header.vue'; import ToolbarButton from '~/vue_shared/components/markdown/toolbar_button.vue'; +import DrawioToolbarButton from '~/vue_shared/components/markdown/drawio_toolbar_button.vue'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; describe('Markdown field header component', () => { @@ -26,6 +27,7 @@ describe('Markdown field header component', () => { findToolbarButtons() .filter((button) => button.props(prop) === value) .at(0); + const findDrawioToolbarButton = () => wrapper.findComponent(DrawioToolbarButton); beforeEach(() => { window.gl = { @@ -197,4 +199,24 @@ describe('Markdown field header component', () => { expect(findToolbarButtons().length).toBe(defaultCount); }); }); + + describe('when drawIOEnabled is true', () => { + const uploadsPath = '/uploads'; + const markdownPreviewPath = '/preview'; + + beforeEach(() => { + createWrapper({ + drawioEnabled: true, + uploadsPath, + markdownPreviewPath, + }); + }); + + it('renders drawio toolbar button', () => { + expect(findDrawioToolbarButton().props()).toEqual({ + uploadsPath, + markdownPreviewPath, + }); + }); + }); }); diff --git a/spec/frontend/work_items/mock_data.js b/spec/frontend/work_items/mock_data.js index 8acf9fd94fb..a864ae75b7f 100644 --- a/spec/frontend/work_items/mock_data.js +++ b/spec/frontend/work_items/mock_data.js @@ -82,6 +82,7 @@ export const workItemQueryResponse = { userPermissions: { deleteWorkItem: false, updateWorkItem: false, + __typename: 'WorkItemPermissions', }, widgets: [ { @@ -182,6 +183,7 @@ export const updateWorkItemMutationResponse = { userPermissions: { deleteWorkItem: false, updateWorkItem: false, + __typename: 'WorkItemPermissions', }, widgets: [ { @@ -330,6 +332,7 @@ export const workItemResponseFactory = ({ userPermissions: { deleteWorkItem: canDelete, updateWorkItem: canUpdate, + __typename: 'WorkItemPermissions', }, widgets: [ { @@ -539,6 +542,7 @@ export const createWorkItemMutationResponse = { userPermissions: { deleteWorkItem: false, updateWorkItem: false, + __typename: 'WorkItemPermissions', }, widgets: [], }, @@ -587,6 +591,7 @@ export const createWorkItemFromTaskMutationResponse = { userPermissions: { deleteWorkItem: false, updateWorkItem: false, + __typename: 'WorkItemPermissions', }, widgets: [ { @@ -627,6 +632,7 @@ export const createWorkItemFromTaskMutationResponse = { userPermissions: { deleteWorkItem: false, updateWorkItem: false, + __typename: 'WorkItemPermissions', }, widgets: [], }, @@ -828,15 +834,20 @@ export const workItemHierarchyEmptyResponse = { data: { workItem: { id: 'gid://gitlab/WorkItem/1', + iid: 1, + state: 'OPEN', workItemType: { - id: 'gid://gitlab/WorkItems::Type/6', + id: 'gid://gitlab/WorkItems::Type/1', name: 'Issue', iconName: 'issue-type-issue', __typename: 'WorkItemType', }, title: 'New title', + description: '', createdAt: '2022-08-03T12:41:54Z', + updatedAt: null, closedAt: null, + author: mockAssignees[0], project: { __typename: 'Project', id: '1', @@ -846,14 +857,11 @@ export const workItemHierarchyEmptyResponse = { userPermissions: { deleteWorkItem: false, updateWorkItem: false, + __typename: 'WorkItemPermissions', }, confidential: false, widgets: [ { - type: 'DESCRIPTION', - __typename: 'WorkItemWidgetDescription', - }, - { type: 'HIERARCHY', parent: null, hasChildren: false, @@ -873,6 +881,8 @@ export const workItemHierarchyNoUpdatePermissionResponse = { data: { workItem: { id: 'gid://gitlab/WorkItem/1', + iid: 1, + state: 'OPEN', workItemType: { id: 'gid://gitlab/WorkItems::Type/6', name: 'Issue', @@ -880,9 +890,15 @@ export const workItemHierarchyNoUpdatePermissionResponse = { __typename: 'WorkItemType', }, title: 'New title', + description: '', + createdAt: '2022-08-03T12:41:54Z', + updatedAt: null, + closedAt: null, + author: mockAssignees[0], userPermissions: { deleteWorkItem: false, updateWorkItem: false, + __typename: 'WorkItemPermissions', }, project: { __typename: 'Project', @@ -893,10 +909,6 @@ export const workItemHierarchyNoUpdatePermissionResponse = { confidential: false, widgets: [ { - type: 'DESCRIPTION', - __typename: 'WorkItemWidgetDescription', - }, - { type: 'HIERARCHY', parent: null, hasChildren: true, @@ -949,6 +961,7 @@ export const workItemTask = { confidential: false, createdAt: '2022-08-03T12:41:54Z', closedAt: null, + widgets: [], __typename: 'WorkItem', }; @@ -966,6 +979,7 @@ export const confidentialWorkItemTask = { confidential: true, createdAt: '2022-08-03T12:41:54Z', closedAt: null, + widgets: [], __typename: 'WorkItem', }; @@ -983,6 +997,7 @@ export const closedWorkItemTask = { confidential: false, createdAt: '2022-08-03T12:41:54Z', closedAt: '2022-08-12T13:07:52Z', + widgets: [], __typename: 'WorkItem', }; @@ -1004,6 +1019,7 @@ export const childrenWorkItems = [ confidential: false, createdAt: '2022-08-03T12:41:54Z', closedAt: null, + widgets: [], __typename: 'WorkItem', }, ]; @@ -1014,15 +1030,19 @@ export const workItemHierarchyResponse = { id: 'gid://gitlab/WorkItem/1', iid: '1', workItemType: { - id: 'gid://gitlab/WorkItems::Type/6', - name: 'Objective', - iconName: 'issue-type-objective', + id: 'gid://gitlab/WorkItems::Type/1', + name: 'Issue', + iconName: 'issue-type-issue', __typename: 'WorkItemType', }, title: 'New title', userPermissions: { deleteWorkItem: true, updateWorkItem: true, + __typename: 'WorkItemPermissions', + }, + author: { + ...mockAssignees[0], }, confidential: false, project: { @@ -1031,12 +1051,13 @@ export const workItemHierarchyResponse = { fullPath: 'test-project-path', archived: false, }, + description: 'Issue description', + state: 'OPEN', + createdAt: '2022-08-03T12:41:54Z', + updatedAt: null, + closedAt: null, widgets: [ { - type: 'DESCRIPTION', - __typename: 'WorkItemWidgetDescription', - }, - { type: 'HIERARCHY', parent: null, hasChildren: true, @@ -1107,6 +1128,7 @@ export const workItemObjectiveWithChild = { userPermissions: { deleteWorkItem: true, updateWorkItem: true, + __typename: 'WorkItemPermissions', }, author: { ...mockAssignees[0], @@ -1173,6 +1195,7 @@ export const workItemHierarchyTreeResponse = { userPermissions: { deleteWorkItem: true, updateWorkItem: true, + __typename: 'WorkItemPermissions', }, confidential: false, project: { @@ -1249,6 +1272,7 @@ export const changeWorkItemParentMutationResponse = { userPermissions: { deleteWorkItem: true, updateWorkItem: true, + __typename: 'WorkItemPermissions', }, description: null, id: 'gid://gitlab/WorkItem/2', @@ -1621,6 +1645,7 @@ export const projectWorkItemResponse = { workItems: { nodes: [workItemQueryResponse.data.workItem], }, + __typename: 'Project', }, }, }; diff --git a/spec/lib/bulk_imports/clients/http_spec.rb b/spec/lib/bulk_imports/clients/http_spec.rb index 09753b3c23d..40261947750 100644 --- a/spec/lib/bulk_imports/clients/http_spec.rb +++ b/spec/lib/bulk_imports/clients/http_spec.rb @@ -284,7 +284,7 @@ RSpec.describe BulkImports::Clients::HTTP, feature_category: :importers do stub_request(:get, 'http://gitlab.example/api/v4/metadata?private_token=token') .to_return(status: 404, body: "", headers: { 'Content-Type' => 'application/json' }) - expect { subject.instance_version }.to raise_exception(BulkImports::Error, 'Import aborted as it was not possible to connect to the provided GitLab instance URL.') + expect { subject.instance_version }.to raise_exception(BulkImports::Error, 'Invalid source URL. Enter only the base URL of the source GitLab instance.') end end diff --git a/spec/lib/gitlab/seeders/ci/runner/runner_fleet_seeder_spec.rb b/spec/lib/gitlab/seeders/ci/runner/runner_fleet_seeder_spec.rb index fe52b586d49..fc16e889636 100644 --- a/spec/lib/gitlab/seeders/ci/runner/runner_fleet_seeder_spec.rb +++ b/spec/lib/gitlab/seeders/ci/runner/runner_fleet_seeder_spec.rb @@ -67,5 +67,29 @@ RSpec.describe ::Gitlab::Seeders::Ci::Runner::RunnerFleetSeeder, feature_categor expect(::Ci::Build.where(runner_id: project[:runner_ids])).to be_empty end end + + context 'when number of group runners exceeds plan limit' do + before do + create(:plan_limits, :default_plan, ci_registered_group_runners: 1) + end + + it { is_expected.to be_nil } + + it 'creates expected hierarchy', :aggregate_failures do + expect { seed }.not_to change { Ci::Runner.count } + end + end + + context 'when number of project runners exceeds plan limit' do + before do + create(:plan_limits, :default_plan, ci_registered_project_runners: 1) + end + + it { is_expected.to be_nil } + + it 'creates expected hierarchy', :aggregate_failures do + expect { seed }.not_to change { Ci::Runner.count } + end + end end end diff --git a/spec/requests/projects/wikis_controller_spec.rb b/spec/requests/projects/wikis_controller_spec.rb new file mode 100644 index 00000000000..4768e7134e8 --- /dev/null +++ b/spec/requests/projects/wikis_controller_spec.rb @@ -0,0 +1,73 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Projects::WikisController, feature_category: :wiki do + using RSpec::Parameterized::TableSyntax + + let_it_be(:user) { create(:user) } + let_it_be(:project) { create(:project, :wiki_repo, namespace: user.namespace) } + let_it_be(:project_wiki) { create(:project_wiki, project: project, user: user) } + let_it_be(:wiki_page) do + create(:wiki_page, + wiki: project_wiki, + title: 'home', content: "Look at this [image](#{path})\n\n ![alt text](#{path})") + end + + let_it_be(:csp_nonce) { 'just=some=noncense' } + + before do + sign_in(user) + + allow_next_instance_of(described_class) do |instance| + allow(instance).to receive(:content_security_policy_nonce).and_return(csp_nonce) + end + end + + shared_examples 'embed.diagrams.net frame-src directive' do + it 'adds drawio frame-src directive to the Content Security Policy header' do + frame_src = response.headers['Content-Security-Policy'].split(';') + .map(&:strip) + .find { |entry| entry.starts_with?('frame-src') } + + expect(frame_src).to include('https://embed.diagrams.net') + end + end + + describe 'CSP policy' do + describe '#new' do + before do + get wiki_path(project_wiki, action: :new) + end + + it_behaves_like 'embed.diagrams.net frame-src directive' + end + + describe '#edit' do + before do + get wiki_page_path(project_wiki, wiki_page, action: 'edit') + end + + it_behaves_like 'embed.diagrams.net frame-src directive' + end + + describe '#create' do + before do + # Creating a page with an invalid title to render edit page + post wiki_path(project_wiki, action: 'create'), params: { wiki: { title: 'home' } } + end + + it_behaves_like 'embed.diagrams.net frame-src directive' + end + + describe '#update' do + before do + # Setting an invalid page title to render edit page + put wiki_page_path(project_wiki, wiki_page), params: { wiki: { title: '' } } + print(response.body) + end + + it_behaves_like 'embed.diagrams.net frame-src directive' + end + end +end diff --git a/spec/support/shared_examples/features/trial_email_validation_shared_example.rb b/spec/support/shared_examples/features/trial_email_validation_shared_example.rb index 5bab1e76e12..81c9ac1164b 100644 --- a/spec/support/shared_examples/features/trial_email_validation_shared_example.rb +++ b/spec/support/shared_examples/features/trial_email_validation_shared_example.rb @@ -1,11 +1,11 @@ # frozen_string_literal: true RSpec.shared_examples 'user email validation' do - let(:email_hint_message) { 'We recommend a work email address.' } - let(:email_error_message) { 'Please provide a valid email address.' } + let(:email_hint_message) { _('We recommend a work email address.') } + let(:email_error_message) { _('Please provide a valid email address.') } let(:email_warning_message) do - 'This email address does not look right, are you sure you typed it correctly?' + _('This email address does not look right, are you sure you typed it correctly?') end it 'shows an error message until a correct email is entered' do diff --git a/yarn.lock b/yarn.lock index 4da96720728..e20109e422a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,17 +2,6 @@ # yarn lockfile v1 -"@_ueberdosis/prosemirror-tables@1.1.3", "@_ueberdosis/prosemirror-tables@^1.1.3": - version "1.1.3" - resolved "https://registry.yarnpkg.com/@_ueberdosis/prosemirror-tables/-/prosemirror-tables-1.1.3.tgz#56fdbc8b1d6ec43e7b7beb21e213c131eec451cd" - integrity sha512-su3pbFi1DT89g6Cuh72TE0MWWKHmWgHcQJ3ODRkm6XfIppWaGpU49t02ur3sgJc7hUhfQXjB93aSkDgOmIii2w== - dependencies: - prosemirror-keymap "^1.1.2" - prosemirror-model "^1.8.1" - prosemirror-state "^1.3.1" - prosemirror-transform "^1.2.1" - prosemirror-view "^1.13.3" - "@ampproject/remapping@^2.1.0": version "2.1.2" resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.1.2.tgz#4edca94973ded9630d20101cd8559cedb8d8bd34" @@ -1838,6 +1827,11 @@ resolved "https://registry.yarnpkg.com/@leichtgewicht/ip-codec/-/ip-codec-2.0.3.tgz#0300943770e04231041a51bd39f0439b5c7ab4f0" integrity sha512-nkalE/f1RvRGChwBnEIoBfSEYOXnCRdleKuv6+lePbMDrMZXeDQnqak5XDOeBgrPPyPfAdcCu/B5z+v3VhplGg== +"@linaria/core@3.0.0-beta.13": + version "3.0.0-beta.13" + resolved "https://registry.yarnpkg.com/@linaria/core/-/core-3.0.0-beta.13.tgz#049c5be5faa67e341e413a0f6b641d5d78d91056" + integrity sha512-3zEi5plBCOsEzUneRVuQb+2SAx3qaC1dj0FfFAI6zIJQoDWu0dlSwKijMRack7oO9tUWrchfj3OkKQAd1LBdVg== + "@miragejs/pretender-node-polyfill@^0.1.0": version "0.1.2" resolved "https://registry.yarnpkg.com/@miragejs/pretender-node-polyfill/-/pretender-node-polyfill-0.1.2.tgz#d26b6b7483fb70cd62189d05c95d2f67153e43f2" @@ -1905,6 +1899,41 @@ resolved "https://registry.yarnpkg.com/@rails/ujs/-/ujs-6.1.4-7.tgz#ef0b83ef40f64bc6704e13ae6624236a4a91fa6f" integrity sha512-842WcLh0BErNgGE8rdqNh31VnqGQcklPQ7RXzQfA0ilQNZcU7AO+t576g1m//18Lk8m7cXZ8fIKA1YB41LKWAQ== +"@remirror/core-constants@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@remirror/core-constants/-/core-constants-2.0.0.tgz#a52f89059d93955e00810023cc76b4f7db9650bf" + integrity sha512-vpePPMecHJllBqCWXl6+FIcZqS+tRUM2kSCCKFeEo1H3XUEv3ocijBIPhnlSAa7g6maX+12ATTgxrOsLpWVr2g== + dependencies: + "@babel/runtime" "^7.13.10" + +"@remirror/core-helpers@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@remirror/core-helpers/-/core-helpers-2.0.1.tgz#6847666a009ada8c9b9f3a093c13a6d07a95d9bb" + integrity sha512-s8M1pn33aBUhduvD1QR02uUQMegnFkGaTr4c1iBzxTTyg0rbQstzuQ7Q8TkL6n64JtgCdJS9jLz2dONb2meBKQ== + dependencies: + "@babel/runtime" "^7.13.10" + "@linaria/core" "3.0.0-beta.13" + "@remirror/core-constants" "^2.0.0" + "@remirror/types" "^1.0.0" + "@types/object.omit" "^3.0.0" + "@types/object.pick" "^1.3.1" + "@types/throttle-debounce" "^2.1.0" + case-anything "^2.1.10" + dash-get "^1.0.2" + deepmerge "^4.2.2" + fast-deep-equal "^3.1.3" + make-error "^1.3.6" + object.omit "^3.0.0" + object.pick "^1.3.0" + throttle-debounce "^3.0.1" + +"@remirror/types@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@remirror/types/-/types-1.0.0.tgz#cc8764440089a2ada71f149c409739575b73b12e" + integrity sha512-7HQbW7k8VxrAtfzs9FxwO6XSDabn8tSFDi1wwzShOnU+cvaYpfxu0ygyTk3TpXsag1hgFKY3ZIlAfB4WVz2LkQ== + dependencies: + type-fest "^2.0.0" + "@sentry/core@5.30.0": version "5.30.0" resolved "https://registry.yarnpkg.com/@sentry/core/-/core-5.30.0.tgz#6b203664f69e75106ee8b5a2fe1d717379b331f3" @@ -2006,227 +2035,214 @@ dom-accessibility-api "^0.5.1" pretty-format "^26.4.2" -"@tiptap/core@^2.0.0-beta.202": - version "2.0.0-beta.202" - resolved "https://registry.yarnpkg.com/@tiptap/core/-/core-2.0.0-beta.202.tgz#be4c6a200e81875c552e1cc15ae9c6cdd86c37c2" - integrity sha512-KnOcZBtkWoDT7EsVLiJr9DyBnQcKJQHI8kOhNIL0snUrksr25q8xBW05iYqw6cGAF7iu1cFM80VikfgefsZUpw== - dependencies: - prosemirror-commands "^1.3.1" - prosemirror-keymap "^1.2.0" - prosemirror-model "^1.18.1" - prosemirror-schema-list "^1.2.2" - prosemirror-state "^1.4.1" - prosemirror-transform "^1.7.0" - prosemirror-view "^1.28.2" +"@tiptap/core@^2.0.0-beta.217": + version "2.0.0-beta.217" + resolved "https://registry.yarnpkg.com/@tiptap/core/-/core-2.0.0-beta.217.tgz#c3cc31c23cc7d12f2727bf065d99f0d18fe59dc1" + integrity sha512-Vifwcg5SglkVjEmtFbnwHOKWU4UUenOhe7ke5fqGhh7FNfGkccu6sK8W1JTDbG4ARWZ1b/632kQ51YE+WuPe7g== -"@tiptap/extension-blockquote@^2.0.0-beta.202": - version "2.0.0-beta.202" - resolved "https://registry.yarnpkg.com/@tiptap/extension-blockquote/-/extension-blockquote-2.0.0-beta.202.tgz#91168b32387ed0ce4d03dd3b6c802682287ede3c" - integrity sha512-weLbMxM7VfI4hJsThw1+mB4jbQnVFizmzRlGU40LKMzEU5yIgIhuaomQ02Z7V0cRgfXsoKX9oc0BYGiO0Ra6/g== +"@tiptap/extension-blockquote@^2.0.0-beta.217": + version "2.0.0-beta.217" + resolved "https://registry.yarnpkg.com/@tiptap/extension-blockquote/-/extension-blockquote-2.0.0-beta.217.tgz#70575fe2b127d1cbcade756bbcd6b07fe263b89c" + integrity sha512-4U6DUr+xaCe3MRMAh3uMA5devHZmyrk6/6MURg57S3uRXDIMiWlKBkSxxys0URjbpS17T41hvKlRQR85tO1N1Q== -"@tiptap/extension-bold@^2.0.0-beta.202": - version "2.0.0-beta.202" - resolved "https://registry.yarnpkg.com/@tiptap/extension-bold/-/extension-bold-2.0.0-beta.202.tgz#877309e7b84701c22865517f09cbc5d288aa9369" - integrity sha512-AsfoChIleoSbY9gAuhbLF8BAEhHPrRKofmU09xJ62SBkL1rtgci8YzJYhL9leQCM4n1MQZEDeVf0ho75HeTPMA== +"@tiptap/extension-bold@^2.0.0-beta.217": + version "2.0.0-beta.217" + resolved "https://registry.yarnpkg.com/@tiptap/extension-bold/-/extension-bold-2.0.0-beta.217.tgz#94be8feed4e26e5f103d5a3a386c4079d946454f" + integrity sha512-oLzekoVWjvkAbDyOIv4Aj9DzjIwCQLTy33kXH/ItTxH2Z/zxZ+Et3y9qAZi0eR8x8wO552cG41lYUte4tuwo1w== -"@tiptap/extension-bubble-menu@2.0.0-beta.200", "@tiptap/extension-bubble-menu@^2.0.0-beta.200": - version "2.0.0-beta.200" - resolved "https://registry.yarnpkg.com/@tiptap/extension-bubble-menu/-/extension-bubble-menu-2.0.0-beta.200.tgz#7851efdb5b9bccf78a020e386ee0f5eb92103198" - integrity sha512-57EIDlYpI+jRurzHzsnulkAgf+LFoGytK5YcoXmuq8A2f295mj9ANzuOUnLJ76nm2NBmt8Y6ks0zUIIktKKK0Q== +"@tiptap/extension-bubble-menu@2.0.0-beta.217", "@tiptap/extension-bubble-menu@^2.0.0-beta.217": + version "2.0.0-beta.217" + resolved "https://registry.yarnpkg.com/@tiptap/extension-bubble-menu/-/extension-bubble-menu-2.0.0-beta.217.tgz#c72e1d9dba18f2cbe45400002e3649618670eaf3" + integrity sha512-f52hjIzNbvAJy0P3pTf6sv/65XlMU1LCLXam4VCTkYJ2HHVvW+LZcu+dG1M8YmGkM9l+6dQPs5L2rriAQDXt+w== dependencies: - prosemirror-state "^1.4.1" - prosemirror-view "^1.28.2" + lodash "^4.17.21" tippy.js "^6.3.7" -"@tiptap/extension-bullet-list@^2.0.0-beta.202": - version "2.0.0-beta.202" - resolved "https://registry.yarnpkg.com/@tiptap/extension-bullet-list/-/extension-bullet-list-2.0.0-beta.202.tgz#1826ec86a83cc2feba3f90b3015c0e642a925f8c" - integrity sha512-Su+GvRGyW9FTBtcFjvNkkYwzDRo+1O2YTNOZi1Z/OkDqbg3g89kRue78avs0nHW7HEgdhCap+z8KtAPrie4eBg== - -"@tiptap/extension-code-block-lowlight@2.0.0-beta.202": - version "2.0.0-beta.202" - resolved "https://registry.yarnpkg.com/@tiptap/extension-code-block-lowlight/-/extension-code-block-lowlight-2.0.0-beta.202.tgz#a94635bbe26f94eec0aef08682ebc6cadd3ca05f" - integrity sha512-FnYBl0Bgir+KkSScK4bUKVfvmEb2Q0aeDQh53umCeCGpVIzyO6JqggNqtBh0RqnJDptCSoP72fq7lu8uhQtYhw== +"@tiptap/extension-bullet-list@^2.0.0-beta.217": + version "2.0.0-beta.217" + resolved "https://registry.yarnpkg.com/@tiptap/extension-bullet-list/-/extension-bullet-list-2.0.0-beta.217.tgz#bedcf2c45412b613987804ef96a977bcd50ba78b" + integrity sha512-t/9lqdZhErYUOtU9H2m/qDjUUSilzVAjI2pVvJt6y9Z7Aex9uS7sPBVhjirjV43G4yQEVhT7BuTAzyCUX+JXkQ== + +"@tiptap/extension-code-block-lowlight@2.0.0-beta.217": + version "2.0.0-beta.217" + resolved "https://registry.yarnpkg.com/@tiptap/extension-code-block-lowlight/-/extension-code-block-lowlight-2.0.0-beta.217.tgz#48018c776f68ec070fa28b5189a7233fb421d58a" + integrity sha512-osfnTclcymplaks4a1vyh1qTFvkQ1ZERmXHq1t6XGdNeus7GrhIt3Lkd+1T0Ws4XdJu/Ut3GX+C1Zisnz7sNRQ== + +"@tiptap/extension-code-block@^2.0.0-beta.217": + version "2.0.0-beta.217" + resolved "https://registry.yarnpkg.com/@tiptap/extension-code-block/-/extension-code-block-2.0.0-beta.217.tgz#ba8e6ef8d848056dec9bbcebabf1e5d2629a96a1" + integrity sha512-vmntduEBM/o+mx4+sSEv+aVU8swr5rOFswMr7tYMXRqzXFYbqjvlEo86kGOQ4lrpnvf/2eUEjrq+2L4Hx2LdsA== + +"@tiptap/extension-code@^2.0.0-beta.217": + version "2.0.0-beta.217" + resolved "https://registry.yarnpkg.com/@tiptap/extension-code/-/extension-code-2.0.0-beta.217.tgz#40f8bea6af46c1571212f4eae8900481dea51829" + integrity sha512-IPpuI8MeVX5N0ueWQcIvh2nD+EN6DbFtKSsoU+FgFJ9meOkiEWGibSJ5yFQinPsiE9zrwYIaZzzjhr4oeY2JNA== + +"@tiptap/extension-document@^2.0.0-beta.217": + version "2.0.0-beta.217" + resolved "https://registry.yarnpkg.com/@tiptap/extension-document/-/extension-document-2.0.0-beta.217.tgz#ce53814197af814fe533da541f9681111fa5d52e" + integrity sha512-CDCTutbVO1Ub7QUULPCYILl2px48ezCX2kxbspQZzPD7nMoYZNmUMZ8gRJvVDdnSpMNlEl7oNnCAU7uC/lny/A== + +"@tiptap/extension-dropcursor@^2.0.0-beta.217": + version "2.0.0-beta.217" + resolved "https://registry.yarnpkg.com/@tiptap/extension-dropcursor/-/extension-dropcursor-2.0.0-beta.217.tgz#6bb99051c576787cf2e6d8ad3d0004dd3b898268" + integrity sha512-+HkLmLBKSAJB987KM6z/pxsE+QMr7wZJiTSJv4JSTQnSjKwyJIHbfYNvNgWZo0k9MSncmLGhd/c4TFxYJZ762w== + +"@tiptap/extension-floating-menu@^2.0.0-beta.217": + version "2.0.0-beta.217" + resolved "https://registry.yarnpkg.com/@tiptap/extension-floating-menu/-/extension-floating-menu-2.0.0-beta.217.tgz#0c571bc1526b00c1831930467be8b1ba996a799b" + integrity sha512-mP77UrimZo3sdC+Xam0DsPeYfXn29hl4ixEuZeiQQBMqz7g1K9T7vOEsRMQCBVFS7Hsc7MWBqLa1lL1GtyBB9Q== dependencies: - prosemirror-model "^1.18.1" - prosemirror-state "^1.4.1" - prosemirror-view "^1.28.2" - -"@tiptap/extension-code-block@^2.0.0-beta.202": - version "2.0.0-beta.202" - resolved "https://registry.yarnpkg.com/@tiptap/extension-code-block/-/extension-code-block-2.0.0-beta.202.tgz#a76fcb90e8e22082bedd9853f7a9bd0a541a800e" - integrity sha512-tfK9khIroGjsXQvk2K/9z1/UyQrB4+zghkjyK1xikzRmhgfOeqQzA0TDrFrz7ywFXmSFQ7GnnYAp+RW6r6wyUg== - dependencies: - prosemirror-state "^1.4.1" - -"@tiptap/extension-code@^2.0.0-beta.202": - version "2.0.0-beta.202" - resolved "https://registry.yarnpkg.com/@tiptap/extension-code/-/extension-code-2.0.0-beta.202.tgz#5d4352a6cc4a2d16262472fb11c54cfca24bd40a" - integrity sha512-XwAr7ysSWJVZWHNXDaNBTPH1CTyVxHnPv/PiCWTGhf8Fkx7R7xW2QCUKx4ablwxFlTY7H8xGmCujaewUQBdO5w== - -"@tiptap/extension-document@^2.0.0-beta.202": - version "2.0.0-beta.202" - resolved "https://registry.yarnpkg.com/@tiptap/extension-document/-/extension-document-2.0.0-beta.202.tgz#fa2b1a42075b1a6bfbcd9564289dfbf7347c903c" - integrity sha512-UsDSe93QtnuDrUo11wYCMtp7XlTIBvL5HNhx+enLRY7B8nUhX+d78u1BzspTpCkMYKcdwDmAGfIYMqqPViPEvA== - -"@tiptap/extension-dropcursor@^2.0.0-beta.202": - version "2.0.0-beta.202" - resolved "https://registry.yarnpkg.com/@tiptap/extension-dropcursor/-/extension-dropcursor-2.0.0-beta.202.tgz#a7e2b3c66dfb5c05077c05991fccd5e4166bdc1d" - integrity sha512-4Q3LnqvMnxP0KdX7tIgCoTCKg949rg351m0wguVb1bo4v9lA0zfJpSgqjQ1Xs2vaYVBwkFjLoqrfhTRn5mnopQ== - dependencies: - prosemirror-dropcursor "1.5.0" - -"@tiptap/extension-floating-menu@^2.0.0-beta.200": - version "2.0.0-beta.202" - resolved "https://registry.yarnpkg.com/@tiptap/extension-floating-menu/-/extension-floating-menu-2.0.0-beta.202.tgz#9f895a04cd93cf4b41542ef3b09f3c90bfd0e215" - integrity sha512-09liirOFsPDFRLS2FiFdnfzyyOQwwyVXLzI6MzUOw5RZbOsGJ5kB8jZdkXvsAIiOs0YYsH3fyOyWirIwSRhBTQ== - dependencies: - prosemirror-state "^1.4.1" - prosemirror-view "^1.28.2" tippy.js "^6.3.7" -"@tiptap/extension-gapcursor@^2.0.0-beta.202": - version "2.0.0-beta.202" - resolved "https://registry.yarnpkg.com/@tiptap/extension-gapcursor/-/extension-gapcursor-2.0.0-beta.202.tgz#567fb6281068db7fb5da2092550e83e01530d8cf" - integrity sha512-jOPMPPnTfVuc5YpFTcQM42/cg1J3+OeHitYb1/vBMpaNinVijuafsK14xDoVP8+sydKVgtBzYkfP/faN82I9iA== - dependencies: - prosemirror-gapcursor "^1.3.1" - -"@tiptap/extension-hard-break@^2.0.0-beta.202": - version "2.0.0-beta.202" - resolved "https://registry.yarnpkg.com/@tiptap/extension-hard-break/-/extension-hard-break-2.0.0-beta.202.tgz#f82c29f71082ae44c361964fb42e6e78d339dc29" - integrity sha512-Nr9BXeP+dXS5vLP/C2voTrhl+4YkDHBtPlc+5xm5NPBn04slTGSPO2lgV3YrMsfUOMNXHqeob1lq4qiLF4pybQ== - -"@tiptap/extension-heading@^2.0.0-beta.202": - version "2.0.0-beta.202" - resolved "https://registry.yarnpkg.com/@tiptap/extension-heading/-/extension-heading-2.0.0-beta.202.tgz#da12041c626825f8a3f3173ac922e11f2df55cbf" - integrity sha512-sF271jSWHgtoJLDNFLS7eyUcUStl7mBDQNJIENWVI+lFu2Ax8GmO7AoB74Q6L5Zaw4h73L6TAvaafHIXurz7tA== - -"@tiptap/extension-highlight@^2.0.0-beta.209": - version "2.0.0-beta.209" - resolved "https://registry.yarnpkg.com/@tiptap/extension-highlight/-/extension-highlight-2.0.0-beta.209.tgz#f373884097c17daf92ce3aecdc6c3781a4904c31" - integrity sha512-uy0Zu1dU2VcozvqpECb7+mxiazxstlb6w7jQQx8n4PAwIfN2LGv1UIil+VKRkc9amjBVwz+knm9psXEeJfjiBw== - -"@tiptap/extension-history@^2.0.0-beta.202": - version "2.0.0-beta.202" - resolved "https://registry.yarnpkg.com/@tiptap/extension-history/-/extension-history-2.0.0-beta.202.tgz#d94770367a1a7b4e8f81f36d8b1731ca1fbc6879" - integrity sha512-BLwaOWmFHBQjOonojYHl1Po27IHxgjSAPw+ijMKtKzqa2msJRJevjC4tBaX5s/YrB7PQ2tFE7rfJED4HLjBm6w== - dependencies: - prosemirror-history "^1.3.0" - -"@tiptap/extension-horizontal-rule@^2.0.0-beta.202": - version "2.0.0-beta.202" - resolved "https://registry.yarnpkg.com/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-2.0.0-beta.202.tgz#12da36ea8ede0e720c7b2e5d1a2dd4c255798b2f" - integrity sha512-ut2Im/TNQynnuqdoY9yOjMDUKmxn97ERVEpqcQSaIgqBuF6bjk60Wa13ob6oS2g6vqXxwWFrnQVz48A9TcF5FQ== - dependencies: - prosemirror-state "^1.4.1" - -"@tiptap/extension-image@^2.0.0-beta.202": - version "2.0.0-beta.202" - resolved "https://registry.yarnpkg.com/@tiptap/extension-image/-/extension-image-2.0.0-beta.202.tgz#5e50fdf7d7ac67b3475b1cfba71fb3acaddab0df" - integrity sha512-aHPJMXuoMgToTYkGZsz2ue8gKzes+B92qb9lVRYlY9f+r/tC2K4q3HMtx6qvh8l4Dei5/yeV9TqliY79E9A5dg== - -"@tiptap/extension-italic@^2.0.0-beta.202": - version "2.0.0-beta.202" - resolved "https://registry.yarnpkg.com/@tiptap/extension-italic/-/extension-italic-2.0.0-beta.202.tgz#0a991167dd06064bdf3c811d638d04cea9029206" - integrity sha512-vgSLy4KDp6AmnAHLHXe/nWeNbLnyUXxmf4U4+esebAV5Hu2F7LgceknFt9D8AGEtYUU+/fYKSeE2NGJgTQG9lA== - -"@tiptap/extension-link@^2.0.0-beta.202": - version "2.0.0-beta.202" - resolved "https://registry.yarnpkg.com/@tiptap/extension-link/-/extension-link-2.0.0-beta.202.tgz#2b10ef877937bf2d491fffaf3e0545105f61b6fb" - integrity sha512-/9bZd43dMkzmo7SCCDtEscgdFKCat9yZAyAyg+PHYdhI8Lbqv5GfjxzBbx58v7jEP1eDKFnwTDFVwAsxCE9f0w== +"@tiptap/extension-gapcursor@^2.0.0-beta.217": + version "2.0.0-beta.217" + resolved "https://registry.yarnpkg.com/@tiptap/extension-gapcursor/-/extension-gapcursor-2.0.0-beta.217.tgz#c3e59b8a08ababc5d267afc5f2417e515245d098" + integrity sha512-BMZAeMuba41EOmSo8ZEClarbrO4XDfGvOslWkjJwqPyJ4oO3nHtX8ep13g6Y3RhMSUDEGmBSUXEKVuUPgSS+Qg== + +"@tiptap/extension-hard-break@^2.0.0-beta.217": + version "2.0.0-beta.217" + resolved "https://registry.yarnpkg.com/@tiptap/extension-hard-break/-/extension-hard-break-2.0.0-beta.217.tgz#2bd5cd1a0a7e7a535287e99bf87e86a3fd86a772" + integrity sha512-15cKxMsJZJ+3cbSKuMa7wjr2sQHxjoJ8aCmbPqUH+zGNLsRtUHvTZzeIPGxJ1SpEjpJpb+rRnu9RMTNjwDMFMA== + +"@tiptap/extension-heading@^2.0.0-beta.217": + version "2.0.0-beta.217" + resolved "https://registry.yarnpkg.com/@tiptap/extension-heading/-/extension-heading-2.0.0-beta.217.tgz#0fc0f2a4974e2b31c890463798d75c5d0f20d384" + integrity sha512-v7G/1R0+qmPm0U2sOCOftqZef5yd89tEg1R+KVF78ucFvuLgH50Pldes8lhHEBlfoRShye2Ht54E6JHDP73J5w== + +"@tiptap/extension-highlight@^2.0.0-beta.217": + version "2.0.0-beta.217" + resolved "https://registry.yarnpkg.com/@tiptap/extension-highlight/-/extension-highlight-2.0.0-beta.217.tgz#366230aa0e6a26ad7c0baf6bdcac026474f2b5c4" + integrity sha512-JS+WJQrMOUfOAeNazi3FioaQ78MsuakMcZmSdy+tZuv3zwPhLPDZzlVj+K4wZtzPejxlucpE93BiW2FmJDyNgg== + +"@tiptap/extension-history@^2.0.0-beta.217": + version "2.0.0-beta.217" + resolved "https://registry.yarnpkg.com/@tiptap/extension-history/-/extension-history-2.0.0-beta.217.tgz#1bbe8ecc7bfc6384a12091b4b0b4433789b3d1a2" + integrity sha512-wdgAGMTFVeEh6hlO1Iy0l0LzqUVuoplrYnc2zjry4YFGCJbWwQqLKEKQaHIvmMea0PaXW4Q5wpNy875+UoXDOw== + +"@tiptap/extension-horizontal-rule@^2.0.0-beta.217": + version "2.0.0-beta.217" + resolved "https://registry.yarnpkg.com/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-2.0.0-beta.217.tgz#7e6a8d6fb2b3fc6eaa23b8029048cd36b18473fc" + integrity sha512-h7uIpGOYLCPfYJUit6Fe8oLnrMf0hK0mHBXa8cV7AWIlDvVRrXZZ/sdtLTF+TXOylvbOYR+vjkvnwDTeVX6dcw== + +"@tiptap/extension-image@^2.0.0-beta.217": + version "2.0.0-beta.217" + resolved "https://registry.yarnpkg.com/@tiptap/extension-image/-/extension-image-2.0.0-beta.217.tgz#ddb03f2c4a4d3f007c5b2777a50b89e084581470" + integrity sha512-8ibJOicpcqhFeiPLqYOY8SO9sxkz2bemzwhtzRvgkevy/QERe9D0mbAL0qm1gg88LtXy3z2eFUdwD8suq1xsPQ== + +"@tiptap/extension-italic@^2.0.0-beta.217": + version "2.0.0-beta.217" + resolved "https://registry.yarnpkg.com/@tiptap/extension-italic/-/extension-italic-2.0.0-beta.217.tgz#e6470cfd1763af0141f74165e404029332bca0bf" + integrity sha512-Oua5QbsOJ/MNV41fvbBtJOE/ciw2AjWybSiPkn8clu1/KjieE9dectlj+COaNbVIOccTNhurECpqfF0h8K87Kg== + +"@tiptap/extension-link@^2.0.0-beta.217": + version "2.0.0-beta.217" + resolved "https://registry.yarnpkg.com/@tiptap/extension-link/-/extension-link-2.0.0-beta.217.tgz#5f0a3eca6ae2bd560d35036e3fffc3823cfbb2bd" + integrity sha512-pC1UnK1OrbW+NDdBdHE8sfmZ+tOpIOsHVAeeX6pg1fkdP/FlbPFFvcvILgJc20dr7DiM6dRqYq16H473G9vyEg== dependencies: linkifyjs "^3.0.5" - prosemirror-model "^1.18.1" - prosemirror-state "^1.4.1" -"@tiptap/extension-list-item@^2.0.0-beta.202": - version "2.0.0-beta.202" - resolved "https://registry.yarnpkg.com/@tiptap/extension-list-item/-/extension-list-item-2.0.0-beta.202.tgz#32bc4beacb0f692abfc8283760292a6354f070dc" - integrity sha512-15yAsO+CCM8ievdX4oxg8kMBVFqhzVAw7pU6E8KL76kIwWCIIyVW6hU3VZdglyBVnAG0ws5/DaZ4VRFtVPRDvg== - -"@tiptap/extension-ordered-list@^2.0.0-beta.202": - version "2.0.0-beta.202" - resolved "https://registry.yarnpkg.com/@tiptap/extension-ordered-list/-/extension-ordered-list-2.0.0-beta.202.tgz#5b5987d3a1c98e579071e70485db2e82bf638ed9" - integrity sha512-PpJn8EtS8MLZ4NN9R3crmrivbjTMHjuSE2Ab3Y9TdeR9x9DIF23O/EkunnkPUiBUx6sNADprEWJIQesgpakrtw== - -"@tiptap/extension-paragraph@^2.0.0-beta.202": - version "2.0.0-beta.202" - resolved "https://registry.yarnpkg.com/@tiptap/extension-paragraph/-/extension-paragraph-2.0.0-beta.202.tgz#5a395a8107bfc767f80335a0cf7f89278ad0cf20" - integrity sha512-QI86DMUAz5froDJJXpbFV0I+iSFikjhQ8W5clYDbnrP/clRI/FYxklQ3oxSk4VzGBGB5EaBJf+jD7htLKb39UA== - -"@tiptap/extension-strike@^2.0.0-beta.202": - version "2.0.0-beta.202" - resolved "https://registry.yarnpkg.com/@tiptap/extension-strike/-/extension-strike-2.0.0-beta.202.tgz#8bf200fb044e5b2401edef26e26920b94ab35249" - integrity sha512-cs87UI/VTkmSfIwlHpm7nAPXok2bAQvxmNJ1y7UPzTATVl+ixP1F4aIkwiYk+X7rE/Sys+09PGg1Pr1shwUUkQ== - -"@tiptap/extension-subscript@^2.0.0-beta.202": - version "2.0.0-beta.202" - resolved "https://registry.yarnpkg.com/@tiptap/extension-subscript/-/extension-subscript-2.0.0-beta.202.tgz#730e0181c5b8d623fb4674d568d5ef4f3bd986d5" - integrity sha512-s/90xjDJGlWxontzyL0howpdTEbnl5EyvIDDYdAwalnHOGkyfWq8JMxL3klnxUL+Rn10sjatHnEYaLS2A5tN+Q== - -"@tiptap/extension-superscript@^2.0.0-beta.202": - version "2.0.0-beta.202" - resolved "https://registry.yarnpkg.com/@tiptap/extension-superscript/-/extension-superscript-2.0.0-beta.202.tgz#d756fb63585f8214ce3a7e2333978f2fb68b1259" - integrity sha512-kDOc+Sf1N5LzKWDBrTYBWaQFG40z3dCt/ljIMDWJt3Vka8ahnH4kcXhW9eNZLeIWavoj+M+4th+CPSfMdAYJbg== - -"@tiptap/extension-table-cell@^2.0.0-beta.202": - version "2.0.0-beta.202" - resolved "https://registry.yarnpkg.com/@tiptap/extension-table-cell/-/extension-table-cell-2.0.0-beta.202.tgz#95f3f9c14d4f21122bab109fb82e10b64ca5e613" - integrity sha512-Ypmcq7zaMSZ0VNKwDPINOsSzyuH+gSIw+FrXy6O1dzVHAo1gNFJ2pEG/ZhQ2RqpDTpGfJFD8tNDx8wjCCAVlxA== - -"@tiptap/extension-table-header@^2.0.0-beta.202": - version "2.0.0-beta.202" - resolved "https://registry.yarnpkg.com/@tiptap/extension-table-header/-/extension-table-header-2.0.0-beta.202.tgz#e9284abcfbe17c3c1ddfff55bb98873879c9d34a" - integrity sha512-/l0lz3Hmc+hikj+RfSW7F6B/jYV2dROGQnK1/EYjgbvOK0158ml1mB6/Dhm+BhldV73MI7eU8+3YLB9uhsPR4w== - -"@tiptap/extension-table-row@^2.0.0-beta.202": - version "2.0.0-beta.202" - resolved "https://registry.yarnpkg.com/@tiptap/extension-table-row/-/extension-table-row-2.0.0-beta.202.tgz#60cc878e7f7ae4ad82e1013471cb39a4058f9844" - integrity sha512-IsHBT3lp//XSqcAWPIGWjPIKQ4okVaDJbwcElehlOo/rcRBeK0orT+c10T08PoOsozi4BeMYRo0nfA5tvrJMEw== - -"@tiptap/extension-table@^2.0.0-beta.202": - version "2.0.0-beta.202" - resolved "https://registry.yarnpkg.com/@tiptap/extension-table/-/extension-table-2.0.0-beta.202.tgz#841254bf183e84a5b76edb4f1719883c85d51df8" - integrity sha512-WMfXtDfx45FgU81WnfxGOSJbVoaDpe8hjuBJSGbwJj+Qj4HGhbK7/RbTtDrM8oqseHRzHuGWgNX+EfOUQppjdA== - dependencies: - "@_ueberdosis/prosemirror-tables" "1.1.3" +"@tiptap/extension-list-item@^2.0.0-beta.217": + version "2.0.0-beta.217" + resolved "https://registry.yarnpkg.com/@tiptap/extension-list-item/-/extension-list-item-2.0.0-beta.217.tgz#811c1197023bed2ed96f1e88f14ee66eac77d8f7" + integrity sha512-yJmB/Je6CAiyjjJyVl77PPv4AWNFejrhxxmIdPfkYNRuQx9xX5yccx/zXC5RMaUFZVttlC4+gXDZsCvxMiVF1g== + +"@tiptap/extension-ordered-list@^2.0.0-beta.217": + version "2.0.0-beta.217" + resolved "https://registry.yarnpkg.com/@tiptap/extension-ordered-list/-/extension-ordered-list-2.0.0-beta.217.tgz#37ec7a2b10c03bb6f53fe901873ffdbc1626c79f" + integrity sha512-Jv79D+imZPqFi7U+nrMBQbNp2FQDk9Y2D7jTovM0ySCYLbgYtG/n5ZLj/eBAN6MwLcqHHWzZa1TXWzF7anlUOQ== + +"@tiptap/extension-paragraph@^2.0.0-beta.217": + version "2.0.0-beta.217" + resolved "https://registry.yarnpkg.com/@tiptap/extension-paragraph/-/extension-paragraph-2.0.0-beta.217.tgz#a5debdebf1290066aa84fec65fad0c71d785842a" + integrity sha512-EeWCz6Boz1eeCrECQ8i9yzbs1MyhmuLmPnvW0WUyNuqlA/qbgr4OvqDGZk4X+4E3CI30Nqzr0PPNpz0CWQgphQ== + +"@tiptap/extension-strike@^2.0.0-beta.217": + version "2.0.0-beta.217" + resolved "https://registry.yarnpkg.com/@tiptap/extension-strike/-/extension-strike-2.0.0-beta.217.tgz#2a650ba010e225d700c5c87a64709c078bc23c3c" + integrity sha512-frC3SUoh9bSrHYWdW2S3A8s+FEqDMaNjp+Ldoe6vz2ugHXxjUFJ+SsF/pcQbmKw6FsNaf/bqL24KieRt22kgpg== + +"@tiptap/extension-subscript@^2.0.0-beta.217": + version "2.0.0-beta.217" + resolved "https://registry.yarnpkg.com/@tiptap/extension-subscript/-/extension-subscript-2.0.0-beta.217.tgz#3e79e2526aa758af9770ea71781afb917a624724" + integrity sha512-Xz9x0YUxGkADtGBP/74v3BCr6d+TFZ4E6BmwnzXSfSCjdJx8ycdNXtX4aZsOMu7Gj0cxXF4aTr+s4bm9RHWyjg== + +"@tiptap/extension-superscript@^2.0.0-beta.217": + version "2.0.0-beta.217" + resolved "https://registry.yarnpkg.com/@tiptap/extension-superscript/-/extension-superscript-2.0.0-beta.217.tgz#4eb81166d1bd095bf2ab1de712122a3b362ba628" + integrity sha512-ZMvUmXN3j3Qm25IRCe8QBkwFIk2r6bKl6qtedUO4R8Yk4xeYkaGp+FA3OHBME03SLEeuNHOoguIWyQ1GO2dCcA== + +"@tiptap/extension-table-cell@^2.0.0-beta.217": + version "2.0.0-beta.217" + resolved "https://registry.yarnpkg.com/@tiptap/extension-table-cell/-/extension-table-cell-2.0.0-beta.217.tgz#6715c2057b28f4c5297dd27419da572c6d15ad15" + integrity sha512-W5UxsZxQdBms916hHp4giXi6AOkwCEfSaTXfi3FQqxcg/EQnmzMNB82/9BcVqBUaoJrx1dIVm4ploIL+GikG/w== + +"@tiptap/extension-table-header@^2.0.0-beta.217": + version "2.0.0-beta.217" + resolved "https://registry.yarnpkg.com/@tiptap/extension-table-header/-/extension-table-header-2.0.0-beta.217.tgz#684671d431430b916f4da01dc2c294e6daa7dfc6" + integrity sha512-oahTLhItvoPzCA9RuGLowZ0ZGro+Yn3+1NefXu/yGlp3twKQyhrwOv3+TqZ21L+8uKGOVLfLgPZnF6oNozEdJQ== + +"@tiptap/extension-table-row@^2.0.0-beta.217": + version "2.0.0-beta.217" + resolved "https://registry.yarnpkg.com/@tiptap/extension-table-row/-/extension-table-row-2.0.0-beta.217.tgz#1e206eef50c7879acf78f0044b806002f30f93da" + integrity sha512-6ie3YtnOliIzER4JtVh0T8HQl3Z2gwTBoCOvqoetsoKIk0zNdsai+ZjVjVN4ZiMLFNYm5xnCUfr83usp9kawhQ== + +"@tiptap/extension-table@^2.0.0-beta.217": + version "2.0.0-beta.217" + resolved "https://registry.yarnpkg.com/@tiptap/extension-table/-/extension-table-2.0.0-beta.217.tgz#5ff8fb533df2beb92b6ad20e5e582cc43a5d3297" + integrity sha512-8PwfNXIRPy1zxZAk0kS+sqFeUE2M6al1y/mA6p0SA9YhSN0iWvjQfmq9Ds52hmRcL2Dv9QmLR97S7WGRmHKcQg== + +"@tiptap/extension-task-item@^2.0.0-beta.217": + version "2.0.0-beta.217" + resolved "https://registry.yarnpkg.com/@tiptap/extension-task-item/-/extension-task-item-2.0.0-beta.217.tgz#bc351cc1fc847496feb12b592345a488f352f970" + integrity sha512-eLdIuLTDkTKPEzuy2uG7akv+M39/+vHK/7E115LtQ9KQiN3iLaE0RHXPb5vPvP8gX9U2gGeboRhhMxf/7yh/vg== + +"@tiptap/extension-task-list@^2.0.0-beta.217": + version "2.0.0-beta.217" + resolved "https://registry.yarnpkg.com/@tiptap/extension-task-list/-/extension-task-list-2.0.0-beta.217.tgz#f90a4c48d5c2aa1e3e6d86c9ce6947aba798ceab" + integrity sha512-d5+YkjPBjc4beVCOip+T1GW1HUo7vLwp9Mrec+PcxMSU1GDUwxvz3zpkKG3rrTnwK5QzEnvmSQC2SzSRp//OPQ== + +"@tiptap/extension-text@^2.0.0-beta.217": + version "2.0.0-beta.217" + resolved "https://registry.yarnpkg.com/@tiptap/extension-text/-/extension-text-2.0.0-beta.217.tgz#7d3f7bda8991b1bac9ef7cbc62ed71d17ad0243b" + integrity sha512-l5PkVhZzVnxbGyMiCMqpLU3ZomJWcZXhuBD0aajry+l85DWc6TK0mhLEAVEU1LoTL8hXFmICoW1KJ1m20Va9dg== + +"@tiptap/pm@^2.0.0-beta.217": + version "2.0.0-beta.217" + resolved "https://registry.yarnpkg.com/@tiptap/pm/-/pm-2.0.0-beta.217.tgz#d944519aa40c74e683402c0d4d39e460d905414e" + integrity sha512-krEplJli2BbBB3U2c+cxDPw4mpHsUEwOMJCM3fr1GTVv7qAOvsrWt2ndRPOwraPSvmUF3rw9gvoP86eyK2nDmA== + dependencies: + prosemirror-changeset "^2.2.0" + prosemirror-collab "^1.3.0" + prosemirror-commands "^1.3.1" + prosemirror-dropcursor "^1.5.0" + prosemirror-gapcursor "^1.3.1" + prosemirror-history "^1.3.0" + prosemirror-inputrules "^1.2.0" + prosemirror-keymap "^1.2.0" + prosemirror-markdown "^1.10.1" + prosemirror-menu "^1.2.1" prosemirror-model "^1.18.1" + prosemirror-schema-basic "^1.2.0" + prosemirror-schema-list "^1.2.2" prosemirror-state "^1.4.1" + prosemirror-tables "^1.3.0" + prosemirror-trailing-node "^2.0.2" + prosemirror-transform "^1.7.0" prosemirror-view "^1.28.2" -"@tiptap/extension-task-item@^2.0.0-beta.202": - version "2.0.0-beta.202" - resolved "https://registry.yarnpkg.com/@tiptap/extension-task-item/-/extension-task-item-2.0.0-beta.202.tgz#350fd2d823300bd9e64a56c98404bbcffe802f08" - integrity sha512-yOd72ELIr/NK3wb4SjetFCsTW/YU66LMjUCv6RFxgbcPtTN3auoaCelyo1l7EZSZmWbovR7G2QOiG1fQmfNgMg== - -"@tiptap/extension-task-list@^2.0.0-beta.202": - version "2.0.0-beta.202" - resolved "https://registry.yarnpkg.com/@tiptap/extension-task-list/-/extension-task-list-2.0.0-beta.202.tgz#b2484ca0507f1d8b413e7f2967f4b71afefa91a9" - integrity sha512-0E7hL2+vVeBm1khONomqk4Lac/LcQfKYesLvzLhjHOEuC7t8wTt7sZxI407b87YnQ+l8dsZey0vrcmV2/Wi4YQ== - -"@tiptap/extension-text@^2.0.0-beta.202": - version "2.0.0-beta.202" - resolved "https://registry.yarnpkg.com/@tiptap/extension-text/-/extension-text-2.0.0-beta.202.tgz#d0152f547572a14c4e0e88287acb2b0c311fef04" - integrity sha512-6UsfU9xvKTxHfZYxVJy5DSQ0ibnhC403KLRQ4ePwpJql0TotBx93/CBfPCVLFEwF86HNhf1fFUCx+j2wuwVxmA== +"@tiptap/suggestion@^2.0.0-beta.217": + version "2.0.0-beta.217" + resolved "https://registry.yarnpkg.com/@tiptap/suggestion/-/suggestion-2.0.0-beta.217.tgz#47a27da8784a8cfe27bdf8d707bd6ed8c15fa05c" + integrity sha512-Z1hXr1giNQ/fkuRrsMICtfyBCbMV+ZPiYYfpKKqUC4uc/lqAeHiRwk3lozpnLZEImnOW42LsYbYug2jtRqrDgA== -"@tiptap/suggestion@^2.0.0-beta.202": - version "2.0.0-beta.202" - resolved "https://registry.yarnpkg.com/@tiptap/suggestion/-/suggestion-2.0.0-beta.202.tgz#f0bc6a6712aeea266d7ed156c7c70ad29fb864e5" - integrity sha512-V2BepOUj3ycvogOZE/nY9q2J4Hm1oH44YhOkqHB+QFQeOu6vqhFRqgGniJji52z2tAF+dvydSpQEzG+uCf2MEA== +"@tiptap/vue-2@2.0.0-beta.217": + version "2.0.0-beta.217" + resolved "https://registry.yarnpkg.com/@tiptap/vue-2/-/vue-2-2.0.0-beta.217.tgz#59dc68a0c30fa36610bb4dc5ea4f0def698b2c6a" + integrity sha512-mjab0xA1YNeVtWGt3yHTfRNk+ozJRO9eXRHqXD9rYL5+UHd1oOnbIAqCeKD8+K2eacklDy5aH6tqg5G22kcuDg== dependencies: - prosemirror-model "^1.18.1" - prosemirror-state "^1.4.1" - prosemirror-view "^1.28.2" - -"@tiptap/vue-2@2.0.0-beta.200": - version "2.0.0-beta.200" - resolved "https://registry.yarnpkg.com/@tiptap/vue-2/-/vue-2-2.0.0-beta.200.tgz#ca3c2be6a813644de0e0ddbf96d973d355971e57" - integrity sha512-sLa7arOVuHUfuoqAKGQrbJACxYaZ0Qlh4pO9rAxbGglVvO+L2VOXKqLORnsh6Sr/G1G6CnfUPSjf/S6BVHFmGg== - dependencies: - "@tiptap/extension-bubble-menu" "^2.0.0-beta.200" - "@tiptap/extension-floating-menu" "^2.0.0-beta.200" - prosemirror-view "^1.28.2" + "@tiptap/extension-bubble-menu" "^2.0.0-beta.217" + "@tiptap/extension-floating-menu" "^2.0.0-beta.217" "@tootallnate/once@2": version "2.0.0" @@ -2470,6 +2486,16 @@ resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz#e486d0d97396d79beedd0a6e33f4534ff6b4973e" integrity sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA== +"@types/object.omit@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/object.omit/-/object.omit-3.0.0.tgz#0d31e1208eac8fe2ad5c9499a1016a8273bbfafc" + integrity sha512-I27IoPpH250TUzc9FzXd0P1BV/BMJuzqD3jOz98ehf9dQqGkxlq+hO1bIqZGWqCg5bVOy0g4AUVJtnxe0klDmw== + +"@types/object.pick@^1.3.1": + version "1.3.2" + resolved "https://registry.yarnpkg.com/@types/object.pick/-/object.pick-1.3.2.tgz#9eb28118240ad8f658b9c9c6caf35359fdb37150" + integrity sha512-sn7L+qQ6RLPdXRoiaE7bZ/Ek+o4uICma/lBFPyJEKDTPTBP1W8u0c4baj3EiS4DiqLs+Hk+KUGvMVJtAw3ePJg== + "@types/parse-json@^4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" @@ -2542,6 +2568,11 @@ resolved "https://registry.yarnpkg.com/@types/strip-json-comments/-/strip-json-comments-0.0.30.tgz#9aa30c04db212a9a0649d6ae6fd50accc40748a1" integrity sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ== +"@types/throttle-debounce@^2.1.0": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@types/throttle-debounce/-/throttle-debounce-2.1.0.tgz#1c3df624bfc4b62f992d3012b84c56d41eab3776" + integrity sha512-5eQEtSCoESnh2FsiLTxE121IiE60hnMqcb435fShf4bpLRjEu1Eoekht23y6zXS9Ts3l+Szu3TARnTsA0GkOkQ== + "@types/tough-cookie@*": version "4.0.2" resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-4.0.2.tgz#6286b4c7228d58ab7866d19716f3696e03a09397" @@ -3735,6 +3766,11 @@ canvas-confetti@^1.4.0: resolved "https://registry.yarnpkg.com/canvas-confetti/-/canvas-confetti-1.4.0.tgz#840f6db4a566f8f32abe28c00dcd82acf39c92bd" integrity sha512-S18o4Y9PqI/uabdlT/jI3MY7XBJjNxnfapFIkjkMwpz6qNxLFZOm2b22OMf4ZYDL9lpNWI+Ih4fEMVPwO1KHFQ== +case-anything@^2.1.10: + version "2.1.10" + resolved "https://registry.yarnpkg.com/case-anything/-/case-anything-2.1.10.tgz#d18a6ca968d54ec3421df71e3e190f3bced23410" + integrity sha512-JczJwVrCP0jPKh05McyVsuOg6AYosrB9XWZKbQzXeDAm2ClE/PJE/BcrrQrVyGYH7Jg8V/LDupmyL4kFlVsVFQ== + ccount@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/ccount/-/ccount-2.0.1.tgz#17a3bf82302e0870d6da43a01311a8bc02a3ecf5" @@ -4246,6 +4282,11 @@ create-require@^1.1.0: resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== +crelt@^1.0.0: + version "1.0.5" + resolved "https://registry.yarnpkg.com/crelt/-/crelt-1.0.5.tgz#57c0d52af8c859e354bace1883eb2e1eb182bb94" + integrity sha512-+BO9wPPi+DWTDcNYhr/W90myha8ptzftZT+LwcmUbbok0rcP/fequmFYCw8NMoH7pkAZQzU78b3kYrlua5a9eA== + cron-validator@^1.1.1: version "1.2.1" resolved "https://registry.yarnpkg.com/cron-validator/-/cron-validator-1.2.1.tgz#0f0de2de36d231a6ace0e43ffc6c0564fe6edf1a" @@ -4944,6 +4985,11 @@ dagre@^0.8.5: graphlib "^2.1.8" lodash "^4.17.15" +dash-get@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/dash-get/-/dash-get-1.0.2.tgz#4c9e9ad5ef04c4bf9d3c9a451f6f7997298dcc7c" + integrity sha512-4FbVrHDwfOASx7uQVxeiCTo7ggSdYZbqs8lH+WU6ViypPlDbe9y6IP5VVUDQBv9DcnyaiPT5XT0UWHgJ64zLeQ== + data-urls@^3.0.1: version "3.0.2" resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-3.0.2.tgz#9cf24a477ae22bcef5cd5f6f0bfbc1d2d3be9143" @@ -7320,7 +7366,7 @@ is-extendable@^0.1.0, is-extendable@^0.1.1: resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" integrity sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik= -is-extendable@^1.0.1: +is-extendable@^1.0.0, is-extendable@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-1.0.1.tgz#a7470f9e426733d81bd81e1155264e3a3507cab4" integrity sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA== @@ -8485,7 +8531,7 @@ make-dir@^3.0.0, make-dir@^3.0.2, make-dir@^3.1.0: dependencies: semver "^6.0.0" -make-error@^1.1.1: +make-error@^1.1.1, make-error@^1.3.6: version "1.3.6" resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== @@ -9665,6 +9711,13 @@ object.entries@^1.1.5: define-properties "^1.1.3" es-abstract "^1.19.1" +object.omit@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/object.omit/-/object.omit-3.0.0.tgz#0e3edc2fce2ba54df5577ff529f6d97bd8a522af" + integrity sha512-EO+BCv6LJfu+gBIF3ggLicFebFLN5zqzz/WWJlMFfkMyGth+oBkhxzDl0wx2W4GkLzuQs/FsSkXZb2IMWQqmBQ== + dependencies: + is-extendable "^1.0.0" + object.pick@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/object.pick/-/object.pick-1.3.0.tgz#87a10ac4c1694bd2e1cbf53591a66141fb5dd747" @@ -10251,19 +10304,33 @@ property-information@^6.0.0: resolved "https://registry.yarnpkg.com/property-information/-/property-information-6.1.1.tgz#5ca85510a3019726cb9afed4197b7b8ac5926a22" integrity sha512-hrzC564QIl0r0vy4l6MvRLhafmUowhO/O3KgVSoXIbbA2Sz4j8HGpJc6T2cubRVwMwpdiG/vKGfhT4IixmKN9w== -prosemirror-commands@^1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/prosemirror-commands/-/prosemirror-commands-1.3.1.tgz#926c88801eebaa50363d4658850b41406d375a31" - integrity sha512-XTporPgoECkOQACVw0JTe3RZGi+fls3/byqt+tXwGTkD7qLuB4KdVrJamDMJf4kfKga3uB8hZ+kUUyZ5oWpnfg== +prosemirror-changeset@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/prosemirror-changeset/-/prosemirror-changeset-2.2.0.tgz#22c05da271a118be40d3e339fa2cace789b1254b" + integrity sha512-QM7ohGtkpVpwVGmFb8wqVhaz9+6IUXcIQBGZ81YNAKYuHiFJ1ShvSzab4pKqTinJhwciZbrtBEk/2WsqSt2PYg== + dependencies: + prosemirror-transform "^1.0.0" + +prosemirror-collab@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/prosemirror-collab/-/prosemirror-collab-1.3.0.tgz#601d33473bf72e6c43041a54b860c84c60b37769" + integrity sha512-+S/IJ69G2cUu2IM5b3PBekuxs94HO1CxJIWOFrLQXUaUDKL/JfBx+QcH31ldBlBXyDEUl+k3Vltfi1E1MKp2mA== + dependencies: + prosemirror-state "^1.0.0" + +prosemirror-commands@^1.0.0, prosemirror-commands@^1.3.1: + version "1.5.0" + resolved "https://registry.yarnpkg.com/prosemirror-commands/-/prosemirror-commands-1.5.0.tgz#d10efece1647c1d984fef6f65d52fdc77785560b" + integrity sha512-zL0Fxbj3fh71GPNHn5YdYgYGX2aU2XLecZYk2ekEF0oOD259HcXtM+96VjPVi5o3h4sGUdDfEEhGiREXW6U+4A== dependencies: prosemirror-model "^1.0.0" prosemirror-state "^1.0.0" prosemirror-transform "^1.0.0" -prosemirror-dropcursor@1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/prosemirror-dropcursor/-/prosemirror-dropcursor-1.5.0.tgz#edbc61d6f71f9f924130eec8e85b0861357957c9" - integrity sha512-vy7i77ddKyXlu8kKBB3nlxLBnsWyKUmQIPB5x8RkYNh01QNp/qqGmdd5yZefJs0s3rtv5r7Izfu2qbtr+tYAMQ== +prosemirror-dropcursor@^1.5.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/prosemirror-dropcursor/-/prosemirror-dropcursor-1.7.0.tgz#a846ba49414dcd99cf8fc8bb26e4f9f24b8f09d0" + integrity sha512-vzab/iPd3CjWILFv6WJz4+BlOwCywOcAGhvY5G/66OYPcaZehN8IVbGtHCV3oyhXk2yAA67nwMv/oNMvBV9k1A== dependencies: prosemirror-state "^1.0.0" prosemirror-transform "^1.1.0" @@ -10279,7 +10346,7 @@ prosemirror-gapcursor@^1.3.1: prosemirror-state "^1.0.0" prosemirror-view "^1.0.0" -prosemirror-history@^1.3.0: +prosemirror-history@^1.0.0, prosemirror-history@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/prosemirror-history/-/prosemirror-history-1.3.0.tgz#bf5a1ff7759aca759ddf0c722c2fa5b14fb0ddc1" integrity sha512-qo/9Wn4B/Bq89/YD+eNWFbAytu6dmIM85EhID+fz9Jcl9+DfGEo8TTSrRhP15+fFEoaPqpHSxlvSzSEbmlxlUA== @@ -10288,10 +10355,18 @@ prosemirror-history@^1.3.0: prosemirror-transform "^1.0.0" rope-sequence "^1.3.0" -prosemirror-keymap@^1.0.0, prosemirror-keymap@^1.1.2, prosemirror-keymap@^1.2.0: +prosemirror-inputrules@^1.2.0: version "1.2.0" - resolved "https://registry.yarnpkg.com/prosemirror-keymap/-/prosemirror-keymap-1.2.0.tgz#d5cc9da9b712020690a994b50b92a0e448a60bf5" - integrity sha512-TdSfu+YyLDd54ufN/ZeD1VtBRYpgZnTPnnbY+4R08DDgs84KrIPEPbJL8t1Lm2dkljFx6xeBE26YWH3aIzkPKg== + resolved "https://registry.yarnpkg.com/prosemirror-inputrules/-/prosemirror-inputrules-1.2.0.tgz#476dde2dc244050b3aca00cf58a82adfad6749e7" + integrity sha512-eAW/M/NTSSzpCOxfR8Abw6OagdG0MiDAiWHQMQveIsZtoKVYzm0AflSPq/ymqJd56/Su1YPbwy9lM13wgHOFmQ== + dependencies: + prosemirror-state "^1.0.0" + prosemirror-transform "^1.0.0" + +prosemirror-keymap@^1.0.0, prosemirror-keymap@^1.1.2, prosemirror-keymap@^1.2.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/prosemirror-keymap/-/prosemirror-keymap-1.2.1.tgz#3839e7db66cecddae7451f4246e73bdd8489be1d" + integrity sha512-kVK6WGC+83LZwuSJnuCb9PsADQnFZllt94qPP3Rx/vLcOUV65+IbBeH2nS5cFggPyEVJhGkGrgYFRrG250WhHQ== dependencies: prosemirror-state "^1.0.0" w3c-keyname "^2.2.0" @@ -10304,19 +10379,37 @@ prosemirror-markdown@1.9.1: markdown-it "^13.0.1" prosemirror-model "^1.0.0" -prosemirror-model@^1.0.0, prosemirror-model@^1.16.0, prosemirror-model@^1.18.1, prosemirror-model@^1.2.0, prosemirror-model@^1.8.1: - version "1.18.1" - resolved "https://registry.yarnpkg.com/prosemirror-model/-/prosemirror-model-1.18.1.tgz#1d5d6b6de7b983ee67a479dc607165fdef3935bd" - integrity sha512-IxSVBKAEMjD7s3n8cgtwMlxAXZrC7Mlag7zYsAKDndAqnDScvSmp/UdnRTV/B33lTCVU3CCm7dyAn/rVVD0mcw== +prosemirror-markdown@^1.10.1: + version "1.10.1" + resolved "https://registry.yarnpkg.com/prosemirror-markdown/-/prosemirror-markdown-1.10.1.tgz#e20468201cda1916a6182686159398b242bb78ab" + integrity sha512-s7iaTLiX+qO5z8kF2NcMmy2T7mIlxzkS4Sp3vTKSYChPtbMpg6YxFkU0Y06rUg2WtKlvBu7v1bXzlGBkfjUWAA== + dependencies: + markdown-it "^13.0.1" + prosemirror-model "^1.0.0" + +prosemirror-menu@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/prosemirror-menu/-/prosemirror-menu-1.2.1.tgz#94d99a8547b7ba5680c20e9c497ce19846ce3b2c" + integrity sha512-sBirXxVfHalZO4f1ZS63WzewINK4182+7dOmoMeBkqYO8wqMBvBS7wQuwVOHnkMWPEh0+N0LJ856KYUN+vFkmQ== + dependencies: + crelt "^1.0.0" + prosemirror-commands "^1.0.0" + prosemirror-history "^1.0.0" + prosemirror-state "^1.0.0" + +prosemirror-model@^1.0.0, prosemirror-model@^1.16.0, prosemirror-model@^1.18.1, prosemirror-model@^1.19.0, prosemirror-model@^1.8.1: + version "1.19.0" + resolved "https://registry.yarnpkg.com/prosemirror-model/-/prosemirror-model-1.19.0.tgz#d7ad9a65ada0bb12196f64fe0dd4fc392c841c29" + integrity sha512-/CvFGJnwc41EJSfDkQLly1cAJJJmBpZwwUJtwZPTjY2RqZJfM8HVbCreOY/jti8wTRbVyjagcylyGoeJH/g/3w== dependencies: orderedmap "^2.0.0" prosemirror-schema-basic@^1.0.0, prosemirror-schema-basic@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/prosemirror-schema-basic/-/prosemirror-schema-basic-1.2.0.tgz#c33ad74426efae1d41e2260371866f623e8eb10e" - integrity sha512-JMN/ammP94ObOUS6cpIy121r0MEDN9V95mAxFVALwC4bbmhpWXGjBGHTA5LHPPdbqZKyR6Jar1Akv4Z5k9CNLw== + version "1.2.1" + resolved "https://registry.yarnpkg.com/prosemirror-schema-basic/-/prosemirror-schema-basic-1.2.1.tgz#a5a137a6399d1a829873332117d2fe8131d291d0" + integrity sha512-vYBdIHsYKSDIqYmPBC7lnwk9DsKn8PnVqK97pMYP5MLEDFqWIX75JiaJTzndBii4bRuNqhC2UfDOfM3FKhlBHg== dependencies: - prosemirror-model "^1.2.0" + prosemirror-model "^1.19.0" prosemirror-schema-list@^1.0.0, prosemirror-schema-list@^1.2.2: version "1.2.2" @@ -10328,12 +10421,24 @@ prosemirror-schema-list@^1.0.0, prosemirror-schema-list@^1.2.2: prosemirror-transform "^1.0.0" prosemirror-state@^1.0.0, prosemirror-state@^1.2.2, prosemirror-state@^1.3.1, prosemirror-state@^1.4.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/prosemirror-state/-/prosemirror-state-1.4.1.tgz#f6e26c7b6a7e11206176689eb6ebbf91870953e1" - integrity sha512-U/LBDW2gNmVa07sz/D229XigSdDQ5CLFwVB1Vb32MJbAHHhWe/6pOc721faI17tqw4pZ49i1xfY/jEZ9tbIhPg== + version "1.4.2" + resolved "https://registry.yarnpkg.com/prosemirror-state/-/prosemirror-state-1.4.2.tgz#f93bd8a33a4454efab917ba9b738259d828db7e5" + integrity sha512-puuzLD2mz/oTdfgd8msFbe0A42j5eNudKAAPDB0+QJRw8cO1ygjLmhLrg9RvDpf87Dkd6D4t93qdef00KKNacQ== dependencies: prosemirror-model "^1.0.0" prosemirror-transform "^1.0.0" + prosemirror-view "^1.27.0" + +prosemirror-tables@^1.3.0: + version "1.3.2" + resolved "https://registry.yarnpkg.com/prosemirror-tables/-/prosemirror-tables-1.3.2.tgz#ca208c6a55d510af14b652d23e800e00ba6bebd4" + integrity sha512-/9JTeN6s58Zq66HXaxP6uf8PAmc7XXKZFPlOGVtLvxEd6xBP6WtzaJB9wBjiGUzwbdhdMEy7V62yuHqk/3VrnQ== + dependencies: + prosemirror-keymap "^1.1.2" + prosemirror-model "^1.8.1" + prosemirror-state "^1.3.1" + prosemirror-transform "^1.2.1" + prosemirror-view "^1.13.3" prosemirror-test-builder@^1.1.0: version "1.1.0" @@ -10344,17 +10449,27 @@ prosemirror-test-builder@^1.1.0: prosemirror-schema-basic "^1.0.0" prosemirror-schema-list "^1.0.0" +prosemirror-trailing-node@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/prosemirror-trailing-node/-/prosemirror-trailing-node-2.0.3.tgz#213fc0e545a434ff3c37b5218a0de69561bf3892" + integrity sha512-lGrjMrn97KWkjQSW/FjdvnhJmqFACmQIyr6lKYApvHitDnKsCoZz6XzrHB7RZYHni/0NxQmZ01p/2vyK2SkvaA== + dependencies: + "@babel/runtime" "^7.13.10" + "@remirror/core-constants" "^2.0.0" + "@remirror/core-helpers" "^2.0.1" + escape-string-regexp "^4.0.0" + prosemirror-transform@^1.0.0, prosemirror-transform@^1.1.0, prosemirror-transform@^1.2.1, prosemirror-transform@^1.7.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/prosemirror-transform/-/prosemirror-transform-1.7.0.tgz#a8a0768f3ee6418d26ebef435beda9d43c65e472" - integrity sha512-O4T697Cqilw06Zvc3Wm+e237R6eZtJL/xGMliCi+Uo8VL6qHk6afz1qq0zNjT3eZMuYwnP8ZS0+YxX/tfcE9TQ== + version "1.7.1" + resolved "https://registry.yarnpkg.com/prosemirror-transform/-/prosemirror-transform-1.7.1.tgz#b516e818c3add0bdf960f4ca8ccb9d057a3ba21b" + integrity sha512-VteoifAfpt46z0yEt6Fc73A5OID9t/y2QIeR5MgxEwTuitadEunD/V0c9jQW8ziT8pbFM54uTzRLJ/nLuQjMxg== dependencies: prosemirror-model "^1.0.0" -prosemirror-view@^1.0.0, prosemirror-view@^1.1.0, prosemirror-view@^1.13.3, prosemirror-view@^1.28.2: - version "1.28.2" - resolved "https://registry.yarnpkg.com/prosemirror-view/-/prosemirror-view-1.28.2.tgz#e997ef9dc623d01afd170e328fc924e6f4382003" - integrity sha512-uK28mJbu0GI8Oz7Aclt6BKL4g+C59EBShBXDB0Y9Y71H25p4bQgmLQLfDWjsT1J9XOw0bR8QQajZmdK8RvXI9g== +prosemirror-view@^1.0.0, prosemirror-view@^1.1.0, prosemirror-view@^1.13.3, prosemirror-view@^1.27.0, prosemirror-view@^1.28.2: + version "1.30.1" + resolved "https://registry.yarnpkg.com/prosemirror-view/-/prosemirror-view-1.30.1.tgz#7cf0ae8dc8553a02c32961e82eca25079c4d8fc9" + integrity sha512-pZUfr7lICJkEY7XwzldAKrkflZDeIvnbfuu2RIS01N5NwJmR/dfZzDzJRzhb3SM2QtT/bM8b4Nnib8X3MGpAhA== dependencies: prosemirror-model "^1.16.0" prosemirror-state "^1.0.0" @@ -10852,9 +10967,9 @@ robust-predicates@^3.0.0: integrity sha512-ndEIpszUHiG4HtDsQLeIuMvRsDnn8c8rYStabochtUeCvfuvNptb5TUbVD68LRAILPX7p9nqQGh4xJgn3EHS/g== rope-sequence@^1.3.0: - version "1.3.2" - resolved "https://registry.yarnpkg.com/rope-sequence/-/rope-sequence-1.3.2.tgz#a19e02d72991ca71feb6b5f8a91154e48e3c098b" - integrity sha512-ku6MFrwEVSVmXLvy3dYph3LAMNS0890K7fabn+0YIRQ2T96T9F4gkFf0vf0WW0JUraNWwGRtInEpH7yO4tbQZg== + version "1.3.3" + resolved "https://registry.yarnpkg.com/rope-sequence/-/rope-sequence-1.3.3.tgz#3f67fc106288b84b71532b4a5fd9d4881e4457f0" + integrity sha512-85aZYCxweiD5J8yTEbw+E6A27zSnLPNDL0WfPdw3YYodq7WjnTKo0q4dtyQ2gz23iPT8Q9CUyJtAaUNcTxRf5Q== route-recognizer@^0.3.3: version "0.3.4" @@ -11843,6 +11958,11 @@ throttle-debounce@^2.1.0: resolved "https://registry.yarnpkg.com/throttle-debounce/-/throttle-debounce-2.1.0.tgz#257e648f0a56bd9e54fe0f132c4ab8611df4e1d5" integrity sha512-AOvyNahXQuU7NN+VVvOOX+uW6FPaWdAOdRP5HfwYxAfCzXTFKRMoIMk+n+po318+ktcChx+F1Dd91G3YHeMKyg== +throttle-debounce@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/throttle-debounce/-/throttle-debounce-3.0.1.tgz#32f94d84dfa894f786c9a1f290e7a645b6a19abb" + integrity sha512-dTEWWNu6JmeVXY0ZYoPuH5cRIwc0MeGbJwah9KUNYSJwommQpCzTySTpEe8Gs1J23aeWEuAobe4Ag7EHVt/LOg== + through2@^2.0.0: version "2.0.5" resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd" @@ -12109,6 +12229,11 @@ type-fest@^0.8.1: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== +type-fest@^2.0.0: + version "2.19.0" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-2.19.0.tgz#88068015bb33036a598b952e55e9311a60fd3a9b" + integrity sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA== + type-is@~1.6.18: version "1.6.18" resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" |