diff --git a/app/javascript/application.js b/app/javascript/application.js index 49cd9516..8889609f 100644 --- a/app/javascript/application.js +++ b/app/javascript/application.js @@ -40,6 +40,7 @@ import DashboardDropzone from './elements/dashboard_dropzone' import RequiredCheckboxGroup from './elements/required_checkbox_group' import PageContainer from './elements/page_container' import EmailEditor from './elements/email_editor' +import MarkdownEditor from './elements/markdown_editor' import MountOnClick from './elements/mount_on_click' import RemoveOnEvent from './elements/remove_on_event' import ScrollTo from './elements/scroll_to' @@ -131,6 +132,7 @@ safeRegisterElement('check-on-click', CheckOnClick) safeRegisterElement('required-checkbox-group', RequiredCheckboxGroup) safeRegisterElement('page-container', PageContainer) safeRegisterElement('email-editor', EmailEditor) +safeRegisterElement('markdown-editor', MarkdownEditor) safeRegisterElement('mount-on-click', MountOnClick) safeRegisterElement('remove-on-event', RemoveOnEvent) safeRegisterElement('scroll-to', ScrollTo) diff --git a/app/javascript/elements/markdown_editor.js b/app/javascript/elements/markdown_editor.js new file mode 100644 index 00000000..010100e7 --- /dev/null +++ b/app/javascript/elements/markdown_editor.js @@ -0,0 +1,411 @@ +import { target, targetable } from '@github/catalyst/lib/targetable' + +function loadTiptap () { + return Promise.all([ + import(/* webpackChunkName: "markdown-editor" */ '@tiptap/core'), + import(/* webpackChunkName: "markdown-editor" */ '@tiptap/extension-bold'), + import(/* webpackChunkName: "markdown-editor" */ '@tiptap/extension-italic'), + import(/* webpackChunkName: "markdown-editor" */ '@tiptap/extension-paragraph'), + import(/* webpackChunkName: "markdown-editor" */ '@tiptap/extension-text'), + import(/* webpackChunkName: "markdown-editor" */ '@tiptap/extension-hard-break'), + import(/* webpackChunkName: "markdown-editor" */ '@tiptap/extension-document'), + import(/* webpackChunkName: "markdown-editor" */ '@tiptap/extension-link'), + import(/* webpackChunkName: "markdown-editor" */ '@tiptap/extension-underline'), + import(/* webpackChunkName: "markdown-editor" */ '@tiptap/extensions'), + import(/* webpackChunkName: "markdown-editor" */ '@tiptap/markdown'), + import(/* webpackChunkName: "markdown-editor" */ '@tiptap/pm/state'), + import(/* webpackChunkName: "markdown-editor" */ '@tiptap/pm/view') + ]).then(([core, bold, italic, paragraph, text, hardBreak, document, link, underline, extensions, markdown, pmState, pmView]) => ({ + Editor: core.Editor, + Extension: core.Extension, + Bold: bold.default || bold, + Italic: italic.default || italic, + Paragraph: paragraph.default || paragraph, + Text: text.default || text, + HardBreak: hardBreak.default || hardBreak, + Document: document.default || document, + Link: link.default || link, + Underline: underline.default || underline, + UndoRedo: extensions.UndoRedo, + Markdown: markdown.Markdown, + Plugin: pmState.Plugin, + Decoration: pmView.Decoration, + DecorationSet: pmView.DecorationSet + })) +} + +class LinkTooltip { + constructor (tooltip, input, saveButton, removeButton, normalizeUrlFn) { + this.tooltip = tooltip + this.input = input + this.saveButton = saveButton + this.removeButton = removeButton + this.normalizeUrl = normalizeUrlFn + this.targetElement = null + this.clickOutsideTimeout = null + this.closeOnClickOutside = null + this.scrollHandler = null + this.saveHandler = null + this.removeHandler = null + this.keyHandler = null + } + + updatePosition () { + const rect = this.targetElement.getBoundingClientRect() + this.tooltip.style.left = `${rect.left}px` + this.tooltip.style.top = `${rect.bottom + 5}px` + } + + setupClickOutside () { + this.closeOnClickOutside = (e) => { + if (!this.tooltip.contains(e.target)) { + this.hide() + } + } + + this.clickOutsideTimeout = setTimeout(() => { + if (this.closeOnClickOutside) { + document.addEventListener('click', this.closeOnClickOutside) + } + }, 100) + } + + setupScrollTracking () { + this.scrollHandler = () => this.updatePosition() + window.addEventListener('scroll', this.scrollHandler, true) + } + + show ({ url, targetElement, onSave, onRemove }) { + this.hide() + + this.input.value = url || '' + this.removeButton.classList.toggle('hidden', !url) + this.targetElement = targetElement + + this.updatePosition() + + this.tooltip.classList.remove('hidden') + this.input.focus() + this.input.select() + + const save = () => { + const inputUrl = this.input.value.trim() + + this.hide() + + if (inputUrl) { + onSave(this.normalizeUrl(inputUrl)) + } + } + + this.saveHandler = () => save() + this.removeHandler = () => { + if (onRemove) onRemove() + this.hide() + } + this.keyHandler = (e) => { + if (e.key === 'Enter') { + e.preventDefault() + save() + } else if (e.key === 'Escape') { + e.preventDefault() + this.hide() + } + } + + this.saveButton.addEventListener('click', this.saveHandler, { once: true }) + this.removeButton.addEventListener('click', this.removeHandler, { once: true }) + this.input.addEventListener('keydown', this.keyHandler) + + this.setupScrollTracking() + this.setupClickOutside() + } + + hide () { + if (this.clickOutsideTimeout) { + clearTimeout(this.clickOutsideTimeout) + this.clickOutsideTimeout = null + } + + if (this.scrollHandler) { + window.removeEventListener('scroll', this.scrollHandler, true) + this.scrollHandler = null + } + + if (this.closeOnClickOutside) { + document.removeEventListener('click', this.closeOnClickOutside) + this.closeOnClickOutside = null + } + + if (this.saveHandler) { + this.saveButton.removeEventListener('click', this.saveHandler) + this.saveHandler = null + } + + if (this.removeHandler) { + this.removeButton.removeEventListener('click', this.removeHandler) + this.removeHandler = null + } + + if (this.keyHandler) { + this.input.removeEventListener('keydown', this.keyHandler) + this.keyHandler = null + } + + this.tooltip?.classList.add('hidden') + this.targetElement = null + } +} + +export default targetable(class extends HTMLElement { + static [target.static] = [ + 'textarea', + 'editorElement', + 'variableButton', + 'variableDropdown', + 'boldButton', + 'italicButton', + 'underlineButton', + 'linkButton', + 'undoButton', + 'redoButton', + 'linkTooltipElement', + 'linkInput', + 'linkSaveButton', + 'linkRemoveButton' + ] + + async connectedCallback () { + if (!this.textarea || !this.editorElement) return + + this.textarea.style.display = 'none' + this.adjustShortcutsForPlatform() + + this.linkTooltip = new LinkTooltip( + this.linkTooltipElement, + this.linkInput, + this.linkSaveButton, + this.linkRemoveButton, + (url) => this.normalizeUrl(url) + ) + + const { Editor, Extension, Bold, Italic, Paragraph, Text, HardBreak, UndoRedo, Document, Link, Underline, Markdown, Plugin, Decoration, DecorationSet } = await loadTiptap() + + const buildDecorations = (doc) => { + const decorations = [] + const regex = /\{[a-zA-Z0-9_.-]+\}/g + + doc.descendants((node, pos) => { + if (!node.isText) return + + let match + + while ((match = regex.exec(node.text)) !== null) { + decorations.push( + Decoration.inline(pos + match.index, pos + match.index + match[0].length, { + class: 'bg-amber-100 py-0.5 px-1 rounded' + }) + ) + } + }) + + return DecorationSet.create(doc, decorations) + } + + const VariableHighlight = Extension.create({ + name: 'variableHighlight', + addProseMirrorPlugins () { + return [new Plugin({ + state: { + init (_, { doc }) { + return buildDecorations(doc) + }, + apply (tr, oldSet) { + return tr.docChanged ? buildDecorations(tr.doc) : oldSet + } + }, + props: { + decorations (state) { + return this.getState(state) + } + } + })] + } + }) + + this.editor = new Editor({ + element: this.editorElement, + extensions: [ + Markdown, + Document, + Paragraph, + Text, + Bold, + Italic, + HardBreak, + UndoRedo, + Link.extend({ + inclusive: false + }).configure({ + openOnClick: false, + HTMLAttributes: { + class: 'link', + style: 'color: #2563eb; text-decoration: underline; cursor: pointer;' + } + }), + Underline, + VariableHighlight + ], + content: this.textarea.value || '', + contentType: 'markdown', + editorProps: { + attributes: { + class: 'prose prose-sm max-w-none p-3 outline-none focus:outline-none min-h-[120px]' + }, + handleClick: (view, pos, event) => { + const clickedPos = view.posAtCoords({ left: event.clientX, top: event.clientY }) + + if (!clickedPos) return false + + const linkMark = view.state.doc.resolve(clickedPos.pos).marks().find(m => m.type.name === 'link') + + if (linkMark) { + event.preventDefault() + + this.editor.chain().setTextSelection(clickedPos.pos).extendMarkRange('link').run() + this.toggleLink() + + return true + } + + return false + } + }, + onUpdate: ({ editor }) => { + this.textarea.value = editor.getMarkdown() + this.textarea.dispatchEvent(new Event('input', { bubbles: true })) + }, + onSelectionUpdate: () => { + this.updateToolbarState() + } + }) + + this.setupToolbar() + + if (this.variableButton) { + this.variableButton.addEventListener('click', () => { + this.variableDropdown.classList.toggle('hidden') + }) + + this.variableDropdown.addEventListener('click', (e) => { + const variable = e.target.closest('[data-variable]')?.dataset.variable + + if (variable) { + this.insertVariable(variable) + this.variableDropdown.classList.add('hidden') + } + }) + + document.addEventListener('click', (e) => { + if (!this.variableButton.contains(e.target) && !this.variableDropdown.contains(e.target)) { + this.variableDropdown.classList.add('hidden') + } + }) + } + } + + adjustShortcutsForPlatform () { + if ((navigator.userAgentData?.platform || navigator.platform)?.toLowerCase()?.includes('mac')) { + this.querySelectorAll('.tooltip[data-tip]').forEach(tooltip => { + const tip = tooltip.getAttribute('data-tip') + + if (tip && tip.includes('Ctrl')) { + tooltip.setAttribute('data-tip', tip.replace(/Ctrl/g, '⌘')) + } + }) + } + } + + setupToolbar () { + this.boldButton?.addEventListener('click', (e) => { + e.preventDefault() + this.editor.chain().focus().toggleBold().run() + this.updateToolbarState() + }) + + this.italicButton?.addEventListener('click', (e) => { + e.preventDefault() + this.editor.chain().focus().toggleItalic().run() + this.updateToolbarState() + }) + + this.underlineButton?.addEventListener('click', (e) => { + e.preventDefault() + this.editor.chain().focus().toggleUnderline().run() + this.updateToolbarState() + }) + + this.linkButton?.addEventListener('click', (e) => { + e.preventDefault() + this.toggleLink() + }) + + this.undoButton?.addEventListener('click', (e) => { + e.preventDefault() + this.editor.chain().focus().undo().run() + }) + + this.redoButton?.addEventListener('click', (e) => { + e.preventDefault() + this.editor.chain().focus().redo().run() + }) + } + + updateToolbarState () { + this.boldButton?.classList.toggle('bg-base-200', this.editor.isActive('bold')) + this.italicButton?.classList.toggle('bg-base-200', this.editor.isActive('italic')) + this.underlineButton?.classList.toggle('bg-base-200', this.editor.isActive('underline')) + this.linkButton?.classList.toggle('bg-base-200', this.editor.isActive('link')) + } + + normalizeUrl (url) { + if (!url) return url + if (/^https?:\/\//i.test(url)) return url + if (/^mailto:/i.test(url)) return url + + return `https://${url}` + } + + toggleLink () { + const { from } = this.editor.state.selection + + const rect = this.editor.view.coordsAtPos(from) + const fakeElement = { getBoundingClientRect: () => rect } + + const previousUrl = this.editor.getAttributes('link').href + + this.linkTooltip.show({ + url: previousUrl, + targetElement: fakeElement, + onSave: (url) => { + this.editor.chain().focus().extendMarkRange('link').setLink({ href: url }).run() + }, + onRemove: previousUrl + ? () => { this.editor.chain().focus().extendMarkRange('link').unsetLink().run() } + : null + }) + } + + insertVariable (variable) { + this.editor.chain().focus().insertContent(`{${variable}}`).run() + } + + disconnectedCallback () { + this.linkTooltip.hide() + + if (this.editor) { + this.editor.destroy() + this.editor = null + } + } +}) diff --git a/app/views/icons/_arrow_back_up.html.erb b/app/views/icons/_arrow_back_up.html.erb new file mode 100644 index 00000000..0b92f52e --- /dev/null +++ b/app/views/icons/_arrow_back_up.html.erb @@ -0,0 +1,5 @@ + diff --git a/app/views/icons/_arrow_forward_up.html.erb b/app/views/icons/_arrow_forward_up.html.erb new file mode 100644 index 00000000..3e43213e --- /dev/null +++ b/app/views/icons/_arrow_forward_up.html.erb @@ -0,0 +1,5 @@ + diff --git a/app/views/icons/_bold.html.erb b/app/views/icons/_bold.html.erb new file mode 100644 index 00000000..da40ebeb --- /dev/null +++ b/app/views/icons/_bold.html.erb @@ -0,0 +1,5 @@ + diff --git a/app/views/icons/_italic.html.erb b/app/views/icons/_italic.html.erb new file mode 100644 index 00000000..0ca1e555 --- /dev/null +++ b/app/views/icons/_italic.html.erb @@ -0,0 +1,6 @@ + diff --git a/app/views/icons/_underline.html.erb b/app/views/icons/_underline.html.erb new file mode 100644 index 00000000..21f16f41 --- /dev/null +++ b/app/views/icons/_underline.html.erb @@ -0,0 +1,5 @@ + diff --git a/app/views/personalization_settings/_email_body_field.html.erb b/app/views/personalization_settings/_email_body_field.html.erb index 264a4240..d58d4bd8 100644 --- a/app/views/personalization_settings/_email_body_field.html.erb +++ b/app/views/personalization_settings/_email_body_field.html.erb @@ -1,11 +1,5 @@
#{parse_inline(content)}
" + end.join + + html.presence || '' + end + + # rubocop:disable Metrics + def parse_inline(text) + context = [] + out = '' + last = 0 + + tag = lambda do |t| + desc = TAGS[t[1] || ''] + return t unless desc + return desc[0] unless desc[1] + + is_end = context.last == t + is_end ? context.pop : context.push(t) + desc[is_end ? 1 : 0] end + + flush = lambda do + str = '' + str += tag.call(context.last) while context.any? + str + end + + while last <= text.length && (m = INLINE_TOKENIZER.match(text, last)) + prev = text[last...m.begin(0)] + last = m.end(0) + chunk = m[0] + + if m[4] + chunk = "#{ERB::Util.html_escape(m[4])}"
+ elsif m[2]
+ out = out.sub(/\A(.*)/m, "\\1")
+ out = out.gsub('', '[')
+ chunk = "#{flush.call}"
+ elsif m[1]
+ chunk = ''
+ elsif m[5]
+ chunk = if m[5] == '***'
+ if context.include?('*') && context.include?('**')
+ tag.call('*') + tag.call('**')
+ else
+ tag.call('**') + tag.call('*')
+ end
+ else
+ tag.call(m[5])
+ end
+ end
+
+ out += prev.to_s + chunk
+ end
+
+ (out + text[last..].to_s + flush.call).gsub('', '[')
end
+ # rubocop:enable Metrics
end
diff --git a/package.json b/package.json
index 44e3284f..05579771 100644
--- a/package.json
+++ b/package.json
@@ -14,6 +14,18 @@
"@hotwired/turbo-rails": "^7.3.0",
"@specious/htmlflow": "^1.1.0",
"@tabler/icons-vue": "^2.47.0",
+ "@tiptap/core": "^3.19.0",
+ "@tiptap/extension-bold": "^3.19.0",
+ "@tiptap/extension-document": "^3.19.0",
+ "@tiptap/extension-hard-break": "^3.19.0",
+ "@tiptap/extension-italic": "^3.19.0",
+ "@tiptap/extension-link": "^3.19.0",
+ "@tiptap/extension-paragraph": "^3.19.0",
+ "@tiptap/extension-text": "^3.19.0",
+ "@tiptap/extension-underline": "^3.19.0",
+ "@tiptap/extensions": "^3.19.0",
+ "@tiptap/markdown": "^3.19.0",
+ "@tiptap/pm": "^3.19.0",
"autocompleter": "^9.1.0",
"autoprefixer": "^10.4.14",
"babel-loader": "9.1.2",
diff --git a/yarn.lock b/yarn.lock
index 7973096e..4a8f5021 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1743,6 +1743,11 @@
resolved "https://registry.yarnpkg.com/@rails/actioncable/-/actioncable-7.0.4.tgz#70a3ca56809f7aaabb80af2f9c01ae51e1a8ed41"
integrity sha512-tz4oM+Zn9CYsvtyicsa/AwzKZKL+ITHWkhiu7x+xF77clh2b4Rm+s6xnOgY/sGDWoFWZmtKsE95hxBPkgQQNnQ==
+"@remirror/core-constants@3.0.0":
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/@remirror/core-constants/-/core-constants-3.0.0.tgz#96fdb89d25c62e7b6a5d08caf0ce5114370e3b8f"
+ integrity sha512-42aWfPrimMfDKDi4YegyS7x+/0tlzaqwPQCULLanv3DMIlu96KTJR0fM5isWX2UViOqlGnX6YFgqWepcX+XMNg==
+
"@sinclair/typebox@^0.25.16":
version "0.25.24"
resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.25.24.tgz#8c7688559979f7079aacaf31aa881c3aa410b718"
@@ -1767,6 +1772,89 @@
resolved "https://registry.yarnpkg.com/@tabler/icons/-/icons-2.47.0.tgz#c41c680d1947e3ab2d60af3febc4132287c60596"
integrity sha512-4w5evLh+7FUUiA1GucvGj2ReX2TvOjEr4ejXdwL/bsjoSkof6r1gQmzqI+VHrE2CpJpB3al7bCTulOkFa/RcyA==
+"@tiptap/core@^3.19.0":
+ version "3.19.0"
+ resolved "https://registry.yarnpkg.com/@tiptap/core/-/core-3.19.0.tgz#dca483b50e1b8a596f695aecde387a79fe7da717"
+ integrity sha512-bpqELwPW+DG8gWiD8iiFtSl4vIBooG5uVJod92Qxn3rA9nFatyXRr4kNbMJmOZ66ezUvmCjXVe/5/G4i5cyzKA==
+
+"@tiptap/extension-bold@^3.19.0":
+ version "3.19.0"
+ resolved "https://registry.yarnpkg.com/@tiptap/extension-bold/-/extension-bold-3.19.0.tgz#ef0ddfd9b242ef9c25e3348aef9bf2dc681cdc19"
+ integrity sha512-UZgb1d0XK4J/JRIZ7jW+s4S6KjuEDT2z1PPM6ugcgofgJkWQvRZelCPbmtSFd3kwsD+zr9UPVgTh9YIuGQ8t+Q==
+
+"@tiptap/extension-document@^3.19.0":
+ version "3.19.0"
+ resolved "https://registry.yarnpkg.com/@tiptap/extension-document/-/extension-document-3.19.0.tgz#dfa6889cff748d489e0bc1028918bf4571372ba5"
+ integrity sha512-AOf0kHKSFO0ymjVgYSYDncRXTITdTcrj1tqxVazrmO60KNl1Rc2dAggDvIVTEBy5NvceF0scc7q3sE/5ZtVV7A==
+
+"@tiptap/extension-hard-break@^3.19.0":
+ version "3.19.0"
+ resolved "https://registry.yarnpkg.com/@tiptap/extension-hard-break/-/extension-hard-break-3.19.0.tgz#7120524cec9ed4b957963693cb4c57cbecbaecf8"
+ integrity sha512-lAmQraYhPS5hafvCl74xDB5+bLuNwBKIEsVoim35I0sDJj5nTrfhaZgMJ91VamMvT+6FF5f1dvBlxBxAWa8jew==
+
+"@tiptap/extension-italic@^3.19.0":
+ version "3.19.0"
+ resolved "https://registry.yarnpkg.com/@tiptap/extension-italic/-/extension-italic-3.19.0.tgz#af2a9c095ec846e379041f3e17e1dd101a5a4bf8"
+ integrity sha512-6GffxOnS/tWyCbDkirWNZITiXRta9wrCmrfa4rh+v32wfaOL1RRQNyqo9qN6Wjyl1R42Js+yXTzTTzZsOaLMYA==
+
+"@tiptap/extension-link@^3.19.0":
+ version "3.19.0"
+ resolved "https://registry.yarnpkg.com/@tiptap/extension-link/-/extension-link-3.19.0.tgz#e8e656735bda6ca1d4b6577821e06274ab0ff6c8"
+ integrity sha512-HEGDJnnCPfr7KWu7Dsq+eRRe/mBCsv6DuI+7fhOCLDJjjKzNgrX2abbo/zG3D/4lCVFaVb+qawgJubgqXR/Smw==
+ dependencies:
+ linkifyjs "^4.3.2"
+
+"@tiptap/extension-paragraph@^3.19.0":
+ version "3.19.0"
+ resolved "https://registry.yarnpkg.com/@tiptap/extension-paragraph/-/extension-paragraph-3.19.0.tgz#91adde189aabf13a2bfbb2d961833d3bc2bc055f"
+ integrity sha512-xWa6gj82l5+AzdYyrSk9P4ynySaDzg/SlR1FarXE5yPXibYzpS95IWaVR0m2Qaz7Rrk+IiYOTGxGRxcHLOelNg==
+
+"@tiptap/extension-text@^3.19.0":
+ version "3.19.0"
+ resolved "https://registry.yarnpkg.com/@tiptap/extension-text/-/extension-text-3.19.0.tgz#353278c97bd8f5bdc29f06942fbd1e856bdb5b18"
+ integrity sha512-K95+SnbZy0h6hNFtfy23n8t/nOcTFEf69In9TSFVVmwn/Nwlke+IfiESAkqbt1/7sKJeegRXYO7WzFEmFl9Q/g==
+
+"@tiptap/extension-underline@^3.19.0":
+ version "3.19.0"
+ resolved "https://registry.yarnpkg.com/@tiptap/extension-underline/-/extension-underline-3.19.0.tgz#bbc81d085725981d256127ab416f91d0802ec2a4"
+ integrity sha512-800MGEWfG49j10wQzAFiW/ele1HT04MamcL8iyuPNu7ZbjbGN2yknvdrJlRy7hZlzIrVkZMr/1tz62KN33VHIw==
+
+"@tiptap/extensions@^3.19.0":
+ version "3.19.0"
+ resolved "https://registry.yarnpkg.com/@tiptap/extensions/-/extensions-3.19.0.tgz#5747c0ebf460b9669e8b4362561872448f66abfe"
+ integrity sha512-ZmGUhLbMWaGqnJh2Bry+6V4M6gMpUDYo4D1xNux5Gng/E/eYtc+PMxMZ/6F7tNTAuujLBOQKj6D+4SsSm457jw==
+
+"@tiptap/markdown@^3.19.0":
+ version "3.19.0"
+ resolved "https://registry.yarnpkg.com/@tiptap/markdown/-/markdown-3.19.0.tgz#dd05451b40f2a553cab0fdbb4a8714a2b2430b5c"
+ integrity sha512-Pnfacq2FHky1rqwmGwEmUJxuZu8VZ8XjaJIqsQC34S3CQWiOU+PukC9In2odzcooiVncLWT9s97jKuYpbmF1tQ==
+ dependencies:
+ marked "^17.0.1"
+
+"@tiptap/pm@^3.19.0":
+ version "3.19.0"
+ resolved "https://registry.yarnpkg.com/@tiptap/pm/-/pm-3.19.0.tgz#5cb499c7b2603ec6550d0c7a70b924f27fdb7692"
+ integrity sha512-789zcnM4a8OWzvbD2DL31d0wbSm9BVeO/R7PLQwLIGysDI3qzrcclyZ8yhqOEVuvPitRRwYLq+mY14jz7kY4cw==
+ dependencies:
+ prosemirror-changeset "^2.3.0"
+ prosemirror-collab "^1.3.1"
+ prosemirror-commands "^1.6.2"
+ prosemirror-dropcursor "^1.8.1"
+ prosemirror-gapcursor "^1.3.2"
+ prosemirror-history "^1.4.1"
+ prosemirror-inputrules "^1.4.0"
+ prosemirror-keymap "^1.2.2"
+ prosemirror-markdown "^1.13.1"
+ prosemirror-menu "^1.2.4"
+ prosemirror-model "^1.24.1"
+ prosemirror-schema-basic "^1.2.3"
+ prosemirror-schema-list "^1.5.0"
+ prosemirror-state "^1.4.3"
+ prosemirror-tables "^1.6.4"
+ prosemirror-trailing-node "^3.0.0"
+ prosemirror-transform "^1.10.2"
+ prosemirror-view "^1.38.1"
+
"@trysound/sax@0.2.0":
version "0.2.0"
resolved "https://registry.yarnpkg.com/@trysound/sax/-/sax-0.2.0.tgz#cccaab758af56761eb7bf37af6f03f326dd798ad"
@@ -1909,6 +1997,24 @@
resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==
+"@types/linkify-it@^5":
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/@types/linkify-it/-/linkify-it-5.0.0.tgz#21413001973106cda1c3a9b91eedd4ccd5469d76"
+ integrity sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==
+
+"@types/markdown-it@^14.0.0":
+ version "14.1.2"
+ resolved "https://registry.yarnpkg.com/@types/markdown-it/-/markdown-it-14.1.2.tgz#57f2532a0800067d9b934f3521429a2e8bfb4c61"
+ integrity sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==
+ dependencies:
+ "@types/linkify-it" "^5"
+ "@types/mdurl" "^2"
+
+"@types/mdurl@^2":
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/@types/mdurl/-/mdurl-2.0.0.tgz#d43878b5b20222682163ae6f897b20447233bdfd"
+ integrity sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==
+
"@types/mime@*":
version "3.0.1"
resolved "https://registry.yarnpkg.com/@types/mime/-/mime-3.0.1.tgz#5f8f2bca0a5863cb69bc0b0acd88c96cb1d4ae10"
@@ -2986,7 +3092,7 @@ cosmiconfig@^8.1.3:
parse-json "^5.0.0"
path-type "^4.0.0"
-crelt@^1.0.5, crelt@^1.0.6:
+crelt@^1.0.0, crelt@^1.0.5, crelt@^1.0.6:
version "1.0.6"
resolved "https://registry.yarnpkg.com/crelt/-/crelt-1.0.6.tgz#7cc898ea74e190fb6ef9dae57f8f81cf7302df72"
integrity sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==
@@ -4780,6 +4886,18 @@ lines-and-columns@^1.1.6:
resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632"
integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==
+linkify-it@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/linkify-it/-/linkify-it-5.0.0.tgz#9ef238bfa6dc70bd8e7f9572b52d369af569b421"
+ integrity sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==
+ dependencies:
+ uc.micro "^2.0.0"
+
+linkifyjs@^4.3.2:
+ version "4.3.2"
+ resolved "https://registry.yarnpkg.com/linkifyjs/-/linkifyjs-4.3.2.tgz#d97eb45419aabf97ceb4b05a7adeb7b8c8ade2b1"
+ integrity sha512-NT1CJtq3hHIreOianA8aSXn6Cw0JzYOuDQbOrSPe7gqFnCpKP++MQe3ODgO3oh2GJFORkAAdqredOa60z63GbA==
+
loader-runner@^4.3.1:
version "4.3.1"
resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.3.1.tgz#6c76ed29b0ccce9af379208299f07f876de737e3"
@@ -4867,6 +4985,23 @@ make-dir@^3.0.2:
dependencies:
semver "^6.0.0"
+markdown-it@^14.0.0:
+ version "14.1.0"
+ resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-14.1.0.tgz#3c3c5992883c633db4714ccb4d7b5935d98b7d45"
+ integrity sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==
+ dependencies:
+ argparse "^2.0.1"
+ entities "^4.4.0"
+ linkify-it "^5.0.0"
+ mdurl "^2.0.0"
+ punycode.js "^2.3.1"
+ uc.micro "^2.1.0"
+
+marked@^17.0.1:
+ version "17.0.1"
+ resolved "https://registry.yarnpkg.com/marked/-/marked-17.0.1.tgz#9db34197ac145e5929572ee49ef701e37ee9b2e6"
+ integrity sha512-boeBdiS0ghpWcSwoNm/jJBwdpFaMnZWRzjA6SkUMYb40SVaN1x7mmfGKp0jvexGcx+7y2La5zRZsYFZI6Qpypg==
+
math-intrinsics@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9"
@@ -4897,6 +5032,11 @@ mdn-data@2.0.30:
resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.30.tgz#ce4df6f80af6cfbe218ecd5c552ba13c4dfa08cc"
integrity sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==
+mdurl@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-2.0.0.tgz#80676ec0433025dd3e17ee983d0fe8de5a2237e0"
+ integrity sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==
+
media-typer@0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
@@ -5218,6 +5358,11 @@ optionator@^0.9.1:
type-check "^0.4.0"
word-wrap "^1.2.3"
+orderedmap@^2.0.0:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/orderedmap/-/orderedmap-2.1.1.tgz#61481269c44031c449915497bf5a4ad273c512d2"
+ integrity sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g==
+
p-limit@^2.2.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1"
@@ -5739,6 +5884,160 @@ proper-lockfile@^4.1.2:
retry "^0.12.0"
signal-exit "^3.0.2"
+prosemirror-changeset@^2.3.0:
+ version "2.3.1"
+ resolved "https://registry.yarnpkg.com/prosemirror-changeset/-/prosemirror-changeset-2.3.1.tgz#eee3299cfabc7a027694e9abdc4e85505e9dd5e7"
+ integrity sha512-j0kORIBm8ayJNl3zQvD1TTPHJX3g042et6y/KQhZhnPrruO8exkTgG8X+NRpj7kIyMMEx74Xb3DyMIBtO0IKkQ==
+ dependencies:
+ prosemirror-transform "^1.0.0"
+
+prosemirror-collab@^1.3.1:
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/prosemirror-collab/-/prosemirror-collab-1.3.1.tgz#0e8c91e76e009b53457eb3b3051fb68dad029a33"
+ integrity sha512-4SnynYR9TTYaQVXd/ieUvsVV4PDMBzrq2xPUWutHivDuOshZXqQ5rGbZM84HEaXKbLdItse7weMGOUdDVcLKEQ==
+ dependencies:
+ prosemirror-state "^1.0.0"
+
+prosemirror-commands@^1.0.0, prosemirror-commands@^1.6.2:
+ version "1.7.1"
+ resolved "https://registry.yarnpkg.com/prosemirror-commands/-/prosemirror-commands-1.7.1.tgz#d101fef85618b1be53d5b99ea17bee5600781b38"
+ integrity sha512-rT7qZnQtx5c0/y/KlYaGvtG411S97UaL6gdp6RIZ23DLHanMYLyfGBV5DtSnZdthQql7W+lEVbpSfwtO8T+L2w==
+ dependencies:
+ prosemirror-model "^1.0.0"
+ prosemirror-state "^1.0.0"
+ prosemirror-transform "^1.10.2"
+
+prosemirror-dropcursor@^1.8.1:
+ version "1.8.2"
+ resolved "https://registry.yarnpkg.com/prosemirror-dropcursor/-/prosemirror-dropcursor-1.8.2.tgz#2ed30c4796109ddeb1cf7282372b3850528b7228"
+ integrity sha512-CCk6Gyx9+Tt2sbYk5NK0nB1ukHi2ryaRgadV/LvyNuO3ena1payM2z6Cg0vO1ebK8cxbzo41ku2DE5Axj1Zuiw==
+ dependencies:
+ prosemirror-state "^1.0.0"
+ prosemirror-transform "^1.1.0"
+ prosemirror-view "^1.1.0"
+
+prosemirror-gapcursor@^1.3.2:
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/prosemirror-gapcursor/-/prosemirror-gapcursor-1.4.0.tgz#e1144a83b79db7ed0ec32cd0e915a0364220af43"
+ integrity sha512-z00qvurSdCEWUIulij/isHaqu4uLS8r/Fi61IbjdIPJEonQgggbJsLnstW7Lgdk4zQ68/yr6B6bf7sJXowIgdQ==
+ dependencies:
+ prosemirror-keymap "^1.0.0"
+ prosemirror-model "^1.0.0"
+ prosemirror-state "^1.0.0"
+ prosemirror-view "^1.0.0"
+
+prosemirror-history@^1.0.0, prosemirror-history@^1.4.1:
+ version "1.5.0"
+ resolved "https://registry.yarnpkg.com/prosemirror-history/-/prosemirror-history-1.5.0.tgz#ee21fc5de85a1473e3e3752015ffd6d649a06859"
+ integrity sha512-zlzTiH01eKA55UAf1MEjtssJeHnGxO0j4K4Dpx+gnmX9n+SHNlDqI2oO1Kv1iPN5B1dm5fsljCfqKF9nFL6HRg==
+ dependencies:
+ prosemirror-state "^1.2.2"
+ prosemirror-transform "^1.0.0"
+ prosemirror-view "^1.31.0"
+ rope-sequence "^1.3.0"
+
+prosemirror-inputrules@^1.4.0:
+ version "1.5.1"
+ resolved "https://registry.yarnpkg.com/prosemirror-inputrules/-/prosemirror-inputrules-1.5.1.tgz#d2e935f6086e3801486b09222638f61dae89a570"
+ integrity sha512-7wj4uMjKaXWAQ1CDgxNzNtR9AlsuwzHfdFH1ygEHA2KHF2DOEaXl1CJfNPAKCg9qNEh4rum975QLaCiQPyY6Fw==
+ dependencies:
+ prosemirror-state "^1.0.0"
+ prosemirror-transform "^1.0.0"
+
+prosemirror-keymap@^1.0.0, prosemirror-keymap@^1.2.2, prosemirror-keymap@^1.2.3:
+ version "1.2.3"
+ resolved "https://registry.yarnpkg.com/prosemirror-keymap/-/prosemirror-keymap-1.2.3.tgz#c0f6ab95f75c0b82c97e44eb6aaf29cbfc150472"
+ integrity sha512-4HucRlpiLd1IPQQXNqeo81BGtkY8Ai5smHhKW9jjPKRc2wQIxksg7Hl1tTI2IfT2B/LgX6bfYvXxEpJl7aKYKw==
+ dependencies:
+ prosemirror-state "^1.0.0"
+ w3c-keyname "^2.2.0"
+
+prosemirror-markdown@^1.13.1:
+ version "1.13.4"
+ resolved "https://registry.yarnpkg.com/prosemirror-markdown/-/prosemirror-markdown-1.13.4.tgz#4620e6a0580cd52b5fc8e352c7e04830cd4b3048"
+ integrity sha512-D98dm4cQ3Hs6EmjK500TdAOew4Z03EV71ajEFiWra3Upr7diytJsjF4mPV2dW+eK5uNectiRj0xFxYI9NLXDbw==
+ dependencies:
+ "@types/markdown-it" "^14.0.0"
+ markdown-it "^14.0.0"
+ prosemirror-model "^1.25.0"
+
+prosemirror-menu@^1.2.4:
+ version "1.2.5"
+ resolved "https://registry.yarnpkg.com/prosemirror-menu/-/prosemirror-menu-1.2.5.tgz#dea00e7b623cea89f4d76963bee22d2ac2343250"
+ integrity sha512-qwXzynnpBIeg1D7BAtjOusR+81xCp53j7iWu/IargiRZqRjGIlQuu1f3jFi+ehrHhWMLoyOQTSRx/IWZJqOYtQ==
+ 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.20.0, prosemirror-model@^1.21.0, prosemirror-model@^1.24.1, prosemirror-model@^1.25.0, prosemirror-model@^1.25.4:
+ version "1.25.4"
+ resolved "https://registry.yarnpkg.com/prosemirror-model/-/prosemirror-model-1.25.4.tgz#8ebfbe29ecbee9e5e2e4048c4fe8e363fcd56e7c"
+ integrity sha512-PIM7E43PBxKce8OQeezAs9j4TP+5yDpZVbuurd1h5phUxEKIu+G2a+EUZzIC5nS1mJktDJWzbqS23n1tsAf5QA==
+ dependencies:
+ orderedmap "^2.0.0"
+
+prosemirror-schema-basic@^1.2.3:
+ version "1.2.4"
+ resolved "https://registry.yarnpkg.com/prosemirror-schema-basic/-/prosemirror-schema-basic-1.2.4.tgz#389ce1ec09b8a30ea9bbb92c58569cb690c2d695"
+ integrity sha512-ELxP4TlX3yr2v5rM7Sb70SqStq5NvI15c0j9j/gjsrO5vaw+fnnpovCLEGIcpeGfifkuqJwl4fon6b+KdrODYQ==
+ dependencies:
+ prosemirror-model "^1.25.0"
+
+prosemirror-schema-list@^1.5.0:
+ version "1.5.1"
+ resolved "https://registry.yarnpkg.com/prosemirror-schema-list/-/prosemirror-schema-list-1.5.1.tgz#5869c8f749e8745c394548bb11820b0feb1e32f5"
+ integrity sha512-927lFx/uwyQaGwJxLWCZRkjXG0p48KpMj6ueoYiu4JX05GGuGcgzAy62dfiV8eFZftgyBUvLx76RsMe20fJl+Q==
+ dependencies:
+ prosemirror-model "^1.0.0"
+ prosemirror-state "^1.0.0"
+ prosemirror-transform "^1.7.3"
+
+prosemirror-state@^1.0.0, prosemirror-state@^1.2.2, prosemirror-state@^1.4.3, prosemirror-state@^1.4.4:
+ version "1.4.4"
+ resolved "https://registry.yarnpkg.com/prosemirror-state/-/prosemirror-state-1.4.4.tgz#72b5e926f9e92dcee12b62a05fcc8a2de3bf5b39"
+ integrity sha512-6jiYHH2CIGbCfnxdHbXZ12gySFY/fz/ulZE333G6bPqIZ4F+TXo9ifiR86nAHpWnfoNjOb3o5ESi7J8Uz1jXHw==
+ dependencies:
+ prosemirror-model "^1.0.0"
+ prosemirror-transform "^1.0.0"
+ prosemirror-view "^1.27.0"
+
+prosemirror-tables@^1.6.4:
+ version "1.8.5"
+ resolved "https://registry.yarnpkg.com/prosemirror-tables/-/prosemirror-tables-1.8.5.tgz#104427012e5a5da1d2a38c122efee8d66bdd5104"
+ integrity sha512-V/0cDCsHKHe/tfWkeCmthNUcEp1IVO3p6vwN8XtwE9PZQLAZJigbw3QoraAdfJPir4NKJtNvOB8oYGKRl+t0Dw==
+ dependencies:
+ prosemirror-keymap "^1.2.3"
+ prosemirror-model "^1.25.4"
+ prosemirror-state "^1.4.4"
+ prosemirror-transform "^1.10.5"
+ prosemirror-view "^1.41.4"
+
+prosemirror-trailing-node@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/prosemirror-trailing-node/-/prosemirror-trailing-node-3.0.0.tgz#5bc223d4fc1e8d9145e4079ec77a932b54e19e04"
+ integrity sha512-xiun5/3q0w5eRnGYfNlW1uU9W6x5MoFKWwq/0TIRgt09lv7Hcser2QYV8t4muXbEr+Fwo0geYn79Xs4GKywrRQ==
+ dependencies:
+ "@remirror/core-constants" "3.0.0"
+ escape-string-regexp "^4.0.0"
+
+prosemirror-transform@^1.0.0, prosemirror-transform@^1.1.0, prosemirror-transform@^1.10.2, prosemirror-transform@^1.10.5, prosemirror-transform@^1.7.3:
+ version "1.11.0"
+ resolved "https://registry.yarnpkg.com/prosemirror-transform/-/prosemirror-transform-1.11.0.tgz#f5c5050354423dc83c6b083f6f1959ec86a3f9ba"
+ integrity sha512-4I7Ce4KpygXb9bkiPS3hTEk4dSHorfRw8uI0pE8IhxlK2GXsqv5tIA7JUSxtSu7u8APVOTtbUBxTmnHIxVkIJw==
+ dependencies:
+ prosemirror-model "^1.21.0"
+
+prosemirror-view@^1.0.0, prosemirror-view@^1.1.0, prosemirror-view@^1.27.0, prosemirror-view@^1.31.0, prosemirror-view@^1.38.1, prosemirror-view@^1.41.4:
+ version "1.41.5"
+ resolved "https://registry.yarnpkg.com/prosemirror-view/-/prosemirror-view-1.41.5.tgz#3e152d14af633f2f5a73aba24e6130c63f643b2b"
+ integrity sha512-UDQbIPnDrjE8tqUBbPmCOZgtd75htE6W3r0JCmY9bL6W1iemDM37MZEKC49d+tdQ0v/CKx4gjxLoLsfkD2NiZA==
+ dependencies:
+ prosemirror-model "^1.20.0"
+ prosemirror-state "^1.0.0"
+ prosemirror-transform "^1.1.0"
+
proxy-addr@~2.0.7:
version "2.0.7"
resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025"
@@ -5752,6 +6051,11 @@ proxy-from-env@^1.1.0:
resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2"
integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==
+punycode.js@^2.3.1:
+ version "2.3.1"
+ resolved "https://registry.yarnpkg.com/punycode.js/-/punycode.js-2.3.1.tgz#6b53e56ad75588234e79f4affa90972c7dd8cdb7"
+ integrity sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==
+
punycode@^2.1.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.0.tgz#f67fa67c94da8f4d0cfff981aee4118064199b8f"
@@ -6010,6 +6314,11 @@ rollbar@^2.26.4:
optionalDependencies:
decache "^3.0.5"
+rope-sequence@^1.3.0:
+ version "1.3.4"
+ resolved "https://registry.yarnpkg.com/rope-sequence/-/rope-sequence-1.3.4.tgz#df85711aaecd32f1e756f76e43a415171235d425"
+ integrity sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==
+
run-applescript@^7.0.0:
version "7.1.0"
resolved "https://registry.yarnpkg.com/run-applescript/-/run-applescript-7.1.0.tgz#2e9e54c4664ec3106c5b5630e249d3d6595c4911"
@@ -6792,6 +7101,11 @@ typed-function@^4.1.1:
resolved "https://registry.yarnpkg.com/typed-function/-/typed-function-4.1.1.tgz#38ce3cae31f4f513bcb263563fdad27b2afa73e8"
integrity sha512-Pq1DVubcvibmm8bYcMowjVnnMwPVMeh0DIdA8ad8NZY2sJgapANJmiigSUwlt+EgXxpfIv8MWrQXTIzkfYZLYQ==
+uc.micro@^2.0.0, uc.micro@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-2.1.0.tgz#f8d3f7d0ec4c3dea35a7e3c8efa4cb8b45c9e7ee"
+ integrity sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==
+
unbox-primitive@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.2.tgz#29032021057d5e6cdbd08c5129c226dff8ed6f9e"
@@ -6934,7 +7248,7 @@ vue@^3.3.2:
"@vue/server-renderer" "3.3.4"
"@vue/shared" "3.3.4"
-w3c-keyname@^2.2.4:
+w3c-keyname@^2.2.0, w3c-keyname@^2.2.4:
version "2.2.8"
resolved "https://registry.yarnpkg.com/w3c-keyname/-/w3c-keyname-2.2.8.tgz#7b17c8c6883d4e8b86ac8aba79d39e880f8869c5"
integrity sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==