@doist/typist 10.0.0 → 10.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/CONTRIBUTING.md +12 -10
- package/dist/components/typist-editor.d.ts +2 -2
- package/dist/components/typist-editor.d.ts.map +1 -1
- package/dist/components/typist-editor.helper.js.map +1 -1
- package/dist/components/typist-editor.js.map +1 -1
- package/dist/extensions/core/extra-editor-commands/commands/create-paragraph-end.js.map +1 -1
- package/dist/extensions/core/extra-editor-commands/commands/extend-word-range.js.map +1 -1
- package/dist/extensions/core/extra-editor-commands/commands/insert-markdown-content-at.js.map +1 -1
- package/dist/extensions/core/extra-editor-commands/commands/insert-markdown-content.js.map +1 -1
- package/dist/extensions/core/extra-editor-commands/extra-editor-commands.js.map +1 -1
- package/dist/extensions/core/view-event-handlers.js.map +1 -1
- package/dist/extensions/plain-text/paste-multiline-text.js.map +1 -1
- package/dist/extensions/plain-text/plain-text-document.js.map +1 -1
- package/dist/extensions/plain-text/plain-text-kit.js +1 -1
- package/dist/extensions/plain-text/plain-text-kit.js.map +1 -1
- package/dist/extensions/plain-text/plain-text-paragraph.js.map +1 -1
- package/dist/extensions/plain-text/smart-markdown-typing/plugins/smart-lists.js +2 -2
- package/dist/extensions/plain-text/smart-markdown-typing/plugins/smart-lists.js.map +1 -1
- package/dist/extensions/plain-text/smart-markdown-typing/plugins/smart-select-wrap.js.map +1 -1
- package/dist/extensions/plain-text/smart-markdown-typing/plugins/smart-url-pasting.js.map +1 -1
- package/dist/extensions/plain-text/smart-markdown-typing/smart-markdown-typing.js.map +1 -1
- package/dist/extensions/rich-text/bold-and-italics.js.map +1 -1
- package/dist/extensions/rich-text/curvenote-codemark.js.map +1 -1
- package/dist/extensions/rich-text/paste-emojis.js.map +1 -1
- package/dist/extensions/rich-text/paste-markdown.js.map +1 -1
- package/dist/extensions/rich-text/rich-text-bullet-list.js +1 -1
- package/dist/extensions/rich-text/rich-text-bullet-list.js.map +1 -1
- package/dist/extensions/rich-text/rich-text-code.js.map +1 -1
- package/dist/extensions/rich-text/rich-text-document.js.map +1 -1
- package/dist/extensions/rich-text/rich-text-heading.js.map +1 -1
- package/dist/extensions/rich-text/rich-text-image.js.map +1 -1
- package/dist/extensions/rich-text/rich-text-kit.js.map +1 -1
- package/dist/extensions/rich-text/rich-text-link.js.map +1 -1
- package/dist/extensions/rich-text/rich-text-ordered-list.js +1 -1
- package/dist/extensions/rich-text/rich-text-ordered-list.js.map +1 -1
- package/dist/extensions/rich-text/rich-text-strikethrough.js.map +1 -1
- package/dist/extensions/shared/copy-markdown-source.js.map +1 -1
- package/dist/extensions/shared/paste-html-table-as-string.js +2 -2
- package/dist/extensions/shared/paste-html-table-as-string.js.map +1 -1
- package/dist/extensions/shared/paste-singleline-text.js.map +1 -1
- package/dist/factories/create-suggestion-extension.d.ts.map +1 -1
- package/dist/factories/create-suggestion-extension.js +4 -4
- package/dist/factories/create-suggestion-extension.js.map +1 -1
- package/dist/helpers/dom.js.map +1 -1
- package/dist/helpers/schema.js.map +1 -1
- package/dist/helpers/serializer.js +10 -7
- package/dist/helpers/serializer.js.map +1 -1
- package/dist/helpers/unified.js.map +1 -1
- package/dist/hooks/use-editor.js.map +1 -1
- package/dist/serializers/html/html.js +1 -1
- package/dist/serializers/html/html.js.map +1 -1
- package/dist/serializers/html/plugins/rehype-code-block.js.map +1 -1
- package/dist/serializers/html/plugins/rehype-image.js.map +1 -1
- package/dist/serializers/html/plugins/rehype-suggestions.js +11 -9
- package/dist/serializers/html/plugins/rehype-suggestions.js.map +1 -1
- package/dist/serializers/html/plugins/rehype-task-list.js.map +1 -1
- package/dist/serializers/html/plugins/remark-autolink-literal.js.map +1 -1
- package/dist/serializers/html/plugins/remark-disable-constructs.js.map +1 -1
- package/dist/serializers/html/plugins/remark-strikethrough.js.map +1 -1
- package/dist/serializers/markdown/markdown.d.ts.map +1 -1
- package/dist/serializers/markdown/markdown.js +5 -0
- package/dist/serializers/markdown/markdown.js.map +1 -1
- package/dist/serializers/markdown/plugins/image.js +1 -1
- package/dist/serializers/markdown/plugins/image.js.map +1 -1
- package/dist/serializers/markdown/plugins/list-item.js +1 -0
- package/dist/serializers/markdown/plugins/list-item.js.map +1 -1
- package/dist/serializers/markdown/plugins/paragraph.js.map +1 -1
- package/dist/serializers/markdown/plugins/strikethrough.js.map +1 -1
- package/dist/serializers/markdown/plugins/suggestion.js.map +1 -1
- package/dist/serializers/markdown/plugins/task-item.js +1 -0
- package/dist/serializers/markdown/plugins/task-item.js.map +1 -1
- package/dist/utilities/can-insert-node-at.d.ts +1 -1
- package/dist/utilities/can-insert-node-at.js +1 -1
- package/dist/utilities/can-insert-node-at.js.map +1 -1
- package/dist/utilities/can-insert-suggestion.d.ts +1 -1
- package/dist/utilities/can-insert-suggestion.js +1 -1
- package/dist/utilities/can-insert-suggestion.js.map +1 -1
- package/package.json +25 -38
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rich-text-bullet-list.js","names":[],"sources":["../../../src/extensions/rich-text/rich-text-bullet-list.ts"],"sourcesContent":["import { BulletList } from '@tiptap/extension-bullet-list'\nimport { ListItem } from '@tiptap/extension-list-item'\nimport { TextStyle } from '@tiptap/extension-text-style'\nimport { Fragment, Slice } from '@tiptap/pm/model'\n\nimport type { BulletListOptions } from '@tiptap/extension-bullet-list'\n\n/**\n * The options available to customize the `RichTextBulletList` extension.\n */\ntype RichTextBulletListOptions = {\n /**\n * Replace hard breaks in the selection with paragraphs before toggling the selection into a\n * bullet list. By default, hard breaks are not replaced.\n */\n smartToggle: boolean\n} & BulletListOptions\n\n/**\n * Custom extension that extends the built-in `BulletList` extension to add an option for smart\n * toggling, which takes into account hard breaks in the selection, and converts them into\n * paragraphs before toggling the selection into a bullet list.\n */\nconst RichTextBulletList = BulletList.extend<RichTextBulletListOptions>({\n addOptions() {\n return {\n ...this.parent?.(),\n smartToggle: false,\n }\n },\n\n addCommands() {\n const { editor, name, options } = this\n\n return {\n ...this.parent?.(),\n toggleBulletList() {\n return ({ commands, state, tr, chain }) => {\n // Replace hard breaks in the selection with paragraphs before toggling?\n if (options.smartToggle) {\n const { schema } = state\n const { selection } = tr\n const { $from, $to } = selection\n\n const hardBreakPositions: number[] = []\n\n // Find and store the positions of all hard breaks in the selection\n tr.doc.nodesBetween($from.pos, $to.pos, (node, pos) => {\n if (node.type.name === 'hardBreak') {\n hardBreakPositions.push(pos)\n }\n })\n\n // Replace each hard break with a slice that closes and re-opens a paragraph,\n // effectively inserting a \"paragraph break\" in place of a \"hard break\"\n // (this is performed in reverse order to compensate for content shifting that\n // occurs with each replacement, ensuring accurate insertion points)\n hardBreakPositions.
|
|
1
|
+
{"version":3,"file":"rich-text-bullet-list.js","names":[],"sources":["../../../src/extensions/rich-text/rich-text-bullet-list.ts"],"sourcesContent":["import { BulletList } from '@tiptap/extension-bullet-list'\nimport { ListItem } from '@tiptap/extension-list-item'\nimport { TextStyle } from '@tiptap/extension-text-style'\nimport { Fragment, Slice } from '@tiptap/pm/model'\n\nimport type { BulletListOptions } from '@tiptap/extension-bullet-list'\n\n/**\n * The options available to customize the `RichTextBulletList` extension.\n */\ntype RichTextBulletListOptions = {\n /**\n * Replace hard breaks in the selection with paragraphs before toggling the selection into a\n * bullet list. By default, hard breaks are not replaced.\n */\n smartToggle: boolean\n} & BulletListOptions\n\n/**\n * Custom extension that extends the built-in `BulletList` extension to add an option for smart\n * toggling, which takes into account hard breaks in the selection, and converts them into\n * paragraphs before toggling the selection into a bullet list.\n */\nconst RichTextBulletList = BulletList.extend<RichTextBulletListOptions>({\n addOptions() {\n return {\n ...this.parent?.(),\n smartToggle: false,\n }\n },\n\n addCommands() {\n const { editor, name, options } = this\n\n return {\n ...this.parent?.(),\n toggleBulletList() {\n return ({ commands, state, tr, chain }) => {\n // Replace hard breaks in the selection with paragraphs before toggling?\n if (options.smartToggle) {\n const { schema } = state\n const { selection } = tr\n const { $from, $to } = selection\n\n const hardBreakPositions: number[] = []\n\n // Find and store the positions of all hard breaks in the selection\n tr.doc.nodesBetween($from.pos, $to.pos, (node, pos) => {\n if (node.type.name === 'hardBreak') {\n hardBreakPositions.push(pos)\n }\n })\n\n // Replace each hard break with a slice that closes and re-opens a paragraph,\n // effectively inserting a \"paragraph break\" in place of a \"hard break\"\n // (this is performed in reverse order to compensate for content shifting that\n // occurs with each replacement, ensuring accurate insertion points)\n hardBreakPositions.toReversed().forEach((pos) => {\n tr.replace(\n pos,\n pos + 1,\n Slice.maxOpen(\n Fragment.fromArray([\n schema.nodes.paragraph.create(),\n schema.nodes.paragraph.create(),\n ]),\n ),\n )\n })\n }\n\n // Toggle the selection into a bullet list, optionally keeping attributes\n // (this is a verbatim copy of the built-in `toggleBulletList` command)\n\n if (options.keepAttributes) {\n return chain()\n .toggleList(name, options.itemTypeName, options.keepMarks)\n .updateAttributes(ListItem.name, editor.getAttributes(TextStyle.name))\n .run()\n }\n\n return commands.toggleList(name, options.itemTypeName, options.keepMarks)\n }\n },\n }\n },\n})\n\nexport { RichTextBulletList }\n\nexport type { RichTextBulletListOptions }\n"],"mappings":";;;;;;;;;;AAuBA,MAAM,qBAAqB,WAAW,OAAkC;CACpE,aAAa;EACT,OAAO;GACH,GAAG,KAAK,UAAU;GAClB,aAAa;GAChB;;CAGL,cAAc;EACV,MAAM,EAAE,QAAQ,MAAM,YAAY;EAElC,OAAO;GACH,GAAG,KAAK,UAAU;GAClB,mBAAmB;IACf,QAAQ,EAAE,UAAU,OAAO,IAAI,YAAY;KAEvC,IAAI,QAAQ,aAAa;MACrB,MAAM,EAAE,WAAW;MACnB,MAAM,EAAE,cAAc;MACtB,MAAM,EAAE,OAAO,QAAQ;MAEvB,MAAM,qBAA+B,EAAE;MAGvC,GAAG,IAAI,aAAa,MAAM,KAAK,IAAI,MAAM,MAAM,QAAQ;OACnD,IAAI,KAAK,KAAK,SAAS,aACnB,mBAAmB,KAAK,IAAI;QAElC;MAMF,mBAAmB,YAAY,CAAC,SAAS,QAAQ;OAC7C,GAAG,QACC,KACA,MAAM,GACN,MAAM,QACF,SAAS,UAAU,CACf,OAAO,MAAM,UAAU,QAAQ,EAC/B,OAAO,MAAM,UAAU,QAAQ,CAClC,CAAC,CACL,CACJ;QACH;;KAMN,IAAI,QAAQ,gBACR,OAAO,OAAO,CACT,WAAW,MAAM,QAAQ,cAAc,QAAQ,UAAU,CACzD,iBAAiB,SAAS,MAAM,OAAO,cAAc,UAAU,KAAK,CAAC,CACrE,KAAK;KAGd,OAAO,SAAS,WAAW,MAAM,QAAQ,cAAc,QAAQ,UAAU;;;GAGpF;;CAER,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rich-text-code.js","names":[],"sources":["../../../src/extensions/rich-text/rich-text-code.ts"],"sourcesContent":["import { markInputRule, markPasteRule } from '@tiptap/core'\nimport { Code } from '@tiptap/extension-code'\n\nimport { CODE_EXTENSION_PRIORITY } from '../../constants/extension-priorities'\n\nimport type { CodeOptions } from '@tiptap/extension-code'\n\n/**\n * The options available to customize the `RichTextCode` extension.\n */\ntype RichTextCodeOptions = CodeOptions\n\n/**\n * The original input regex for Markdown inline code (i.e. `<code>code</code>`) to prevent the issue\n * introduced in this PR: https://github.com/ueberdosis/tiptap/pull/4468#issuecomment-2575093998\n */\nconst inputRegex = /(?:^|\\s)(`(?!\\s+`)((?:[^`]+))`(?!\\s+`))$/\n\n/**\n * The original paste regex for Markdown inline code (i.e. `<code>code</code>`) to prevent the issue\n * introduced in this PR: https://github.com/ueberdosis/tiptap/pull/4468#issuecomment-2575093998\n */\nconst pasteRegex = /(?:^|\\s)(`(?!\\s+`)((?:[^`]+))`(?!\\s+`))/g\n\n/**\n * Custom extension that extends the built-in `Code` extension to allow all marks (e.g., Bold,\n * Italic, and Strikethrough) to coexist with the `Code` mark (as opposed to disallowing all any\n * other mark by default).\n *\n * @see https://tiptap.dev/api/schema#excludes\n * @see https://prosemirror.net/docs/ref/#model.MarkSpec.excludes\n */\nconst RichTextCode = Code.extend<RichTextCodeOptions>({\n priority: CODE_EXTENSION_PRIORITY,\n excludes: Code.name,\n addInputRules() {\n return [\n markInputRule({\n find: inputRegex,\n type: this.type,\n }),\n ]\n },\n addPasteRules() {\n return [\n markPasteRule({\n find: pasteRegex,\n type: this.type,\n }),\n ]\n },\n})\n\nexport { RichTextCode }\n\nexport type { RichTextCodeOptions }\n"],"mappings":";;;;;;;;AAgBA,MAAM,aAAa;;;;;AAMnB,MAAM,aAAa;;;;;;;;;AAUnB,MAAM,eAAe,KAAK,OAA4B;CAClD,UAAA;CACA,UAAU,KAAK;CACf,gBAAgB;
|
|
1
|
+
{"version":3,"file":"rich-text-code.js","names":[],"sources":["../../../src/extensions/rich-text/rich-text-code.ts"],"sourcesContent":["import { markInputRule, markPasteRule } from '@tiptap/core'\nimport { Code } from '@tiptap/extension-code'\n\nimport { CODE_EXTENSION_PRIORITY } from '../../constants/extension-priorities'\n\nimport type { CodeOptions } from '@tiptap/extension-code'\n\n/**\n * The options available to customize the `RichTextCode` extension.\n */\ntype RichTextCodeOptions = CodeOptions\n\n/**\n * The original input regex for Markdown inline code (i.e. `<code>code</code>`) to prevent the issue\n * introduced in this PR: https://github.com/ueberdosis/tiptap/pull/4468#issuecomment-2575093998\n */\nconst inputRegex = /(?:^|\\s)(`(?!\\s+`)((?:[^`]+))`(?!\\s+`))$/\n\n/**\n * The original paste regex for Markdown inline code (i.e. `<code>code</code>`) to prevent the issue\n * introduced in this PR: https://github.com/ueberdosis/tiptap/pull/4468#issuecomment-2575093998\n */\nconst pasteRegex = /(?:^|\\s)(`(?!\\s+`)((?:[^`]+))`(?!\\s+`))/g\n\n/**\n * Custom extension that extends the built-in `Code` extension to allow all marks (e.g., Bold,\n * Italic, and Strikethrough) to coexist with the `Code` mark (as opposed to disallowing all any\n * other mark by default).\n *\n * @see https://tiptap.dev/api/schema#excludes\n * @see https://prosemirror.net/docs/ref/#model.MarkSpec.excludes\n */\nconst RichTextCode = Code.extend<RichTextCodeOptions>({\n priority: CODE_EXTENSION_PRIORITY,\n excludes: Code.name,\n addInputRules() {\n return [\n markInputRule({\n find: inputRegex,\n type: this.type,\n }),\n ]\n },\n addPasteRules() {\n return [\n markPasteRule({\n find: pasteRegex,\n type: this.type,\n }),\n ]\n },\n})\n\nexport { RichTextCode }\n\nexport type { RichTextCodeOptions }\n"],"mappings":";;;;;;;;AAgBA,MAAM,aAAa;;;;;AAMnB,MAAM,aAAa;;;;;;;;;AAUnB,MAAM,eAAe,KAAK,OAA4B;CAClD,UAAA;CACA,UAAU,KAAK;CACf,gBAAgB;EACZ,OAAO,CACH,cAAc;GACV,MAAM;GACN,MAAM,KAAK;GACd,CAAC,CACL;;CAEL,gBAAgB;EACZ,OAAO,CACH,cAAc;GACV,MAAM;GACN,MAAM,KAAK;GACd,CAAC,CACL;;CAER,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rich-text-document.js","names":[],"sources":["../../../src/extensions/rich-text/rich-text-document.ts"],"sourcesContent":["import { Document } from '@tiptap/extension-document'\n\n/**\n * The options available to customize the `RichTextDocumentOptions` extension.\n */\ntype RichTextDocumentOptions = {\n /**\n * Indicates whether the document accepts multiple lines of input or only a single line.\n */\n multiline: boolean\n}\n\n/**\n * Custom extension that extends the built-in `Document` extension to define a schema for multiline\n * or singleline rich-text documents (as opposed to the multiple block nodes by default).\n */\nconst RichTextDocument = Document.extend<RichTextDocumentOptions>({\n addOptions() {\n return {\n multiline: true,\n }\n },\n content() {\n // ref: https://tiptap.dev/api/schema#content\n return `block${this.options.multiline ? '+' : ''}`\n },\n})\n\nexport { RichTextDocument }\n\nexport type { RichTextDocumentOptions }\n"],"mappings":";;;;;;AAgBA,MAAM,mBAAmB,SAAS,OAAgC;CAC9D,aAAa;
|
|
1
|
+
{"version":3,"file":"rich-text-document.js","names":[],"sources":["../../../src/extensions/rich-text/rich-text-document.ts"],"sourcesContent":["import { Document } from '@tiptap/extension-document'\n\n/**\n * The options available to customize the `RichTextDocumentOptions` extension.\n */\ntype RichTextDocumentOptions = {\n /**\n * Indicates whether the document accepts multiple lines of input or only a single line.\n */\n multiline: boolean\n}\n\n/**\n * Custom extension that extends the built-in `Document` extension to define a schema for multiline\n * or singleline rich-text documents (as opposed to the multiple block nodes by default).\n */\nconst RichTextDocument = Document.extend<RichTextDocumentOptions>({\n addOptions() {\n return {\n multiline: true,\n }\n },\n content() {\n // ref: https://tiptap.dev/api/schema#content\n return `block${this.options.multiline ? '+' : ''}`\n },\n})\n\nexport { RichTextDocument }\n\nexport type { RichTextDocumentOptions }\n"],"mappings":";;;;;;AAgBA,MAAM,mBAAmB,SAAS,OAAgC;CAC9D,aAAa;EACT,OAAO,EACH,WAAW,MACd;;CAEL,UAAU;EAEN,OAAO,QAAQ,KAAK,QAAQ,YAAY,MAAM;;CAErD,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rich-text-heading.js","names":[],"sources":["../../../src/extensions/rich-text/rich-text-heading.ts"],"sourcesContent":["import { Heading } from '@tiptap/extension-heading'\nimport { textblockTypeInputRule } from '@tiptap/react'\n\nimport type { HeadingOptions } from '@tiptap/extension-heading'\n\n/**\n * The options available to customize the `RichTextHeading` extension.\n */\ntype RichTextHeadingOptions = HeadingOptions\n\n/**\n * Custom extension that extends the built-in `Heading` extension to override the input rule for\n * headings to trigger only on space (e.g., `## `), preventing conflicts with suggestion extensions\n * that use `#` as their trigger character.\n *\n * This was properly fixed in Tiptap v3 where input rules respect extension priorities, making this\n * extension likely unnecessary after migrating.\n *\n * @see https://github.com/ueberdosis/tiptap/issues/2570\n * @see https://github.com/ueberdosis/tiptap/pull/6832\n */\nconst RichTextHeading = Heading.extend<RichTextHeadingOptions>({\n addInputRules() {\n return this.options.levels.map((level: number) => {\n return textblockTypeInputRule({\n find: new RegExp(`^(#{1,${level}}) $`),\n type: this.type,\n getAttributes: {\n level,\n },\n })\n })\n },\n})\n\nexport { RichTextHeading }\n\nexport type { RichTextHeadingOptions }\n"],"mappings":";;;;;;;;;;;;;;AAqBA,MAAM,kBAAkB,QAAQ,OAA+B,EAC3D,gBAAgB;
|
|
1
|
+
{"version":3,"file":"rich-text-heading.js","names":[],"sources":["../../../src/extensions/rich-text/rich-text-heading.ts"],"sourcesContent":["import { Heading } from '@tiptap/extension-heading'\nimport { textblockTypeInputRule } from '@tiptap/react'\n\nimport type { HeadingOptions } from '@tiptap/extension-heading'\n\n/**\n * The options available to customize the `RichTextHeading` extension.\n */\ntype RichTextHeadingOptions = HeadingOptions\n\n/**\n * Custom extension that extends the built-in `Heading` extension to override the input rule for\n * headings to trigger only on space (e.g., `## `), preventing conflicts with suggestion extensions\n * that use `#` as their trigger character.\n *\n * This was properly fixed in Tiptap v3 where input rules respect extension priorities, making this\n * extension likely unnecessary after migrating.\n *\n * @see https://github.com/ueberdosis/tiptap/issues/2570\n * @see https://github.com/ueberdosis/tiptap/pull/6832\n */\nconst RichTextHeading = Heading.extend<RichTextHeadingOptions>({\n addInputRules() {\n return this.options.levels.map((level: number) => {\n return textblockTypeInputRule({\n find: new RegExp(`^(#{1,${level}}) $`),\n type: this.type,\n getAttributes: {\n level,\n },\n })\n })\n },\n})\n\nexport { RichTextHeading }\n\nexport type { RichTextHeadingOptions }\n"],"mappings":";;;;;;;;;;;;;;AAqBA,MAAM,kBAAkB,QAAQ,OAA+B,EAC3D,gBAAgB;CACZ,OAAO,KAAK,QAAQ,OAAO,KAAK,UAAkB;EAC9C,OAAO,uBAAuB;GAC1B,MAAM,IAAI,OAAO,SAAS,MAAM,MAAM;GACtC,MAAM,KAAK;GACX,eAAe,EACX,OACH;GACJ,CAAC;GACJ;GAET,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rich-text-image.js","names":[],"sources":["../../../src/extensions/rich-text/rich-text-image.ts"],"sourcesContent":["import { Image } from '@tiptap/extension-image'\nimport { Plugin, PluginKey, Selection } from '@tiptap/pm/state'\nimport { ReactNodeViewRenderer } from '@tiptap/react'\n\nimport type { NodeView } from '@tiptap/pm/view'\nimport type { ReactNodeViewProps } from '@tiptap/react'\n\n/**\n * The properties that describe `RichTextImage` node attributes.\n */\ntype RichTextImageAttributes = {\n /**\n * Additional metadata about an image attachment upload.\n */\n metadata?: {\n /**\n * A unique ID for the image attachment.\n */\n attachmentId: string\n\n /**\n * Specifies if the image attachment failed to upload.\n */\n isUploadFailed: boolean\n\n /**\n * The upload progress for the image attachment.\n */\n uploadProgress: number\n }\n} & Pick<HTMLImageElement, 'src'> &\n Pick<Partial<HTMLImageElement>, 'alt' | 'title'>\n\n/**\n * Augment the official `@tiptap/core` module with extra commands, relevant for this extension, so\n * that the compiler knows about them.\n */\ndeclare module '@tiptap/core' {\n interface Commands<ReturnType> {\n richTextImage: {\n /**\n * Inserts an image into the editor with the given attributes.\n */\n insertImage: (attributes: RichTextImageAttributes) => ReturnType\n\n /**\n * Updates the attributes for an existing image in the editor.\n */\n updateImage: (\n attributes: Partial<RichTextImageAttributes> &\n Required<Pick<RichTextImageAttributes, 'metadata'>>,\n ) => ReturnType\n }\n }\n}\n\n/**\n * The options available to customize the `RichTextImage` extension.\n */\ntype RichTextImageOptions = {\n /**\n * A list of accepted MIME types for images pasting.\n */\n acceptedImageMimeTypes: string[]\n\n /**\n * Renders the image node inline (e.g., <p><img src=\"doist.jpg\"></p>). By default images are on\n * the same level as paragraphs.\n */\n inline: boolean\n\n /**\n * Custom HTML attributes that should be added to the rendered HTML tag.\n */\n HTMLAttributes: Record<string, string>\n\n /**\n * A React component to render inside the interactive node view.\n */\n NodeViewComponent?: React.ComponentType<ReactNodeViewProps>\n\n /**\n * The event handler that is fired when an image file is pasted.\n */\n onImageFilePaste?: (file: File) => void\n}\n\n/**\n * Custom extension that extends the built-in `Image` extension to add support for image pasting,\n * and also adds the ability to pass aditional metadata about an image attachment upload.\n */\nconst RichTextImage = Image.extend<RichTextImageOptions>({\n draggable: true,\n addOptions() {\n return {\n ...this.parent?.(),\n acceptedImageMimeTypes: ['image/gif', 'image/jpeg', 'image/jpg', 'image/png'],\n NodeViewComponent: undefined,\n }\n },\n addAttributes() {\n return {\n ...this.parent?.(),\n metadata: {\n default: null,\n rendered: false,\n },\n }\n },\n addCommands() {\n const { name: nodeTypeName } = this\n\n return {\n ...this.parent?.(),\n insertImage(attributes) {\n return ({ editor, commands }) => {\n const selectionAtEnd = Selection.atEnd(editor.state.doc)\n\n return commands.insertContent([\n {\n type: nodeTypeName,\n attrs: attributes,\n },\n // Insert a blank paragraph after the image when at the end of the document\n ...(editor.state.selection.to === selectionAtEnd.to\n ? [{ type: 'paragraph' }]\n : []),\n ])\n }\n },\n updateImage(attributes) {\n return ({ commands }) => {\n return commands.command(({ tr }) => {\n tr.doc.descendants((node, position) => {\n const { metadata } = node.attrs as {\n metadata: RichTextImageAttributes['metadata']\n }\n\n // Update the image attributes to the corresponding node\n if (\n node.type.name === nodeTypeName &&\n metadata?.attachmentId === attributes.metadata?.attachmentId\n ) {\n tr.setNodeMarkup(position, node.type, {\n ...node.attrs,\n ...attributes,\n })\n }\n })\n\n return true\n })\n }\n },\n }\n },\n addNodeView() {\n const { NodeViewComponent } = this.options\n\n // Do not add a node view if component was not specified\n if (!NodeViewComponent) {\n return () => ({}) as NodeView\n }\n\n // Render the node view with the provided React component\n return ReactNodeViewRenderer(NodeViewComponent, {\n as: 'div',\n className: `Typist-${this.type.name}`,\n })\n },\n addProseMirrorPlugins() {\n const { acceptedImageMimeTypes, onImageFilePaste } = this.options\n\n return [\n new Plugin({\n key: new PluginKey(this.name),\n props: {\n handlePaste(_, event) {\n // Do not handle the event if we don't have a callback\n if (!onImageFilePaste) {\n return false\n }\n\n const pastedFiles = Array.from(event.clipboardData?.files || [])\n\n // Do not handle the event if no files were pasted\n if (pastedFiles.length === 0) {\n return false\n }\n\n let wasPasteHandled = false\n\n // Invoke the callback for every pasted file that is an accepted image type\n pastedFiles.forEach((pastedFile) => {\n if (acceptedImageMimeTypes.includes(pastedFile.type)) {\n onImageFilePaste(pastedFile)\n wasPasteHandled = true\n }\n })\n\n // Suppress the default handling behaviour if at least one image was handled\n return wasPasteHandled\n },\n },\n }),\n ]\n },\n})\n\nexport { RichTextImage }\n\nexport type { RichTextImageAttributes, RichTextImageOptions }\n"],"mappings":";;;;;;;;AA2FA,MAAM,gBAAgB,MAAM,OAA6B;CACrD,WAAW;CACX,aAAa;
|
|
1
|
+
{"version":3,"file":"rich-text-image.js","names":[],"sources":["../../../src/extensions/rich-text/rich-text-image.ts"],"sourcesContent":["import { Image } from '@tiptap/extension-image'\nimport { Plugin, PluginKey, Selection } from '@tiptap/pm/state'\nimport { ReactNodeViewRenderer } from '@tiptap/react'\n\nimport type { NodeView } from '@tiptap/pm/view'\nimport type { ReactNodeViewProps } from '@tiptap/react'\n\n/**\n * The properties that describe `RichTextImage` node attributes.\n */\ntype RichTextImageAttributes = {\n /**\n * Additional metadata about an image attachment upload.\n */\n metadata?: {\n /**\n * A unique ID for the image attachment.\n */\n attachmentId: string\n\n /**\n * Specifies if the image attachment failed to upload.\n */\n isUploadFailed: boolean\n\n /**\n * The upload progress for the image attachment.\n */\n uploadProgress: number\n }\n} & Pick<HTMLImageElement, 'src'> &\n Pick<Partial<HTMLImageElement>, 'alt' | 'title'>\n\n/**\n * Augment the official `@tiptap/core` module with extra commands, relevant for this extension, so\n * that the compiler knows about them.\n */\ndeclare module '@tiptap/core' {\n interface Commands<ReturnType> {\n richTextImage: {\n /**\n * Inserts an image into the editor with the given attributes.\n */\n insertImage: (attributes: RichTextImageAttributes) => ReturnType\n\n /**\n * Updates the attributes for an existing image in the editor.\n */\n updateImage: (\n attributes: Partial<RichTextImageAttributes> &\n Required<Pick<RichTextImageAttributes, 'metadata'>>,\n ) => ReturnType\n }\n }\n}\n\n/**\n * The options available to customize the `RichTextImage` extension.\n */\ntype RichTextImageOptions = {\n /**\n * A list of accepted MIME types for images pasting.\n */\n acceptedImageMimeTypes: string[]\n\n /**\n * Renders the image node inline (e.g., <p><img src=\"doist.jpg\"></p>). By default images are on\n * the same level as paragraphs.\n */\n inline: boolean\n\n /**\n * Custom HTML attributes that should be added to the rendered HTML tag.\n */\n HTMLAttributes: Record<string, string>\n\n /**\n * A React component to render inside the interactive node view.\n */\n NodeViewComponent?: React.ComponentType<ReactNodeViewProps>\n\n /**\n * The event handler that is fired when an image file is pasted.\n */\n onImageFilePaste?: (file: File) => void\n}\n\n/**\n * Custom extension that extends the built-in `Image` extension to add support for image pasting,\n * and also adds the ability to pass aditional metadata about an image attachment upload.\n */\nconst RichTextImage = Image.extend<RichTextImageOptions>({\n draggable: true,\n addOptions() {\n return {\n ...this.parent?.(),\n acceptedImageMimeTypes: ['image/gif', 'image/jpeg', 'image/jpg', 'image/png'],\n NodeViewComponent: undefined,\n }\n },\n addAttributes() {\n return {\n ...this.parent?.(),\n metadata: {\n default: null,\n rendered: false,\n },\n }\n },\n addCommands() {\n const { name: nodeTypeName } = this\n\n return {\n ...this.parent?.(),\n insertImage(attributes) {\n return ({ editor, commands }) => {\n const selectionAtEnd = Selection.atEnd(editor.state.doc)\n\n return commands.insertContent([\n {\n type: nodeTypeName,\n attrs: attributes,\n },\n // Insert a blank paragraph after the image when at the end of the document\n ...(editor.state.selection.to === selectionAtEnd.to\n ? [{ type: 'paragraph' }]\n : []),\n ])\n }\n },\n updateImage(attributes) {\n return ({ commands }) => {\n return commands.command(({ tr }) => {\n tr.doc.descendants((node, position) => {\n const { metadata } = node.attrs as {\n metadata: RichTextImageAttributes['metadata']\n }\n\n // Update the image attributes to the corresponding node\n if (\n node.type.name === nodeTypeName &&\n metadata?.attachmentId === attributes.metadata?.attachmentId\n ) {\n tr.setNodeMarkup(position, node.type, {\n ...node.attrs,\n ...attributes,\n })\n }\n })\n\n return true\n })\n }\n },\n }\n },\n addNodeView() {\n const { NodeViewComponent } = this.options\n\n // Do not add a node view if component was not specified\n if (!NodeViewComponent) {\n return () => ({}) as NodeView\n }\n\n // Render the node view with the provided React component\n return ReactNodeViewRenderer(NodeViewComponent, {\n as: 'div',\n className: `Typist-${this.type.name}`,\n })\n },\n addProseMirrorPlugins() {\n const { acceptedImageMimeTypes, onImageFilePaste } = this.options\n\n return [\n new Plugin({\n key: new PluginKey(this.name),\n props: {\n handlePaste(_, event) {\n // Do not handle the event if we don't have a callback\n if (!onImageFilePaste) {\n return false\n }\n\n const pastedFiles = Array.from(event.clipboardData?.files || [])\n\n // Do not handle the event if no files were pasted\n if (pastedFiles.length === 0) {\n return false\n }\n\n let wasPasteHandled = false\n\n // Invoke the callback for every pasted file that is an accepted image type\n pastedFiles.forEach((pastedFile) => {\n if (acceptedImageMimeTypes.includes(pastedFile.type)) {\n onImageFilePaste(pastedFile)\n wasPasteHandled = true\n }\n })\n\n // Suppress the default handling behaviour if at least one image was handled\n return wasPasteHandled\n },\n },\n }),\n ]\n },\n})\n\nexport { RichTextImage }\n\nexport type { RichTextImageAttributes, RichTextImageOptions }\n"],"mappings":";;;;;;;;AA2FA,MAAM,gBAAgB,MAAM,OAA6B;CACrD,WAAW;CACX,aAAa;EACT,OAAO;GACH,GAAG,KAAK,UAAU;GAClB,wBAAwB;IAAC;IAAa;IAAc;IAAa;IAAY;GAC7E,mBAAmB,KAAA;GACtB;;CAEL,gBAAgB;EACZ,OAAO;GACH,GAAG,KAAK,UAAU;GAClB,UAAU;IACN,SAAS;IACT,UAAU;IACb;GACJ;;CAEL,cAAc;EACV,MAAM,EAAE,MAAM,iBAAiB;EAE/B,OAAO;GACH,GAAG,KAAK,UAAU;GAClB,YAAY,YAAY;IACpB,QAAQ,EAAE,QAAQ,eAAe;KAC7B,MAAM,iBAAiB,UAAU,MAAM,OAAO,MAAM,IAAI;KAExD,OAAO,SAAS,cAAc,CAC1B;MACI,MAAM;MACN,OAAO;MACV,EAED,GAAI,OAAO,MAAM,UAAU,OAAO,eAAe,KAC3C,CAAC,EAAE,MAAM,aAAa,CAAC,GACvB,EAAE,CACX,CAAC;;;GAGV,YAAY,YAAY;IACpB,QAAQ,EAAE,eAAe;KACrB,OAAO,SAAS,SAAS,EAAE,SAAS;MAChC,GAAG,IAAI,aAAa,MAAM,aAAa;OACnC,MAAM,EAAE,aAAa,KAAK;OAK1B,IACI,KAAK,KAAK,SAAS,gBACnB,UAAU,iBAAiB,WAAW,UAAU,cAEhD,GAAG,cAAc,UAAU,KAAK,MAAM;QAClC,GAAG,KAAK;QACR,GAAG;QACN,CAAC;QAER;MAEF,OAAO;OACT;;;GAGb;;CAEL,cAAc;EACV,MAAM,EAAE,sBAAsB,KAAK;EAGnC,IAAI,CAAC,mBACD,cAAc,EAAE;EAIpB,OAAO,sBAAsB,mBAAmB;GAC5C,IAAI;GACJ,WAAW,UAAU,KAAK,KAAK;GAClC,CAAC;;CAEN,wBAAwB;EACpB,MAAM,EAAE,wBAAwB,qBAAqB,KAAK;EAE1D,OAAO,CACH,IAAI,OAAO;GACP,KAAK,IAAI,UAAU,KAAK,KAAK;GAC7B,OAAO,EACH,YAAY,GAAG,OAAO;IAElB,IAAI,CAAC,kBACD,OAAO;IAGX,MAAM,cAAc,MAAM,KAAK,MAAM,eAAe,SAAS,EAAE,CAAC;IAGhE,IAAI,YAAY,WAAW,GACvB,OAAO;IAGX,IAAI,kBAAkB;IAGtB,YAAY,SAAS,eAAe;KAChC,IAAI,uBAAuB,SAAS,WAAW,KAAK,EAAE;MAClD,iBAAiB,WAAW;MAC5B,kBAAkB;;MAExB;IAGF,OAAO;MAEd;GACJ,CAAC,CACL;;CAER,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rich-text-kit.js","names":[],"sources":["../../../src/extensions/rich-text/rich-text-kit.ts"],"sourcesContent":["import { Extension } from '@tiptap/core'\nimport { Blockquote } from '@tiptap/extension-blockquote'\nimport { Bold } from '@tiptap/extension-bold'\nimport { CodeBlock } from '@tiptap/extension-code-block'\nimport { Dropcursor } from '@tiptap/extension-dropcursor'\nimport { Gapcursor } from '@tiptap/extension-gapcursor'\nimport { HardBreak } from '@tiptap/extension-hard-break'\nimport { History } from '@tiptap/extension-history'\nimport { HorizontalRule } from '@tiptap/extension-horizontal-rule'\nimport { Italic } from '@tiptap/extension-italic'\nimport { ListItem } from '@tiptap/extension-list-item'\nimport { ListKeymap } from '@tiptap/extension-list-keymap'\nimport { Paragraph } from '@tiptap/extension-paragraph'\nimport { Text } from '@tiptap/extension-text'\nimport { Typography } from '@tiptap/extension-typography'\n\nimport { BLOCKQUOTE_EXTENSION_PRIORITY } from '../../constants/extension-priorities'\nimport { CopyMarkdownSource } from '../shared/copy-markdown-source'\nimport { PasteHTMLTableAsString } from '../shared/paste-html-table-as-string'\nimport { PasteSinglelineText } from '../shared/paste-singleline-text'\n\nimport { BoldAndItalics } from './bold-and-italics'\nimport { CurvenoteCodemark } from './curvenote-codemark'\nimport { PasteEmojis } from './paste-emojis'\nimport { PasteMarkdown } from './paste-markdown'\nimport { RichTextBulletList } from './rich-text-bullet-list'\nimport { RichTextCode } from './rich-text-code'\nimport { RichTextDocument } from './rich-text-document'\nimport { RichTextHeading, RichTextHeadingOptions } from './rich-text-heading'\nimport { RichTextImage } from './rich-text-image'\nimport { RichTextLink } from './rich-text-link'\nimport { RichTextOrderedList } from './rich-text-ordered-list'\nimport { RichTextStrikethrough } from './rich-text-strikethrough'\n\nimport type { Extensions } from '@tiptap/core'\nimport type { BlockquoteOptions } from '@tiptap/extension-blockquote'\nimport type { BoldOptions } from '@tiptap/extension-bold'\nimport type { CodeOptions } from '@tiptap/extension-code'\nimport type { CodeBlockOptions } from '@tiptap/extension-code-block'\nimport type { DropcursorOptions } from '@tiptap/extension-dropcursor'\nimport type { HardBreakOptions } from '@tiptap/extension-hard-break'\nimport type { HistoryOptions } from '@tiptap/extension-history'\nimport type { HorizontalRuleOptions } from '@tiptap/extension-horizontal-rule'\nimport type { ItalicOptions } from '@tiptap/extension-italic'\nimport type { ListItemOptions } from '@tiptap/extension-list-item'\nimport type { ListKeymapOptions } from '@tiptap/extension-list-keymap'\nimport type { ParagraphOptions } from '@tiptap/extension-paragraph'\nimport type { RichTextBulletListOptions } from './rich-text-bullet-list'\nimport type { RichTextDocumentOptions } from './rich-text-document'\nimport type { RichTextImageOptions } from './rich-text-image'\nimport type { RichTextLinkOptions } from './rich-text-link'\nimport type { RichTextOrderedListOptions } from './rich-text-ordered-list'\nimport type { RichTextStrikethroughOptions } from './rich-text-strikethrough'\n\n/**\n * The options available to customize the `RichTextKit` extension.\n */\ntype RichTextKitOptions = {\n /**\n * Set options for the `Blockquote` extension, or `false` to disable.\n */\n blockquote: Partial<BlockquoteOptions> | false\n\n /**\n * Set options for the `Bold` extension, or `false` to disable.\n */\n bold: Partial<BoldOptions> | false\n\n /**\n * Set options for the `BulletList` extension, or `false` to disable.\n */\n bulletList: Partial<RichTextBulletListOptions> | false\n\n /**\n * Set options for the `Code` extension, or `false` to disable.\n */\n code: Partial<CodeOptions> | false\n\n /**\n * Set options for the `CodeBlock` extension, or `false` to disable.\n */\n codeBlock: Partial<CodeBlockOptions> | false\n\n /**\n * Set options for the `Document` extension, or `false` to disable.\n */\n document: Partial<RichTextDocumentOptions> | false\n\n /**\n * Set options for the `Dropcursor` extension, or `false` to disable.\n */\n dropCursor: Partial<DropcursorOptions> | false\n\n /**\n * Set to `false` to disable the `Gapcursor` extension.\n */\n gapCursor: false\n\n /**\n * Set options for the `HardBreak` extension, or `false` to disable.\n */\n hardBreak: Partial<HardBreakOptions> | false\n\n /**\n * Set options for the `Heading` extension, or `false` to disable.\n */\n heading: Partial<RichTextHeadingOptions> | false\n\n /**\n * Set options for the `History` extension, or `false` to disable.\n */\n history: Partial<HistoryOptions> | false\n\n /**\n * Set options for the `HorizontalRule` extension, or `false` to disable.\n */\n horizontalRule: Partial<HorizontalRuleOptions> | false\n\n /**\n * Set options for the `Image` extension, or `false` to disable.\n */\n image: Partial<RichTextImageOptions> | false\n\n /**\n * Set options for the `Italic` extension, or `false` to disable.\n */\n italic: Partial<ItalicOptions> | false\n\n /**\n * Set options for the `Link` extension, or `false` to disable.\n */\n link: Partial<RichTextLinkOptions> | false\n\n /**\n * Set options for the `ListItem` extension, or `false` to disable.\n */\n listItem: Partial<ListItemOptions> | false\n\n /**\n * Set options for the `ListKeymap` extension, or `false` to disable.\n */\n listKeymap: Partial<ListKeymapOptions> | false\n\n /**\n * Set options for the `OrderedList` extension, or `false` to disable.\n */\n orderedList: Partial<RichTextOrderedListOptions> | false\n\n /**\n * Set options for the `Paragraph` extension, or `false` to disable.\n */\n paragraph: Partial<ParagraphOptions> | false\n\n /**\n * Set to `false` to disable the `PasteEmojis` extension.\n */\n pasteEmojis: false\n\n /**\n * Set to `false` to disable the `PasteMarkdown` extension.\n */\n pasteMarkdown: false\n\n /**\n * Set to `false` to disable the `PasteSinglelineText` extension.\n */\n pasteSinglelineText: false\n\n /**\n * Set to `false` to disable the `PasteHTMLTableAsString` extension.\n */\n pasteHTMLTableAsString: false\n\n /**\n * Set options for the `Strike` extension, or `false` to disable.\n */\n strike: Partial<RichTextStrikethroughOptions> | false\n\n /**\n * Set to `false` to disable the `Text` extension.\n */\n text: false\n\n /**\n * Set to `false` to disable the `Typography` extension.\n */\n typography: false\n}\n\n/**\n * The `RichTextKit` extension is a collection of the minimal required extensions to have a full\n * WYSIWYG text editor working. This extension is based on the official `StarterKit` extension\n * implementation, allowing almost every extension to be customized or disabled.\n */\nconst RichTextKit = Extension.create<RichTextKitOptions>({\n name: 'richTextKit',\n addExtensions() {\n const extensions: Extensions = []\n\n if (this.options.blockquote !== false) {\n extensions.push(\n Blockquote.extend({\n priority: BLOCKQUOTE_EXTENSION_PRIORITY,\n }).configure(this.options?.blockquote),\n )\n }\n\n if (this.options.bold !== false) {\n extensions.push(Bold.configure(this.options?.bold))\n }\n\n if (this.options.bulletList !== false) {\n extensions.push(RichTextBulletList.configure(this.options?.bulletList))\n }\n\n if (this.options.code !== false) {\n extensions.push(\n RichTextCode.configure(this.options?.code),\n\n // Enhances the Code extension capabilities with additional features\n CurvenoteCodemark,\n )\n }\n\n if (this.options.codeBlock !== false) {\n extensions.push(CodeBlock.configure(this.options?.codeBlock))\n }\n\n if (this.options.document !== false) {\n extensions.push(\n RichTextDocument.configure(this.options?.document),\n\n // Supports copying the underlying Markdown source to the clipboard\n CopyMarkdownSource.configure({\n keyboardShortcut: 'Mod-Shift-c',\n }),\n )\n\n if (this.options?.pasteEmojis !== false) {\n // Supports pasting HTML image emojis as unicode characters\n extensions.push(PasteEmojis)\n }\n\n if (this.options?.pasteMarkdown !== false) {\n // Supports pasting Markdown content as HTML into the editor\n extensions.push(PasteMarkdown)\n }\n\n if (\n this.options?.document?.multiline === false &&\n this.options?.pasteSinglelineText !== false\n ) {\n // Supports pasting multiple lines into a singleline editor, by joining all the\n // pasted lines together\n extensions.push(PasteSinglelineText)\n }\n\n if (this.options?.pasteHTMLTableAsString !== false) {\n // Supports pasting tables (from spreadsheets and websites) into the editor\n extensions.push(PasteHTMLTableAsString)\n }\n }\n\n if (this.options.dropCursor !== false) {\n extensions.push(Dropcursor.configure(this.options?.dropCursor))\n }\n\n if (this.options.gapCursor !== false) {\n extensions.push(Gapcursor)\n }\n\n if (this.options.hardBreak !== false) {\n extensions.push(HardBreak.configure(this.options?.hardBreak))\n }\n\n if (this.options.heading !== false) {\n extensions.push(RichTextHeading.configure(this.options?.heading))\n }\n\n if (this.options.history !== false) {\n extensions.push(History.configure(this.options?.history))\n }\n\n if (this.options.horizontalRule !== false) {\n extensions.push(HorizontalRule.configure(this.options?.horizontalRule))\n }\n\n if (this.options.image !== false) {\n extensions.push(RichTextImage.configure(this.options?.image))\n }\n\n if (this.options.italic !== false) {\n extensions.push(Italic.configure(this.options?.italic))\n }\n\n if (this.options.bold !== false && this.options.italic !== false) {\n extensions.push(BoldAndItalics)\n }\n\n if (this.options.link !== false) {\n extensions.push(RichTextLink.configure(this.options?.link))\n }\n\n if (this.options.listItem !== false) {\n extensions.push(ListItem.configure(this.options?.listItem))\n }\n\n if (this.options.listKeymap !== false) {\n extensions.push(ListKeymap.configure(this.options?.listKeymap))\n }\n\n if (this.options.orderedList !== false) {\n extensions.push(RichTextOrderedList.configure(this.options?.orderedList))\n }\n\n if (this.options.paragraph !== false) {\n extensions.push(Paragraph.configure(this.options?.paragraph))\n }\n\n if (this.options.strike !== false) {\n extensions.push(RichTextStrikethrough.configure(this.options?.strike))\n }\n\n if (this.options.text !== false) {\n extensions.push(Text)\n }\n\n if (this.options.typography !== false) {\n extensions.push(Typography)\n }\n\n return extensions\n },\n})\n\nexport { RichTextKit }\n\nexport type { RichTextKitOptions }\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkMA,MAAM,cAAc,UAAU,OAA2B;CACrD,MAAM;CACN,gBAAgB;EACZ,MAAM,aAAyB,EAAE;AAEjC,MAAI,KAAK,QAAQ,eAAe,MAC5B,YAAW,KACP,WAAW,OAAO,EACd,UAAA,KACH,CAAC,CAAC,UAAU,KAAK,SAAS,WAAW,CACzC;AAGL,MAAI,KAAK,QAAQ,SAAS,MACtB,YAAW,KAAK,KAAK,UAAU,KAAK,SAAS,KAAK,CAAC;AAGvD,MAAI,KAAK,QAAQ,eAAe,MAC5B,YAAW,KAAK,mBAAmB,UAAU,KAAK,SAAS,WAAW,CAAC;AAG3E,MAAI,KAAK,QAAQ,SAAS,MACtB,YAAW,KACP,aAAa,UAAU,KAAK,SAAS,KAAK,EAG1C,kBACH;AAGL,MAAI,KAAK,QAAQ,cAAc,MAC3B,YAAW,KAAK,UAAU,UAAU,KAAK,SAAS,UAAU,CAAC;AAGjE,MAAI,KAAK,QAAQ,aAAa,OAAO;AACjC,cAAW,KACP,iBAAiB,UAAU,KAAK,SAAS,SAAS,EAGlD,mBAAmB,UAAU,EACzB,kBAAkB,eACrB,CAAC,CACL;AAED,OAAI,KAAK,SAAS,gBAAgB,MAE9B,YAAW,KAAK,YAAY;AAGhC,OAAI,KAAK,SAAS,kBAAkB,MAEhC,YAAW,KAAK,cAAc;AAGlC,OACI,KAAK,SAAS,UAAU,cAAc,SACtC,KAAK,SAAS,wBAAwB,MAItC,YAAW,KAAK,oBAAoB;AAGxC,OAAI,KAAK,SAAS,2BAA2B,MAEzC,YAAW,KAAK,uBAAuB;;AAI/C,MAAI,KAAK,QAAQ,eAAe,MAC5B,YAAW,KAAK,WAAW,UAAU,KAAK,SAAS,WAAW,CAAC;AAGnE,MAAI,KAAK,QAAQ,cAAc,MAC3B,YAAW,KAAK,UAAU;AAG9B,MAAI,KAAK,QAAQ,cAAc,MAC3B,YAAW,KAAK,UAAU,UAAU,KAAK,SAAS,UAAU,CAAC;AAGjE,MAAI,KAAK,QAAQ,YAAY,MACzB,YAAW,KAAK,gBAAgB,UAAU,KAAK,SAAS,QAAQ,CAAC;AAGrE,MAAI,KAAK,QAAQ,YAAY,MACzB,YAAW,KAAK,QAAQ,UAAU,KAAK,SAAS,QAAQ,CAAC;AAG7D,MAAI,KAAK,QAAQ,mBAAmB,MAChC,YAAW,KAAK,eAAe,UAAU,KAAK,SAAS,eAAe,CAAC;AAG3E,MAAI,KAAK,QAAQ,UAAU,MACvB,YAAW,KAAK,cAAc,UAAU,KAAK,SAAS,MAAM,CAAC;AAGjE,MAAI,KAAK,QAAQ,WAAW,MACxB,YAAW,KAAK,OAAO,UAAU,KAAK,SAAS,OAAO,CAAC;AAG3D,MAAI,KAAK,QAAQ,SAAS,SAAS,KAAK,QAAQ,WAAW,MACvD,YAAW,KAAK,eAAe;AAGnC,MAAI,KAAK,QAAQ,SAAS,MACtB,YAAW,KAAK,aAAa,UAAU,KAAK,SAAS,KAAK,CAAC;AAG/D,MAAI,KAAK,QAAQ,aAAa,MAC1B,YAAW,KAAK,SAAS,UAAU,KAAK,SAAS,SAAS,CAAC;AAG/D,MAAI,KAAK,QAAQ,eAAe,MAC5B,YAAW,KAAK,WAAW,UAAU,KAAK,SAAS,WAAW,CAAC;AAGnE,MAAI,KAAK,QAAQ,gBAAgB,MAC7B,YAAW,KAAK,oBAAoB,UAAU,KAAK,SAAS,YAAY,CAAC;AAG7E,MAAI,KAAK,QAAQ,cAAc,MAC3B,YAAW,KAAK,UAAU,UAAU,KAAK,SAAS,UAAU,CAAC;AAGjE,MAAI,KAAK,QAAQ,WAAW,MACxB,YAAW,KAAK,sBAAsB,UAAU,KAAK,SAAS,OAAO,CAAC;AAG1E,MAAI,KAAK,QAAQ,SAAS,MACtB,YAAW,KAAK,KAAK;AAGzB,MAAI,KAAK,QAAQ,eAAe,MAC5B,YAAW,KAAK,WAAW;AAG/B,SAAO;;CAEd,CAAC"}
|
|
1
|
+
{"version":3,"file":"rich-text-kit.js","names":[],"sources":["../../../src/extensions/rich-text/rich-text-kit.ts"],"sourcesContent":["import { Extension } from '@tiptap/core'\nimport { Blockquote } from '@tiptap/extension-blockquote'\nimport { Bold } from '@tiptap/extension-bold'\nimport { CodeBlock } from '@tiptap/extension-code-block'\nimport { Dropcursor } from '@tiptap/extension-dropcursor'\nimport { Gapcursor } from '@tiptap/extension-gapcursor'\nimport { HardBreak } from '@tiptap/extension-hard-break'\nimport { History } from '@tiptap/extension-history'\nimport { HorizontalRule } from '@tiptap/extension-horizontal-rule'\nimport { Italic } from '@tiptap/extension-italic'\nimport { ListItem } from '@tiptap/extension-list-item'\nimport { ListKeymap } from '@tiptap/extension-list-keymap'\nimport { Paragraph } from '@tiptap/extension-paragraph'\nimport { Text } from '@tiptap/extension-text'\nimport { Typography } from '@tiptap/extension-typography'\n\nimport { BLOCKQUOTE_EXTENSION_PRIORITY } from '../../constants/extension-priorities'\nimport { CopyMarkdownSource } from '../shared/copy-markdown-source'\nimport { PasteHTMLTableAsString } from '../shared/paste-html-table-as-string'\nimport { PasteSinglelineText } from '../shared/paste-singleline-text'\n\nimport { BoldAndItalics } from './bold-and-italics'\nimport { CurvenoteCodemark } from './curvenote-codemark'\nimport { PasteEmojis } from './paste-emojis'\nimport { PasteMarkdown } from './paste-markdown'\nimport { RichTextBulletList } from './rich-text-bullet-list'\nimport { RichTextCode } from './rich-text-code'\nimport { RichTextDocument } from './rich-text-document'\nimport { RichTextHeading, RichTextHeadingOptions } from './rich-text-heading'\nimport { RichTextImage } from './rich-text-image'\nimport { RichTextLink } from './rich-text-link'\nimport { RichTextOrderedList } from './rich-text-ordered-list'\nimport { RichTextStrikethrough } from './rich-text-strikethrough'\n\nimport type { Extensions } from '@tiptap/core'\nimport type { BlockquoteOptions } from '@tiptap/extension-blockquote'\nimport type { BoldOptions } from '@tiptap/extension-bold'\nimport type { CodeOptions } from '@tiptap/extension-code'\nimport type { CodeBlockOptions } from '@tiptap/extension-code-block'\nimport type { DropcursorOptions } from '@tiptap/extension-dropcursor'\nimport type { HardBreakOptions } from '@tiptap/extension-hard-break'\nimport type { HistoryOptions } from '@tiptap/extension-history'\nimport type { HorizontalRuleOptions } from '@tiptap/extension-horizontal-rule'\nimport type { ItalicOptions } from '@tiptap/extension-italic'\nimport type { ListItemOptions } from '@tiptap/extension-list-item'\nimport type { ListKeymapOptions } from '@tiptap/extension-list-keymap'\nimport type { ParagraphOptions } from '@tiptap/extension-paragraph'\nimport type { RichTextBulletListOptions } from './rich-text-bullet-list'\nimport type { RichTextDocumentOptions } from './rich-text-document'\nimport type { RichTextImageOptions } from './rich-text-image'\nimport type { RichTextLinkOptions } from './rich-text-link'\nimport type { RichTextOrderedListOptions } from './rich-text-ordered-list'\nimport type { RichTextStrikethroughOptions } from './rich-text-strikethrough'\n\n/**\n * The options available to customize the `RichTextKit` extension.\n */\ntype RichTextKitOptions = {\n /**\n * Set options for the `Blockquote` extension, or `false` to disable.\n */\n blockquote: Partial<BlockquoteOptions> | false\n\n /**\n * Set options for the `Bold` extension, or `false` to disable.\n */\n bold: Partial<BoldOptions> | false\n\n /**\n * Set options for the `BulletList` extension, or `false` to disable.\n */\n bulletList: Partial<RichTextBulletListOptions> | false\n\n /**\n * Set options for the `Code` extension, or `false` to disable.\n */\n code: Partial<CodeOptions> | false\n\n /**\n * Set options for the `CodeBlock` extension, or `false` to disable.\n */\n codeBlock: Partial<CodeBlockOptions> | false\n\n /**\n * Set options for the `Document` extension, or `false` to disable.\n */\n document: Partial<RichTextDocumentOptions> | false\n\n /**\n * Set options for the `Dropcursor` extension, or `false` to disable.\n */\n dropCursor: Partial<DropcursorOptions> | false\n\n /**\n * Set to `false` to disable the `Gapcursor` extension.\n */\n gapCursor: false\n\n /**\n * Set options for the `HardBreak` extension, or `false` to disable.\n */\n hardBreak: Partial<HardBreakOptions> | false\n\n /**\n * Set options for the `Heading` extension, or `false` to disable.\n */\n heading: Partial<RichTextHeadingOptions> | false\n\n /**\n * Set options for the `History` extension, or `false` to disable.\n */\n history: Partial<HistoryOptions> | false\n\n /**\n * Set options for the `HorizontalRule` extension, or `false` to disable.\n */\n horizontalRule: Partial<HorizontalRuleOptions> | false\n\n /**\n * Set options for the `Image` extension, or `false` to disable.\n */\n image: Partial<RichTextImageOptions> | false\n\n /**\n * Set options for the `Italic` extension, or `false` to disable.\n */\n italic: Partial<ItalicOptions> | false\n\n /**\n * Set options for the `Link` extension, or `false` to disable.\n */\n link: Partial<RichTextLinkOptions> | false\n\n /**\n * Set options for the `ListItem` extension, or `false` to disable.\n */\n listItem: Partial<ListItemOptions> | false\n\n /**\n * Set options for the `ListKeymap` extension, or `false` to disable.\n */\n listKeymap: Partial<ListKeymapOptions> | false\n\n /**\n * Set options for the `OrderedList` extension, or `false` to disable.\n */\n orderedList: Partial<RichTextOrderedListOptions> | false\n\n /**\n * Set options for the `Paragraph` extension, or `false` to disable.\n */\n paragraph: Partial<ParagraphOptions> | false\n\n /**\n * Set to `false` to disable the `PasteEmojis` extension.\n */\n pasteEmojis: false\n\n /**\n * Set to `false` to disable the `PasteMarkdown` extension.\n */\n pasteMarkdown: false\n\n /**\n * Set to `false` to disable the `PasteSinglelineText` extension.\n */\n pasteSinglelineText: false\n\n /**\n * Set to `false` to disable the `PasteHTMLTableAsString` extension.\n */\n pasteHTMLTableAsString: false\n\n /**\n * Set options for the `Strike` extension, or `false` to disable.\n */\n strike: Partial<RichTextStrikethroughOptions> | false\n\n /**\n * Set to `false` to disable the `Text` extension.\n */\n text: false\n\n /**\n * Set to `false` to disable the `Typography` extension.\n */\n typography: false\n}\n\n/**\n * The `RichTextKit` extension is a collection of the minimal required extensions to have a full\n * WYSIWYG text editor working. This extension is based on the official `StarterKit` extension\n * implementation, allowing almost every extension to be customized or disabled.\n */\nconst RichTextKit = Extension.create<RichTextKitOptions>({\n name: 'richTextKit',\n addExtensions() {\n const extensions: Extensions = []\n\n if (this.options.blockquote !== false) {\n extensions.push(\n Blockquote.extend({\n priority: BLOCKQUOTE_EXTENSION_PRIORITY,\n }).configure(this.options?.blockquote),\n )\n }\n\n if (this.options.bold !== false) {\n extensions.push(Bold.configure(this.options?.bold))\n }\n\n if (this.options.bulletList !== false) {\n extensions.push(RichTextBulletList.configure(this.options?.bulletList))\n }\n\n if (this.options.code !== false) {\n extensions.push(\n RichTextCode.configure(this.options?.code),\n\n // Enhances the Code extension capabilities with additional features\n CurvenoteCodemark,\n )\n }\n\n if (this.options.codeBlock !== false) {\n extensions.push(CodeBlock.configure(this.options?.codeBlock))\n }\n\n if (this.options.document !== false) {\n extensions.push(\n RichTextDocument.configure(this.options?.document),\n\n // Supports copying the underlying Markdown source to the clipboard\n CopyMarkdownSource.configure({\n keyboardShortcut: 'Mod-Shift-c',\n }),\n )\n\n if (this.options?.pasteEmojis !== false) {\n // Supports pasting HTML image emojis as unicode characters\n extensions.push(PasteEmojis)\n }\n\n if (this.options?.pasteMarkdown !== false) {\n // Supports pasting Markdown content as HTML into the editor\n extensions.push(PasteMarkdown)\n }\n\n if (\n this.options?.document?.multiline === false &&\n this.options?.pasteSinglelineText !== false\n ) {\n // Supports pasting multiple lines into a singleline editor, by joining all the\n // pasted lines together\n extensions.push(PasteSinglelineText)\n }\n\n if (this.options?.pasteHTMLTableAsString !== false) {\n // Supports pasting tables (from spreadsheets and websites) into the editor\n extensions.push(PasteHTMLTableAsString)\n }\n }\n\n if (this.options.dropCursor !== false) {\n extensions.push(Dropcursor.configure(this.options?.dropCursor))\n }\n\n if (this.options.gapCursor !== false) {\n extensions.push(Gapcursor)\n }\n\n if (this.options.hardBreak !== false) {\n extensions.push(HardBreak.configure(this.options?.hardBreak))\n }\n\n if (this.options.heading !== false) {\n extensions.push(RichTextHeading.configure(this.options?.heading))\n }\n\n if (this.options.history !== false) {\n extensions.push(History.configure(this.options?.history))\n }\n\n if (this.options.horizontalRule !== false) {\n extensions.push(HorizontalRule.configure(this.options?.horizontalRule))\n }\n\n if (this.options.image !== false) {\n extensions.push(RichTextImage.configure(this.options?.image))\n }\n\n if (this.options.italic !== false) {\n extensions.push(Italic.configure(this.options?.italic))\n }\n\n if (this.options.bold !== false && this.options.italic !== false) {\n extensions.push(BoldAndItalics)\n }\n\n if (this.options.link !== false) {\n extensions.push(RichTextLink.configure(this.options?.link))\n }\n\n if (this.options.listItem !== false) {\n extensions.push(ListItem.configure(this.options?.listItem))\n }\n\n if (this.options.listKeymap !== false) {\n extensions.push(ListKeymap.configure(this.options?.listKeymap))\n }\n\n if (this.options.orderedList !== false) {\n extensions.push(RichTextOrderedList.configure(this.options?.orderedList))\n }\n\n if (this.options.paragraph !== false) {\n extensions.push(Paragraph.configure(this.options?.paragraph))\n }\n\n if (this.options.strike !== false) {\n extensions.push(RichTextStrikethrough.configure(this.options?.strike))\n }\n\n if (this.options.text !== false) {\n extensions.push(Text)\n }\n\n if (this.options.typography !== false) {\n extensions.push(Typography)\n }\n\n return extensions\n },\n})\n\nexport { RichTextKit }\n\nexport type { RichTextKitOptions }\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkMA,MAAM,cAAc,UAAU,OAA2B;CACrD,MAAM;CACN,gBAAgB;EACZ,MAAM,aAAyB,EAAE;EAEjC,IAAI,KAAK,QAAQ,eAAe,OAC5B,WAAW,KACP,WAAW,OAAO,EACd,UAAA,KACH,CAAC,CAAC,UAAU,KAAK,SAAS,WAAW,CACzC;EAGL,IAAI,KAAK,QAAQ,SAAS,OACtB,WAAW,KAAK,KAAK,UAAU,KAAK,SAAS,KAAK,CAAC;EAGvD,IAAI,KAAK,QAAQ,eAAe,OAC5B,WAAW,KAAK,mBAAmB,UAAU,KAAK,SAAS,WAAW,CAAC;EAG3E,IAAI,KAAK,QAAQ,SAAS,OACtB,WAAW,KACP,aAAa,UAAU,KAAK,SAAS,KAAK,EAG1C,kBACH;EAGL,IAAI,KAAK,QAAQ,cAAc,OAC3B,WAAW,KAAK,UAAU,UAAU,KAAK,SAAS,UAAU,CAAC;EAGjE,IAAI,KAAK,QAAQ,aAAa,OAAO;GACjC,WAAW,KACP,iBAAiB,UAAU,KAAK,SAAS,SAAS,EAGlD,mBAAmB,UAAU,EACzB,kBAAkB,eACrB,CAAC,CACL;GAED,IAAI,KAAK,SAAS,gBAAgB,OAE9B,WAAW,KAAK,YAAY;GAGhC,IAAI,KAAK,SAAS,kBAAkB,OAEhC,WAAW,KAAK,cAAc;GAGlC,IACI,KAAK,SAAS,UAAU,cAAc,SACtC,KAAK,SAAS,wBAAwB,OAItC,WAAW,KAAK,oBAAoB;GAGxC,IAAI,KAAK,SAAS,2BAA2B,OAEzC,WAAW,KAAK,uBAAuB;;EAI/C,IAAI,KAAK,QAAQ,eAAe,OAC5B,WAAW,KAAK,WAAW,UAAU,KAAK,SAAS,WAAW,CAAC;EAGnE,IAAI,KAAK,QAAQ,cAAc,OAC3B,WAAW,KAAK,UAAU;EAG9B,IAAI,KAAK,QAAQ,cAAc,OAC3B,WAAW,KAAK,UAAU,UAAU,KAAK,SAAS,UAAU,CAAC;EAGjE,IAAI,KAAK,QAAQ,YAAY,OACzB,WAAW,KAAK,gBAAgB,UAAU,KAAK,SAAS,QAAQ,CAAC;EAGrE,IAAI,KAAK,QAAQ,YAAY,OACzB,WAAW,KAAK,QAAQ,UAAU,KAAK,SAAS,QAAQ,CAAC;EAG7D,IAAI,KAAK,QAAQ,mBAAmB,OAChC,WAAW,KAAK,eAAe,UAAU,KAAK,SAAS,eAAe,CAAC;EAG3E,IAAI,KAAK,QAAQ,UAAU,OACvB,WAAW,KAAK,cAAc,UAAU,KAAK,SAAS,MAAM,CAAC;EAGjE,IAAI,KAAK,QAAQ,WAAW,OACxB,WAAW,KAAK,OAAO,UAAU,KAAK,SAAS,OAAO,CAAC;EAG3D,IAAI,KAAK,QAAQ,SAAS,SAAS,KAAK,QAAQ,WAAW,OACvD,WAAW,KAAK,eAAe;EAGnC,IAAI,KAAK,QAAQ,SAAS,OACtB,WAAW,KAAK,aAAa,UAAU,KAAK,SAAS,KAAK,CAAC;EAG/D,IAAI,KAAK,QAAQ,aAAa,OAC1B,WAAW,KAAK,SAAS,UAAU,KAAK,SAAS,SAAS,CAAC;EAG/D,IAAI,KAAK,QAAQ,eAAe,OAC5B,WAAW,KAAK,WAAW,UAAU,KAAK,SAAS,WAAW,CAAC;EAGnE,IAAI,KAAK,QAAQ,gBAAgB,OAC7B,WAAW,KAAK,oBAAoB,UAAU,KAAK,SAAS,YAAY,CAAC;EAG7E,IAAI,KAAK,QAAQ,cAAc,OAC3B,WAAW,KAAK,UAAU,UAAU,KAAK,SAAS,UAAU,CAAC;EAGjE,IAAI,KAAK,QAAQ,WAAW,OACxB,WAAW,KAAK,sBAAsB,UAAU,KAAK,SAAS,OAAO,CAAC;EAG1E,IAAI,KAAK,QAAQ,SAAS,OACtB,WAAW,KAAK,KAAK;EAGzB,IAAI,KAAK,QAAQ,eAAe,OAC5B,WAAW,KAAK,WAAW;EAG/B,OAAO;;CAEd,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rich-text-link.js","names":[],"sources":["../../../src/extensions/rich-text/rich-text-link.ts"],"sourcesContent":["import { InputRule, markInputRule, markPasteRule, PasteRule } from '@tiptap/core'\nimport { Link } from '@tiptap/extension-link'\n\nimport type { LinkOptions } from '@tiptap/extension-link'\n\n/**\n * The input regex for Markdown links with title support, and multiple quotation marks (required\n * in case the `Typography` extension is being included).\n */\nconst inputRegex = /(?:^|\\s)\\[([^\\]]*)?\\]\\((\\S+)(?: [\"“](.+)[\"”])?\\)$/i\n\n/**\n * The paste regex for Markdown links with title support, and multiple quotation marks (required\n * in case the `Typography` extension is being included).\n */\nconst pasteRegex = /(?:^|\\s)\\[([^\\]]*)?\\]\\((\\S+)(?: [\"“](.+)[\"”])?\\)/gi\n\n/**\n * Input rule built specifically for the `Link` extension, which ignores the auto-linked URL in\n * parentheses (e.g., `(https://doist.dev)`).\n *\n * @see https://github.com/ueberdosis/tiptap/discussions/1865\n */\nfunction linkInputRule(config: Parameters<typeof markInputRule>[0]) {\n const defaultMarkInputRule = markInputRule(config)\n\n return new InputRule({\n find: config.find,\n handler(props) {\n const { tr } = props.state\n\n defaultMarkInputRule.handler(props)\n tr.setMeta('preventAutolink', true)\n },\n })\n}\n\n/**\n * Paste rule built specifically for the `Link` extension, which ignores the auto-linked URL in\n * parentheses (e.g., `(https://doist.dev)`). This extension was inspired from the multiple\n * implementations found in a Tiptap discussion at GitHub.\n *\n * @see https://github.com/ueberdosis/tiptap/discussions/1865\n */\nfunction linkPasteRule(config: Parameters<typeof markPasteRule>[0]) {\n const defaultMarkPasteRule = markPasteRule(config)\n\n return new PasteRule({\n find: config.find,\n handler(props) {\n const { tr } = props.state\n\n defaultMarkPasteRule.handler(props)\n tr.setMeta('preventAutolink', true)\n },\n })\n}\n\n/**\n * The options available to customize the `RichTextLink` extension.\n */\ntype RichTextLinkOptions = LinkOptions\n\n/**\n * Custom extension that extends the built-in `Link` extension to add additional input/paste rules\n * for converting the Markdown link syntax (i.e. `[Doist](https://doist.com)`) into links, and also\n * adds support for the `title` attribute.\n */\nconst RichTextLink = Link.extend<RichTextLinkOptions>({\n inclusive: false,\n addOptions() {\n return {\n ...this.parent?.(),\n openOnClick: 'whenNotEditable',\n }\n },\n addAttributes() {\n return {\n ...this.parent?.(),\n title: {\n default: null,\n },\n }\n },\n addInputRules() {\n return [\n linkInputRule({\n find: inputRegex,\n type: this.type,\n\n // We need to use `pop()` to remove the last capture groups from the match to\n // satisfy Tiptap's `markPasteRule` expectation of having the content as the last\n // capture group in the match (this makes the attribute order important)\n getAttributes(match) {\n return {\n title: match.pop()?.trim(),\n href: match.pop()?.trim(),\n }\n },\n }),\n ]\n },\n addPasteRules() {\n return [\n linkPasteRule({\n find: pasteRegex,\n type: this.type,\n\n // We need to use `pop()` to remove the last capture groups from the match to\n // satisfy Tiptap's `markInputRule` expectation of having the content as the last\n // capture group in the match (this makes the attribute order important)\n getAttributes(match) {\n return {\n title: match.pop()?.trim(),\n href: match.pop()?.trim(),\n }\n },\n }),\n ]\n },\n})\n\nexport { RichTextLink }\n\nexport type { RichTextLinkOptions }\n"],"mappings":";;;;;;;AASA,MAAM,aAAa;;;;;AAMnB,MAAM,aAAa;;;;;;;AAQnB,SAAS,cAAc,QAA6C;CAChE,MAAM,uBAAuB,cAAc,OAAO;
|
|
1
|
+
{"version":3,"file":"rich-text-link.js","names":[],"sources":["../../../src/extensions/rich-text/rich-text-link.ts"],"sourcesContent":["import { InputRule, markInputRule, markPasteRule, PasteRule } from '@tiptap/core'\nimport { Link } from '@tiptap/extension-link'\n\nimport type { LinkOptions } from '@tiptap/extension-link'\n\n/**\n * The input regex for Markdown links with title support, and multiple quotation marks (required\n * in case the `Typography` extension is being included).\n */\nconst inputRegex = /(?:^|\\s)\\[([^\\]]*)?\\]\\((\\S+)(?: [\"“](.+)[\"”])?\\)$/i\n\n/**\n * The paste regex for Markdown links with title support, and multiple quotation marks (required\n * in case the `Typography` extension is being included).\n */\nconst pasteRegex = /(?:^|\\s)\\[([^\\]]*)?\\]\\((\\S+)(?: [\"“](.+)[\"”])?\\)/gi\n\n/**\n * Input rule built specifically for the `Link` extension, which ignores the auto-linked URL in\n * parentheses (e.g., `(https://doist.dev)`).\n *\n * @see https://github.com/ueberdosis/tiptap/discussions/1865\n */\nfunction linkInputRule(config: Parameters<typeof markInputRule>[0]) {\n const defaultMarkInputRule = markInputRule(config)\n\n return new InputRule({\n find: config.find,\n handler(props) {\n const { tr } = props.state\n\n defaultMarkInputRule.handler(props)\n tr.setMeta('preventAutolink', true)\n },\n })\n}\n\n/**\n * Paste rule built specifically for the `Link` extension, which ignores the auto-linked URL in\n * parentheses (e.g., `(https://doist.dev)`). This extension was inspired from the multiple\n * implementations found in a Tiptap discussion at GitHub.\n *\n * @see https://github.com/ueberdosis/tiptap/discussions/1865\n */\nfunction linkPasteRule(config: Parameters<typeof markPasteRule>[0]) {\n const defaultMarkPasteRule = markPasteRule(config)\n\n return new PasteRule({\n find: config.find,\n handler(props) {\n const { tr } = props.state\n\n defaultMarkPasteRule.handler(props)\n tr.setMeta('preventAutolink', true)\n },\n })\n}\n\n/**\n * The options available to customize the `RichTextLink` extension.\n */\ntype RichTextLinkOptions = LinkOptions\n\n/**\n * Custom extension that extends the built-in `Link` extension to add additional input/paste rules\n * for converting the Markdown link syntax (i.e. `[Doist](https://doist.com)`) into links, and also\n * adds support for the `title` attribute.\n */\nconst RichTextLink = Link.extend<RichTextLinkOptions>({\n inclusive: false,\n addOptions() {\n return {\n ...this.parent?.(),\n openOnClick: 'whenNotEditable',\n }\n },\n addAttributes() {\n return {\n ...this.parent?.(),\n title: {\n default: null,\n },\n }\n },\n addInputRules() {\n return [\n linkInputRule({\n find: inputRegex,\n type: this.type,\n\n // We need to use `pop()` to remove the last capture groups from the match to\n // satisfy Tiptap's `markPasteRule` expectation of having the content as the last\n // capture group in the match (this makes the attribute order important)\n getAttributes(match) {\n return {\n title: match.pop()?.trim(),\n href: match.pop()?.trim(),\n }\n },\n }),\n ]\n },\n addPasteRules() {\n return [\n linkPasteRule({\n find: pasteRegex,\n type: this.type,\n\n // We need to use `pop()` to remove the last capture groups from the match to\n // satisfy Tiptap's `markInputRule` expectation of having the content as the last\n // capture group in the match (this makes the attribute order important)\n getAttributes(match) {\n return {\n title: match.pop()?.trim(),\n href: match.pop()?.trim(),\n }\n },\n }),\n ]\n },\n})\n\nexport { RichTextLink }\n\nexport type { RichTextLinkOptions }\n"],"mappings":";;;;;;;AASA,MAAM,aAAa;;;;;AAMnB,MAAM,aAAa;;;;;;;AAQnB,SAAS,cAAc,QAA6C;CAChE,MAAM,uBAAuB,cAAc,OAAO;CAElD,OAAO,IAAI,UAAU;EACjB,MAAM,OAAO;EACb,QAAQ,OAAO;GACX,MAAM,EAAE,OAAO,MAAM;GAErB,qBAAqB,QAAQ,MAAM;GACnC,GAAG,QAAQ,mBAAmB,KAAK;;EAE1C,CAAC;;;;;;;;;AAUN,SAAS,cAAc,QAA6C;CAChE,MAAM,uBAAuB,cAAc,OAAO;CAElD,OAAO,IAAI,UAAU;EACjB,MAAM,OAAO;EACb,QAAQ,OAAO;GACX,MAAM,EAAE,OAAO,MAAM;GAErB,qBAAqB,QAAQ,MAAM;GACnC,GAAG,QAAQ,mBAAmB,KAAK;;EAE1C,CAAC;;;;;;;AAaN,MAAM,eAAe,KAAK,OAA4B;CAClD,WAAW;CACX,aAAa;EACT,OAAO;GACH,GAAG,KAAK,UAAU;GAClB,aAAa;GAChB;;CAEL,gBAAgB;EACZ,OAAO;GACH,GAAG,KAAK,UAAU;GAClB,OAAO,EACH,SAAS,MACZ;GACJ;;CAEL,gBAAgB;EACZ,OAAO,CACH,cAAc;GACV,MAAM;GACN,MAAM,KAAK;GAKX,cAAc,OAAO;IACjB,OAAO;KACH,OAAO,MAAM,KAAK,EAAE,MAAM;KAC1B,MAAM,MAAM,KAAK,EAAE,MAAM;KAC5B;;GAER,CAAC,CACL;;CAEL,gBAAgB;EACZ,OAAO,CACH,cAAc;GACV,MAAM;GACN,MAAM,KAAK;GAKX,cAAc,OAAO;IACjB,OAAO;KACH,OAAO,MAAM,KAAK,EAAE,MAAM;KAC1B,MAAM,MAAM,KAAK,EAAE,MAAM;KAC5B;;GAER,CAAC,CACL;;CAER,CAAC"}
|
|
@@ -29,7 +29,7 @@ const RichTextOrderedList = OrderedList.extend({
|
|
|
29
29
|
tr.doc.nodesBetween($from.pos, $to.pos, (node, pos) => {
|
|
30
30
|
if (node.type.name === "hardBreak") hardBreakPositions.push(pos);
|
|
31
31
|
});
|
|
32
|
-
hardBreakPositions.
|
|
32
|
+
hardBreakPositions.toReversed().forEach((pos) => {
|
|
33
33
|
tr.replace(pos, pos + 1, Slice.maxOpen(Fragment.fromArray([schema.nodes.paragraph.create(), schema.nodes.paragraph.create()])));
|
|
34
34
|
});
|
|
35
35
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rich-text-ordered-list.js","names":[],"sources":["../../../src/extensions/rich-text/rich-text-ordered-list.ts"],"sourcesContent":["import { ListItem } from '@tiptap/extension-list-item'\nimport { OrderedList } from '@tiptap/extension-ordered-list'\nimport { TextStyle } from '@tiptap/extension-text-style'\nimport { Fragment, Slice } from '@tiptap/pm/model'\n\nimport type { OrderedListOptions } from '@tiptap/extension-ordered-list'\n\n/**\n * The options available to customize the `RichTextOrderedList` extension.\n */\ntype RichTextOrderedListOptions = {\n /**\n * Replace hard breaks in the selection with paragraphs before toggling the selection into a\n * bullet list. By default, hard breaks are not replaced.\n */\n smartToggle: boolean\n} & OrderedListOptions\n\n/**\n * Custom extension that extends the built-in `OrderedList` extension to add an option for smart\n * toggling, which takes into account hard breaks in the selection, and converts them into\n * paragraphs before toggling the selection into a bullet list.\n */\nconst RichTextOrderedList = OrderedList.extend<RichTextOrderedListOptions>({\n addOptions() {\n return {\n ...this.parent?.(),\n smartToggle: false,\n }\n },\n\n addCommands() {\n const { editor, name, options } = this\n\n return {\n ...this.parent?.(),\n toggleOrderedList() {\n return ({ commands, state, tr, chain }) => {\n // Replace hard breaks in the selection with paragraphs before toggling?\n if (options.smartToggle) {\n const { schema } = state\n const { selection } = tr\n const { $from, $to } = selection\n\n const hardBreakPositions: number[] = []\n\n // Find and store the positions of all hard breaks in the selection\n tr.doc.nodesBetween($from.pos, $to.pos, (node, pos) => {\n if (node.type.name === 'hardBreak') {\n hardBreakPositions.push(pos)\n }\n })\n\n // Replace each hard break with a slice that closes and re-opens a paragraph,\n // effectively inserting a \"paragraph break\" in place of a \"hard break\"\n // (this is performed in reverse order to compensate for content shifting that\n // occurs with each replacement, ensuring accurate insertion points)\n hardBreakPositions.
|
|
1
|
+
{"version":3,"file":"rich-text-ordered-list.js","names":[],"sources":["../../../src/extensions/rich-text/rich-text-ordered-list.ts"],"sourcesContent":["import { ListItem } from '@tiptap/extension-list-item'\nimport { OrderedList } from '@tiptap/extension-ordered-list'\nimport { TextStyle } from '@tiptap/extension-text-style'\nimport { Fragment, Slice } from '@tiptap/pm/model'\n\nimport type { OrderedListOptions } from '@tiptap/extension-ordered-list'\n\n/**\n * The options available to customize the `RichTextOrderedList` extension.\n */\ntype RichTextOrderedListOptions = {\n /**\n * Replace hard breaks in the selection with paragraphs before toggling the selection into a\n * bullet list. By default, hard breaks are not replaced.\n */\n smartToggle: boolean\n} & OrderedListOptions\n\n/**\n * Custom extension that extends the built-in `OrderedList` extension to add an option for smart\n * toggling, which takes into account hard breaks in the selection, and converts them into\n * paragraphs before toggling the selection into a bullet list.\n */\nconst RichTextOrderedList = OrderedList.extend<RichTextOrderedListOptions>({\n addOptions() {\n return {\n ...this.parent?.(),\n smartToggle: false,\n }\n },\n\n addCommands() {\n const { editor, name, options } = this\n\n return {\n ...this.parent?.(),\n toggleOrderedList() {\n return ({ commands, state, tr, chain }) => {\n // Replace hard breaks in the selection with paragraphs before toggling?\n if (options.smartToggle) {\n const { schema } = state\n const { selection } = tr\n const { $from, $to } = selection\n\n const hardBreakPositions: number[] = []\n\n // Find and store the positions of all hard breaks in the selection\n tr.doc.nodesBetween($from.pos, $to.pos, (node, pos) => {\n if (node.type.name === 'hardBreak') {\n hardBreakPositions.push(pos)\n }\n })\n\n // Replace each hard break with a slice that closes and re-opens a paragraph,\n // effectively inserting a \"paragraph break\" in place of a \"hard break\"\n // (this is performed in reverse order to compensate for content shifting that\n // occurs with each replacement, ensuring accurate insertion points)\n hardBreakPositions.toReversed().forEach((pos) => {\n tr.replace(\n pos,\n pos + 1,\n Slice.maxOpen(\n Fragment.fromArray([\n schema.nodes.paragraph.create(),\n schema.nodes.paragraph.create(),\n ]),\n ),\n )\n })\n }\n\n // Toggle the selection into a bullet list, optionally keeping attributes\n // (this is a verbatim copy of the built-in `toggleBulletList` command)\n\n if (options.keepAttributes) {\n return chain()\n .toggleList(name, options.itemTypeName, options.keepMarks)\n .updateAttributes(ListItem.name, editor.getAttributes(TextStyle.name))\n .run()\n }\n\n return commands.toggleList(name, options.itemTypeName, options.keepMarks)\n }\n },\n }\n },\n})\n\nexport { RichTextOrderedList }\n\nexport type { RichTextOrderedListOptions }\n"],"mappings":";;;;;;;;;;AAuBA,MAAM,sBAAsB,YAAY,OAAmC;CACvE,aAAa;EACT,OAAO;GACH,GAAG,KAAK,UAAU;GAClB,aAAa;GAChB;;CAGL,cAAc;EACV,MAAM,EAAE,QAAQ,MAAM,YAAY;EAElC,OAAO;GACH,GAAG,KAAK,UAAU;GAClB,oBAAoB;IAChB,QAAQ,EAAE,UAAU,OAAO,IAAI,YAAY;KAEvC,IAAI,QAAQ,aAAa;MACrB,MAAM,EAAE,WAAW;MACnB,MAAM,EAAE,cAAc;MACtB,MAAM,EAAE,OAAO,QAAQ;MAEvB,MAAM,qBAA+B,EAAE;MAGvC,GAAG,IAAI,aAAa,MAAM,KAAK,IAAI,MAAM,MAAM,QAAQ;OACnD,IAAI,KAAK,KAAK,SAAS,aACnB,mBAAmB,KAAK,IAAI;QAElC;MAMF,mBAAmB,YAAY,CAAC,SAAS,QAAQ;OAC7C,GAAG,QACC,KACA,MAAM,GACN,MAAM,QACF,SAAS,UAAU,CACf,OAAO,MAAM,UAAU,QAAQ,EAC/B,OAAO,MAAM,UAAU,QAAQ,CAClC,CAAC,CACL,CACJ;QACH;;KAMN,IAAI,QAAQ,gBACR,OAAO,OAAO,CACT,WAAW,MAAM,QAAQ,cAAc,QAAQ,UAAU,CACzD,iBAAiB,SAAS,MAAM,OAAO,cAAc,UAAU,KAAK,CAAC,CACrE,KAAK;KAGd,OAAO,SAAS,WAAW,MAAM,QAAQ,cAAc,QAAQ,UAAU;;;GAGpF;;CAER,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rich-text-strikethrough.js","names":[],"sources":["../../../src/extensions/rich-text/rich-text-strikethrough.ts"],"sourcesContent":["import { Strike } from '@tiptap/extension-strike'\n\nimport type { StrikeOptions } from '@tiptap/extension-strike'\n\n/**\n * The options available to customize the `RichTextStrikethrough` extension.\n */\ntype RichTextStrikethroughOptions = StrikeOptions\n\n/**\n * Custom extension that extends the built-in `Strike` extension to overwrite the default keyboard.\n */\nconst RichTextStrikethrough = Strike.extend<RichTextStrikethroughOptions>({\n addKeyboardShortcuts() {\n return {\n 'Mod-Shift-x': () => this.editor.commands.toggleStrike(),\n }\n },\n})\n\nexport { RichTextStrikethrough }\n\nexport type { RichTextStrikethroughOptions }\n"],"mappings":";;;;;AAYA,MAAM,wBAAwB,OAAO,OAAqC,EACtE,uBAAuB;
|
|
1
|
+
{"version":3,"file":"rich-text-strikethrough.js","names":[],"sources":["../../../src/extensions/rich-text/rich-text-strikethrough.ts"],"sourcesContent":["import { Strike } from '@tiptap/extension-strike'\n\nimport type { StrikeOptions } from '@tiptap/extension-strike'\n\n/**\n * The options available to customize the `RichTextStrikethrough` extension.\n */\ntype RichTextStrikethroughOptions = StrikeOptions\n\n/**\n * Custom extension that extends the built-in `Strike` extension to overwrite the default keyboard.\n */\nconst RichTextStrikethrough = Strike.extend<RichTextStrikethroughOptions>({\n addKeyboardShortcuts() {\n return {\n 'Mod-Shift-x': () => this.editor.commands.toggleStrike(),\n }\n },\n})\n\nexport { RichTextStrikethrough }\n\nexport type { RichTextStrikethroughOptions }\n"],"mappings":";;;;;AAYA,MAAM,wBAAwB,OAAO,OAAqC,EACtE,uBAAuB;CACnB,OAAO,EACH,qBAAqB,KAAK,OAAO,SAAS,cAAc,EAC3D;GAER,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"copy-markdown-source.js","names":[],"sources":["../../../src/extensions/shared/copy-markdown-source.ts"],"sourcesContent":["import { Extension, getHTMLFromFragment } from '@tiptap/core'\n\nimport { getMarkdownSerializerInstance } from '../../serializers/markdown/markdown'\n\n/**\n * The options available to customize the `CopyMarkdownSource` extension.\n */\ntype CopyMarkdownSourceOptions = {\n /**\n * The keyboard shortcut to copy the editor underlying Markdown source to the system clipboard\n * (default: `Mod-c`).\n */\n keyboardShortcut: string\n}\n\n/**\n * The `CopyMarkdownSource` extension adds the ability to copy the editor underlying Markdown\n * source, and write it to the system clipboard. This extension has full support for both the\n * plain-text and rich-text editors, considering that it's powered by the Markdown serializer.\n */\nconst CopyMarkdownSource = Extension.create<CopyMarkdownSourceOptions>({\n name: 'copyMarkdownSource',\n addOptions() {\n return {\n keyboardShortcut: 'Mod-c',\n }\n },\n addKeyboardShortcuts() {\n return {\n [this.options.keyboardShortcut]: ({ editor }) => {\n // Get a fragment of the editor's content based on the selection\n const nodeSelection = editor.state.doc.cut(\n editor.state.selection.from,\n editor.state.selection.to,\n )\n\n // Serialize the selected content HTML to Markdown\n const markdownContent = getMarkdownSerializerInstance(editor.schema).serialize(\n getHTMLFromFragment(nodeSelection.content, editor.schema),\n )\n\n // Writes the selected Markdown content to the system clipboard\n navigator?.clipboard\n ?.writeText(markdownContent)\n // No need to handle the success of the writeText call\n .then(() => undefined)\n // No need to handle the failure of the writeText call\n .catch(() => undefined)\n\n // Suppress the default handling behaviour\n return true\n },\n }\n },\n})\n\nexport { CopyMarkdownSource }\n\nexport type { CopyMarkdownSourceOptions }\n"],"mappings":";;;;;;;;AAoBA,MAAM,qBAAqB,UAAU,OAAkC;CACnE,MAAM;CACN,aAAa;
|
|
1
|
+
{"version":3,"file":"copy-markdown-source.js","names":[],"sources":["../../../src/extensions/shared/copy-markdown-source.ts"],"sourcesContent":["import { Extension, getHTMLFromFragment } from '@tiptap/core'\n\nimport { getMarkdownSerializerInstance } from '../../serializers/markdown/markdown'\n\n/**\n * The options available to customize the `CopyMarkdownSource` extension.\n */\ntype CopyMarkdownSourceOptions = {\n /**\n * The keyboard shortcut to copy the editor underlying Markdown source to the system clipboard\n * (default: `Mod-c`).\n */\n keyboardShortcut: string\n}\n\n/**\n * The `CopyMarkdownSource` extension adds the ability to copy the editor underlying Markdown\n * source, and write it to the system clipboard. This extension has full support for both the\n * plain-text and rich-text editors, considering that it's powered by the Markdown serializer.\n */\nconst CopyMarkdownSource = Extension.create<CopyMarkdownSourceOptions>({\n name: 'copyMarkdownSource',\n addOptions() {\n return {\n keyboardShortcut: 'Mod-c',\n }\n },\n addKeyboardShortcuts() {\n return {\n [this.options.keyboardShortcut]: ({ editor }) => {\n // Get a fragment of the editor's content based on the selection\n const nodeSelection = editor.state.doc.cut(\n editor.state.selection.from,\n editor.state.selection.to,\n )\n\n // Serialize the selected content HTML to Markdown\n const markdownContent = getMarkdownSerializerInstance(editor.schema).serialize(\n getHTMLFromFragment(nodeSelection.content, editor.schema),\n )\n\n // Writes the selected Markdown content to the system clipboard\n navigator?.clipboard\n ?.writeText(markdownContent)\n // No need to handle the success of the writeText call\n .then(() => undefined)\n // No need to handle the failure of the writeText call\n .catch(() => undefined)\n\n // Suppress the default handling behaviour\n return true\n },\n }\n },\n})\n\nexport { CopyMarkdownSource }\n\nexport type { CopyMarkdownSourceOptions }\n"],"mappings":";;;;;;;;AAoBA,MAAM,qBAAqB,UAAU,OAAkC;CACnE,MAAM;CACN,aAAa;EACT,OAAO,EACH,kBAAkB,SACrB;;CAEL,uBAAuB;EACnB,OAAO,GACF,KAAK,QAAQ,oBAAoB,EAAE,aAAa;GAE7C,MAAM,gBAAgB,OAAO,MAAM,IAAI,IACnC,OAAO,MAAM,UAAU,MACvB,OAAO,MAAM,UAAU,GAC1B;GAGD,MAAM,kBAAkB,8BAA8B,OAAO,OAAO,CAAC,UACjE,oBAAoB,cAAc,SAAS,OAAO,OAAO,CAC5D;GAGD,WAAW,WACL,UAAU,gBAAgB,CAE3B,WAAW,KAAA,EAAU,CAErB,YAAY,KAAA,EAAU;GAG3B,OAAO;KAEd;;CAER,CAAC"}
|
|
@@ -13,8 +13,8 @@ function transformPastedHTML(html) {
|
|
|
13
13
|
for (const table of Array.from(tables)) {
|
|
14
14
|
if (!table.rows) continue;
|
|
15
15
|
const paragraphs = Array.from(table.rows).map((row) => Array.from(row.cells).map((cell) => {
|
|
16
|
-
const
|
|
17
|
-
for (const p of Array.from(
|
|
16
|
+
const cellParagraphs = cell.querySelectorAll("p");
|
|
17
|
+
for (const p of Array.from(cellParagraphs)) p.replaceWith(...Array.from(p.childNodes));
|
|
18
18
|
return cell.innerHTML;
|
|
19
19
|
}).join(" ")).filter((row) => row.trim().length > 0).map((row) => {
|
|
20
20
|
const p = document.createElement("p");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"paste-html-table-as-string.js","names":[],"sources":["../../../src/extensions/shared/paste-html-table-as-string.ts"],"sourcesContent":["import { Extension } from '@tiptap/core'\nimport { Plugin, PluginKey } from '@tiptap/pm/state'\n\nimport { PASTE_HTML_TABLE_AS_STRING_EXTENSION_PRIORITY } from '../../constants/extension-priorities'\nimport { parseHtmlToElement } from '../../helpers/dom'\n\n/**\n * Transforms pasted HTML by converting tables to paragraphs while preserving surrounding content.\n */\nfunction transformPastedHTML(html: string): string {\n const body = parseHtmlToElement(html)\n const tables = body.querySelectorAll('table')\n\n if (tables.length === 0) {\n return html\n }\n\n for (const table of Array.from(tables)) {\n if (!table.rows) {\n continue\n }\n\n // Convert table rows to paragraphs (using innerHTML to preserve formatting)\n const paragraphs = Array.from(table.rows)\n .map((row) =>\n Array.from(row.cells)\n .map((cell) => {\n // Unwrap paragraphs but preserve inline formatting\n const
|
|
1
|
+
{"version":3,"file":"paste-html-table-as-string.js","names":[],"sources":["../../../src/extensions/shared/paste-html-table-as-string.ts"],"sourcesContent":["import { Extension } from '@tiptap/core'\nimport { Plugin, PluginKey } from '@tiptap/pm/state'\n\nimport { PASTE_HTML_TABLE_AS_STRING_EXTENSION_PRIORITY } from '../../constants/extension-priorities'\nimport { parseHtmlToElement } from '../../helpers/dom'\n\n/**\n * Transforms pasted HTML by converting tables to paragraphs while preserving surrounding content.\n */\nfunction transformPastedHTML(html: string): string {\n const body = parseHtmlToElement(html)\n const tables = body.querySelectorAll('table')\n\n if (tables.length === 0) {\n return html\n }\n\n for (const table of Array.from(tables)) {\n if (!table.rows) {\n continue\n }\n\n // Convert table rows to paragraphs (using innerHTML to preserve formatting)\n const paragraphs = Array.from(table.rows)\n .map((row) =>\n Array.from(row.cells)\n .map((cell) => {\n // Unwrap paragraphs but preserve inline formatting\n const cellParagraphs = cell.querySelectorAll('p')\n\n for (const p of Array.from(cellParagraphs)) {\n p.replaceWith(...Array.from(p.childNodes))\n }\n\n return cell.innerHTML\n })\n .join(' '),\n )\n .filter((row) => row.trim().length > 0)\n .map((row) => {\n const p = document.createElement('p')\n p.innerHTML = row\n return p\n })\n\n table.replaceWith(...paragraphs)\n }\n\n return body.innerHTML\n}\n\n/**\n * The `PasteHTMLTableAsString` extension adds the ability to paste a table copied from a spreadsheet\n * web app (e.g., Google Sheets, Microsoft Excel), along with tables rendered by GitHub Flavored\n * Markdown (GFM), into the editor.\n *\n * Since Typist does not yet support tables, this extension simply pastes the table as a string of\n * paragraphs (one paragraph per row), with each cell separated by a space character. However,\n * whenever we do add support for tables, this extension will need to be completely rewritten.\n *\n * Lastly, please note that formatting is lost when the copied table comes from Google Sheets or\n * Microsoft Excel, because unfortunately, these apps style the cell contents using CSS.\n */\nconst PasteHTMLTableAsString = Extension.create({\n name: 'pasteHTMLTableAsString',\n priority: PASTE_HTML_TABLE_AS_STRING_EXTENSION_PRIORITY,\n addProseMirrorPlugins() {\n return [\n new Plugin({\n key: new PluginKey('pasteHTMLTableAsString'),\n props: {\n transformPastedHTML,\n },\n }),\n ]\n },\n})\n\nexport { PasteHTMLTableAsString, transformPastedHTML }\n"],"mappings":";;;;;;;;AASA,SAAS,oBAAoB,MAAsB;CAC/C,MAAM,OAAO,mBAAmB,KAAK;CACrC,MAAM,SAAS,KAAK,iBAAiB,QAAQ;CAE7C,IAAI,OAAO,WAAW,GAClB,OAAO;CAGX,KAAK,MAAM,SAAS,MAAM,KAAK,OAAO,EAAE;EACpC,IAAI,CAAC,MAAM,MACP;EAIJ,MAAM,aAAa,MAAM,KAAK,MAAM,KAAK,CACpC,KAAK,QACF,MAAM,KAAK,IAAI,MAAM,CAChB,KAAK,SAAS;GAEX,MAAM,iBAAiB,KAAK,iBAAiB,IAAI;GAEjD,KAAK,MAAM,KAAK,MAAM,KAAK,eAAe,EACtC,EAAE,YAAY,GAAG,MAAM,KAAK,EAAE,WAAW,CAAC;GAG9C,OAAO,KAAK;IACd,CACD,KAAK,IAAI,CACjB,CACA,QAAQ,QAAQ,IAAI,MAAM,CAAC,SAAS,EAAE,CACtC,KAAK,QAAQ;GACV,MAAM,IAAI,SAAS,cAAc,IAAI;GACrC,EAAE,YAAY;GACd,OAAO;IACT;EAEN,MAAM,YAAY,GAAG,WAAW;;CAGpC,OAAO,KAAK;;;;;;;;;;;;;;AAehB,MAAM,yBAAyB,UAAU,OAAO;CAC5C,MAAM;CACN,UAAU;CACV,wBAAwB;EACpB,OAAO,CACH,IAAI,OAAO;GACP,KAAK,IAAI,UAAU,yBAAyB;GAC5C,OAAO,EACH,qBACH;GACJ,CAAC,CACL;;CAER,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"paste-singleline-text.js","names":[],"sources":["../../../src/extensions/shared/paste-singleline-text.ts"],"sourcesContent":["import { Extension } from '@tiptap/core'\nimport { Plugin, PluginKey } from '@tiptap/pm/state'\nimport { escape } from 'lodash-es'\n\nimport { REGEX_LINE_BREAKS } from '../../constants/regular-expressions'\nimport { parseHtmlToElement } from '../../helpers/dom'\nimport { isPlainTextDocument } from '../../helpers/schema'\n\n/**\n * The `PasteSinglelineText` extension joins all paragraphs into a single fragment when\n * copying-and-pasting text into the editor, adding spaces as block separators. This custom\n * extension is required for an editor configured with `multiline: false`, so that multiline\n * clipboard text is pasted into the singleline editor correctly.\n */\nconst PasteSinglelineText = Extension.create({\n name: 'pasteSinglelineText',\n addProseMirrorPlugins() {\n return [\n new Plugin({\n key: new PluginKey('pasteSinglelineText'),\n props: {\n transformPastedHTML(html, view) {\n const bodyElement = parseHtmlToElement(html)\n\n bodyElement.innerHTML = bodyElement.innerHTML\n // Join break lines with a space character in-between\n .replace(/<br>/g, ' ')\n // Join paragraphs with a space character in-between\n .replace(/<p[^>]*>(.*?)<\\/p>/g, '$1 ')\n\n return isPlainTextDocument(view.state.schema)\n ? escape(bodyElement.innerText)\n : bodyElement.innerHTML\n },\n transformPastedText(text) {\n return (\n text\n // Join new lines with a space character in-between\n .replace(REGEX_LINE_BREAKS, ' ')\n // Collapse multiple space characters into one\n .replace(/\\s+/g, ' ')\n )\n },\n },\n }),\n ]\n },\n})\n\nexport { PasteSinglelineText }\n"],"mappings":";;;;;;;;;;;;;AAcA,MAAM,sBAAsB,UAAU,OAAO;CACzC,MAAM;CACN,wBAAwB;
|
|
1
|
+
{"version":3,"file":"paste-singleline-text.js","names":[],"sources":["../../../src/extensions/shared/paste-singleline-text.ts"],"sourcesContent":["import { Extension } from '@tiptap/core'\nimport { Plugin, PluginKey } from '@tiptap/pm/state'\nimport { escape } from 'lodash-es'\n\nimport { REGEX_LINE_BREAKS } from '../../constants/regular-expressions'\nimport { parseHtmlToElement } from '../../helpers/dom'\nimport { isPlainTextDocument } from '../../helpers/schema'\n\n/**\n * The `PasteSinglelineText` extension joins all paragraphs into a single fragment when\n * copying-and-pasting text into the editor, adding spaces as block separators. This custom\n * extension is required for an editor configured with `multiline: false`, so that multiline\n * clipboard text is pasted into the singleline editor correctly.\n */\nconst PasteSinglelineText = Extension.create({\n name: 'pasteSinglelineText',\n addProseMirrorPlugins() {\n return [\n new Plugin({\n key: new PluginKey('pasteSinglelineText'),\n props: {\n transformPastedHTML(html, view) {\n const bodyElement = parseHtmlToElement(html)\n\n bodyElement.innerHTML = bodyElement.innerHTML\n // Join break lines with a space character in-between\n .replace(/<br>/g, ' ')\n // Join paragraphs with a space character in-between\n .replace(/<p[^>]*>(.*?)<\\/p>/g, '$1 ')\n\n return isPlainTextDocument(view.state.schema)\n ? escape(bodyElement.innerText)\n : bodyElement.innerHTML\n },\n transformPastedText(text) {\n return (\n text\n // Join new lines with a space character in-between\n .replace(REGEX_LINE_BREAKS, ' ')\n // Collapse multiple space characters into one\n .replace(/\\s+/g, ' ')\n )\n },\n },\n }),\n ]\n },\n})\n\nexport { PasteSinglelineText }\n"],"mappings":";;;;;;;;;;;;;AAcA,MAAM,sBAAsB,UAAU,OAAO;CACzC,MAAM;CACN,wBAAwB;EACpB,OAAO,CACH,IAAI,OAAO;GACP,KAAK,IAAI,UAAU,sBAAsB;GACzC,OAAO;IACH,oBAAoB,MAAM,MAAM;KAC5B,MAAM,cAAc,mBAAmB,KAAK;KAE5C,YAAY,YAAY,YAAY,UAE/B,QAAQ,SAAS,IAAI,CAErB,QAAQ,uBAAuB,MAAM;KAE1C,OAAO,oBAAoB,KAAK,MAAM,OAAO,GACvC,OAAO,YAAY,UAAU,GAC7B,YAAY;;IAEtB,oBAAoB,MAAM;KACtB,OACI,KAEK,QAAQ,mBAAmB,IAAI,CAE/B,QAAQ,QAAQ,IAAI;;IAGpC;GACJ,CAAC,CACL;;CAER,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-suggestion-extension.d.ts","names":[],"sources":["../../src/factories/create-suggestion-extension.ts"],"mappings":";;;;;;;
|
|
1
|
+
{"version":3,"file":"create-suggestion-extension.d.ts","names":[],"sources":["../../src/factories/create-suggestion-extension.ts"],"mappings":";;;;;;;AAemE;KAK9D,wBAAA;;;;EAID,EAAA;EAYwB;;;;EANxB,KAAA;AAAA;;;;KAMC,uBAAA;EAID;;;EAAA,KAAA,EAAO,eAAA,CAAoB,eAAA;EAKlB;;;EAAT,OAAA,EAAS,eAAA,CAAoB,eAAA,EAAiB,wBAAA;AAAA;AAAwB;;;;AAAA,KAOrE,qBAAA;EACD,SAAA,GAAY,KAAA,EAAO,sBAAA;AAAA;;;AAA0B;KAM5C,mBAAA;EAAiB;;;EAIlB,WAAA;EAoB0B;;;EAf1B,WAAA,EAAa,iBAAA;EA2BA;;;EAtBb,eAAA,EAAiB,iBAAA;EA4BK;;;EAvBtB,WAAA,EAAa,iBAAA;EAfb;;;EAoBA,eAAA,IAAmB,KAAA,EAAO,wBAAA;EAVT;;;EAejB,gBAAA,GAAmB,iBAAA,CAAsB,eAAA;EALf;;;EAU1B,cAAA,IACI,KAAA,UACA,OAAA,EAAS,iBAAA,CAAkB,eAAA,MAC1B,eAAA,KAAoB,OAAA,CAAQ,eAAA;EARQ;;;EAazC,YAAA,IAAgB,IAAA,EAAM,eAAA;AAAA;;;;KAMrB,iBAAA,oBAAqC,QAAA;EANtC;;;EAUA,KAAA,EAAO,eAAA;EAV8B;AAAA;;EAerC,SAAA;IAAA,UAAuB,EAAA,EAAI,wBAAA,SAAiC,eAAA;EAAA;AAAA;;;;KAM3D,yBAAA,oBAA6C,IAAA,CAAK,mBAAA,CAAkB,eAAA;;;;;;;;;;AANM;;;;;;;;;;;;;;AAMS;;iBA2B/E,yBAAA;EAAA,CAEA,EAAA,EAAI,wBAAA;AAAA,IACL,wBAAA,CAAA,CAEJ,IAAA,UACA,KAAA,GAAO,eAAA,OAKJ,iBAAA,EAAmB,eAAA,SAAwB,wBAAA,SAGpC,iBAAA;EACI,EAAA,EAAI,eAAA,CAAgB,eAAA,EAAiB,wBAAA;EACrC,KAAA,EAAO,eAAA,CAAgB,eAAA,EAAiB,wBAAA;AAAA,MAGvD,yBAAA,CAA0B,eAAA"}
|
|
@@ -54,12 +54,12 @@ function createSuggestionExtension(type, items = [], ...attributesMapping) {
|
|
|
54
54
|
addStorage() {
|
|
55
55
|
return {
|
|
56
56
|
items,
|
|
57
|
-
itemsById: items.
|
|
58
|
-
...acc,
|
|
59
|
-
[String(item[idAttribute])]: item
|
|
60
|
-
}), {})
|
|
57
|
+
itemsById: Object.fromEntries(items.map((item) => [String(item[idAttribute]), item]))
|
|
61
58
|
};
|
|
62
59
|
},
|
|
60
|
+
extendNodeSchema(extension) {
|
|
61
|
+
return { triggerChar: extension.options.triggerChar };
|
|
62
|
+
},
|
|
63
63
|
addAttributes() {
|
|
64
64
|
return {
|
|
65
65
|
id: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-suggestion-extension.js","names":["TiptapSuggestion"],"sources":["../../src/factories/create-suggestion-extension.ts"],"sourcesContent":["import { mergeAttributes, Node } from '@tiptap/core'\nimport { PluginKey } from '@tiptap/pm/state'\nimport { Suggestion as TiptapSuggestion } from '@tiptap/suggestion'\nimport { camelCase, kebabCase } from 'lodash-es'\n\nimport { SUGGESTION_EXTENSION_PRIORITY } from '../constants/extension-priorities'\nimport { canInsertNodeAt } from '../utilities/can-insert-node-at'\nimport { canInsertSuggestion } from '../utilities/can-insert-suggestion'\n\nimport type {\n SuggestionKeyDownProps as CoreSuggestionKeyDownProps,\n SuggestionOptions as CoreSuggestionOptions,\n SuggestionProps as CoreSuggestionProps,\n} from '@tiptap/suggestion'\nimport type { ConditionalKeys, RequireAtLeastOne } from 'type-fest'\n\n/**\n * A type that describes the suggestion node attributes.\n */\ntype SuggestionNodeAttributes = {\n /**\n * The suggestion node unique identifier to be rendered by the editor as a `data-id` attribute.\n */\n id: number | string\n\n /**\n * The suggestion node label to be rendered by the editor as a `data-label` attribute and the\n * display text itself.\n */\n label: string\n}\n\n/**\n * A type that describes the minimal props that an autocomplete dropdown must receive.\n */\ntype SuggestionRendererProps<TSuggestionItem> = {\n /**\n * The list of suggestion items to be rendered by the autocomplete dropdown.\n */\n items: CoreSuggestionProps<TSuggestionItem>['items']\n\n /**\n * The function that must be invoked when a suggestion item is selected.\n */\n command: CoreSuggestionProps<TSuggestionItem, SuggestionNodeAttributes>['command']\n}\n\n/**\n * A type that describes the forwarded ref that an autocomplete dropdown must implement with\n * `useImperativeHandle` to handle `keydown` events in the dropdown render function.\n */\ntype SuggestionRendererRef = {\n onKeyDown: (props: CoreSuggestionKeyDownProps) => boolean\n}\n\n/**\n * The options available to customize the extension created by the factory function.\n */\ntype SuggestionOptions<TSuggestionItem> = {\n /**\n * The character that triggers the autocomplete dropdown.\n */\n triggerChar: string\n\n /**\n * Allows or disallows spaces in suggested items.\n */\n allowSpaces: CoreSuggestionOptions['allowSpaces']\n\n /**\n * The prefix characters that are allowed to trigger a suggestion.\n */\n allowedPrefixes: CoreSuggestionOptions['allowedPrefixes']\n\n /**\n * Trigger the autocomplete dropdown at the start of a line only.\n */\n startOfLine: CoreSuggestionOptions['startOfLine']\n\n /**\n * Define how the suggestion item `aria-label` attribute should be rendered.\n */\n renderAriaLabel?: (attrs: SuggestionNodeAttributes) => string\n\n /**\n * A render function for the autocomplete dropdown.\n */\n dropdownRenderFn?: CoreSuggestionOptions<TSuggestionItem>['render']\n\n /**\n * The event handler that is fired when the search string has changed.\n */\n onSearchChange?: (\n query: string,\n storage: SuggestionStorage<TSuggestionItem>,\n ) => TSuggestionItem[] | Promise<TSuggestionItem[]>\n\n /**\n * The event handler that is fired when a suggestion item is selected.\n */\n onItemSelect?: (item: TSuggestionItem) => void\n}\n\n/**\n * The storage holding the suggestion items original array, and a collection indexed by the item id.\n */\ntype SuggestionStorage<TSuggestionItem> = Readonly<{\n /**\n * The original array of suggestion items.\n */\n items: TSuggestionItem[]\n\n /**\n * A collection of suggestion items indexed by the item id.\n */\n itemsById: { readonly [id: SuggestionNodeAttributes['id']]: TSuggestionItem | undefined }\n}>\n\n/**\n * The return type for a suggestion extension created by the factory function.\n */\ntype SuggestionExtensionResult<TSuggestionItem> = Node<SuggestionOptions<TSuggestionItem>>\n\n/**\n * A factory function responsible for creating different types of suggestion extensions with\n * flexibility and customizability in mind.\n *\n * Extensions created by this factory function render editor nodes with internal `data-id` and\n * `data-label` attributes (as a way to save and restore the editor nodes data) based on properties\n * of the same name (minus the `data-` prefix) from the source item type. However, in the event of\n * unmatched properties between the internal attributes and the source item type, you should\n * specify the source item type, and use the optional `attributesMapping` option to map the\n * source properties to the internal `data-id` and `data-label` attributes.\n *\n * This factory function also stores the suggestion items internally in the editor storage (as-is,\n * and indexed by an identifier), as a way to make sure that if a previously referenced suggestion\n * changes its label, the editor will always render the most up-to-date label for the suggestion by\n * reading it from the storage. An example use case for this is when a user mention is added to the\n * editor, and the user changed its name afterwards, the editor will always render the most\n * up-to-date user name for the mention.\n *\n * @param type A unique identifier for the suggestion extension type.\n * @param items An array of suggestion items to be stored in the editor storage.\n * @param attributesMapping An object to map the `data-id` and `data-label` attributes with the\n * source item type properties.\n *\n * @returns A new suggestion extension tailored to a specific use case.\n */\nfunction createSuggestionExtension<\n TSuggestionItem extends {\n [id: SuggestionNodeAttributes['id']]: unknown\n } = SuggestionNodeAttributes,\n>(\n type: string,\n items: TSuggestionItem[] = [],\n\n // This type makes sure that if a generic type variable is specified, the `attributesMapping`\n // is also defined (and vice versa) along with making sure that at least one attribute is\n // specified, and that all constraints are satisfied.\n ...attributesMapping: TSuggestionItem extends SuggestionNodeAttributes\n ? []\n : [\n RequireAtLeastOne<{\n id: ConditionalKeys<TSuggestionItem, SuggestionNodeAttributes['id']>\n label: ConditionalKeys<TSuggestionItem, SuggestionNodeAttributes['label']>\n }>,\n ]\n): SuggestionExtensionResult<TSuggestionItem> {\n // Normalize the node type and add the `Suggestion` suffix so that it can be easily identified\n // when parsing the editor schema programatically (useful for Markdown/HTML serialization)\n const nodeType = `${camelCase(type)}Suggestion`\n\n // Normalize the node type to kebab-case to be used as a `data-*` HTML attribute\n const attributeType = kebabCase(type)\n\n // Get the specified attributes, if available, or use the defaults\n const idAttribute = String(attributesMapping[0]?.id ?? 'id')\n const labelAttribute = String(attributesMapping[0]?.label ?? 'label')\n\n // Create a personalized suggestion extension\n return Node.create<SuggestionOptions<TSuggestionItem>, SuggestionStorage<TSuggestionItem>>({\n name: nodeType,\n priority: SUGGESTION_EXTENSION_PRIORITY,\n inline: true,\n group: 'inline',\n selectable: false,\n atom: true,\n addOptions() {\n return {\n triggerChar: '@',\n // Disable option by default until the following Tiptap issue is fixed:\n // https://github.com/ueberdosis/tiptap/issues/2159\n allowSpaces: false,\n allowedPrefixes: [' '],\n startOfLine: false,\n }\n },\n addStorage() {\n return {\n items,\n itemsById: items.reduce(\n (acc, item) => ({ ...acc, [String(item[idAttribute])]: item }),\n {},\n ),\n }\n },\n addAttributes() {\n return {\n id: {\n default: null,\n parseHTML: (element) => element.getAttribute('data-id'),\n renderHTML: (attributes) => ({\n 'data-id': String(attributes.id),\n }),\n },\n label: {\n default: null,\n parseHTML: (element: Element) => {\n const id = String(element.getAttribute('data-id'))\n const item = this.storage.itemsById[id]\n\n // Attempt to read the item label from the storage first (as a way to make\n // sure that a previously referenced suggestion always renders the most\n // up-to-date label for the suggestion), and fallback to the `data-label`\n // attribute if the item is not found in the storage\n const labelValue =\n item?.[labelAttribute] ?? element.getAttribute('data-label')\n return typeof labelValue === 'string' ? labelValue : ''\n },\n renderHTML: (attributes) => ({\n 'data-label': String(attributes.label),\n }),\n },\n }\n },\n parseHTML() {\n return [{ tag: `span[data-${attributeType}]` }]\n },\n renderHTML({ node, HTMLAttributes }) {\n return [\n 'span',\n mergeAttributes(\n {\n [`data-${attributeType}`]: '',\n 'aria-label': this.options.renderAriaLabel?.({\n id: String(node.attrs.id),\n label: String(node.attrs.label),\n }),\n },\n HTMLAttributes,\n ),\n `${String(this.options.triggerChar)}${String(node.attrs.label)}`,\n ]\n },\n renderText({ node }) {\n return `${String(this.options.triggerChar)}${String(node.attrs.label)}`\n },\n addProseMirrorPlugins() {\n const {\n options: {\n triggerChar,\n allowSpaces,\n allowedPrefixes,\n startOfLine,\n onSearchChange,\n onItemSelect,\n dropdownRenderFn,\n },\n storage,\n } = this\n\n return [\n TiptapSuggestion<TSuggestionItem, SuggestionNodeAttributes>({\n pluginKey: new PluginKey(nodeType),\n editor: this.editor,\n char: triggerChar,\n allowedPrefixes,\n allowSpaces,\n startOfLine,\n items({ query, editor }) {\n return (\n onSearchChange?.(\n query,\n editor.storage[nodeType] as SuggestionStorage<TSuggestionItem>,\n ) || []\n )\n },\n allow({ editor, range, state }) {\n return (\n canInsertNodeAt({ editor, nodeType, range }) &&\n canInsertSuggestion({ editor, state })\n )\n },\n command({ editor, range, props }) {\n const nodeAfter = editor.view.state.selection.$to.nodeAfter\n const overrideSpace = nodeAfter?.text?.startsWith(' ')\n\n if (overrideSpace) {\n range.to += 1\n }\n\n editor\n .chain()\n .focus()\n .insertContentAt(range, [\n {\n type: nodeType,\n attrs: props,\n },\n {\n type: 'text',\n text: ' ',\n },\n ])\n .run()\n\n const item = storage.itemsById[props.id]\n\n if (item) {\n onItemSelect?.(item)\n }\n },\n render: dropdownRenderFn,\n }),\n ]\n },\n })\n}\n\nexport { createSuggestionExtension }\n\nexport type {\n SuggestionExtensionResult,\n SuggestionOptions,\n SuggestionRendererProps,\n SuggestionRendererRef,\n SuggestionStorage,\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoJA,SAAS,0BAKL,MACA,QAA2B,EAAE,EAK7B,GAAG,mBAQuC;CAG1C,MAAM,WAAW,GAAG,UAAU,KAAK,CAAC;CAGpC,MAAM,gBAAgB,UAAU,KAAK;CAGrC,MAAM,cAAc,OAAO,kBAAkB,IAAI,MAAM,KAAK;CAC5D,MAAM,iBAAiB,OAAO,kBAAkB,IAAI,SAAS,QAAQ;AAGrE,QAAO,KAAK,OAA+E;EACvF,MAAM;EACN,UAAU;EACV,QAAQ;EACR,OAAO;EACP,YAAY;EACZ,MAAM;EACN,aAAa;AACT,UAAO;IACH,aAAa;IAGb,aAAa;IACb,iBAAiB,CAAC,IAAI;IACtB,aAAa;IAChB;;EAEL,aAAa;AACT,UAAO;IACH;IACA,WAAW,MAAM,QACZ,KAAK,UAAU;KAAE,GAAG;MAAM,OAAO,KAAK,aAAa,GAAG;KAAM,GAC7D,EAAE,CACL;IACJ;;EAEL,gBAAgB;AACZ,UAAO;IACH,IAAI;KACA,SAAS;KACT,YAAY,YAAY,QAAQ,aAAa,UAAU;KACvD,aAAa,gBAAgB,EACzB,WAAW,OAAO,WAAW,GAAG,EACnC;KACJ;IACD,OAAO;KACH,SAAS;KACT,YAAY,YAAqB;MAC7B,MAAM,KAAK,OAAO,QAAQ,aAAa,UAAU,CAAC;MAOlD,MAAM,aANO,KAAK,QAAQ,UAAU,MAOzB,mBAAmB,QAAQ,aAAa,aAAa;AAChE,aAAO,OAAO,eAAe,WAAW,aAAa;;KAEzD,aAAa,gBAAgB,EACzB,cAAc,OAAO,WAAW,MAAM,EACzC;KACJ;IACJ;;EAEL,YAAY;AACR,UAAO,CAAC,EAAE,KAAK,aAAa,cAAc,IAAI,CAAC;;EAEnD,WAAW,EAAE,MAAM,kBAAkB;AACjC,UAAO;IACH;IACA,gBACI;MACK,QAAQ,kBAAkB;KAC3B,cAAc,KAAK,QAAQ,kBAAkB;MACzC,IAAI,OAAO,KAAK,MAAM,GAAG;MACzB,OAAO,OAAO,KAAK,MAAM,MAAM;MAClC,CAAC;KACL,EACD,eACH;IACD,GAAG,OAAO,KAAK,QAAQ,YAAY,GAAG,OAAO,KAAK,MAAM,MAAM;IACjE;;EAEL,WAAW,EAAE,QAAQ;AACjB,UAAO,GAAG,OAAO,KAAK,QAAQ,YAAY,GAAG,OAAO,KAAK,MAAM,MAAM;;EAEzE,wBAAwB;GACpB,MAAM,EACF,SAAS,EACL,aACA,aACA,iBACA,aACA,gBACA,cACA,oBAEJ,YACA;AAEJ,UAAO,CACHA,WAA4D;IACxD,WAAW,IAAI,UAAU,SAAS;IAClC,QAAQ,KAAK;IACb,MAAM;IACN;IACA;IACA;IACA,MAAM,EAAE,OAAO,UAAU;AACrB,YACI,iBACI,OACA,OAAO,QAAQ,UAClB,IAAI,EAAE;;IAGf,MAAM,EAAE,QAAQ,OAAO,SAAS;AAC5B,YACI,gBAAgB;MAAE;MAAQ;MAAU;MAAO,CAAC,IAC5C,oBAAoB;MAAE;MAAQ;MAAO,CAAC;;IAG9C,QAAQ,EAAE,QAAQ,OAAO,SAAS;AAI9B,SAHkB,OAAO,KAAK,MAAM,UAAU,IAAI,WACjB,MAAM,WAAW,IAAI,CAGlD,OAAM,MAAM;AAGhB,YACK,OAAO,CACP,OAAO,CACP,gBAAgB,OAAO,CACpB;MACI,MAAM;MACN,OAAO;MACV,EACD;MACI,MAAM;MACN,MAAM;MACT,CACJ,CAAC,CACD,KAAK;KAEV,MAAM,OAAO,QAAQ,UAAU,MAAM;AAErC,SAAI,KACA,gBAAe,KAAK;;IAG5B,QAAQ;IACX,CAAC,CACL;;EAER,CAAC"}
|
|
1
|
+
{"version":3,"file":"create-suggestion-extension.js","names":["TiptapSuggestion"],"sources":["../../src/factories/create-suggestion-extension.ts"],"sourcesContent":["import { mergeAttributes, Node } from '@tiptap/core'\nimport { PluginKey } from '@tiptap/pm/state'\nimport { Suggestion as TiptapSuggestion } from '@tiptap/suggestion'\nimport { camelCase, kebabCase } from 'lodash-es'\n\nimport { SUGGESTION_EXTENSION_PRIORITY } from '../constants/extension-priorities'\nimport { DEFAULT_SUGGESTION_TRIGGER_CHAR } from '../constants/suggestions'\nimport { canInsertNodeAt } from '../utilities/can-insert-node-at'\nimport { canInsertSuggestion } from '../utilities/can-insert-suggestion'\n\nimport type {\n SuggestionKeyDownProps as CoreSuggestionKeyDownProps,\n SuggestionOptions as CoreSuggestionOptions,\n SuggestionProps as CoreSuggestionProps,\n} from '@tiptap/suggestion'\nimport type { ConditionalKeys, RequireAtLeastOne } from 'type-fest'\n\n/**\n * A type that describes the suggestion node attributes.\n */\ntype SuggestionNodeAttributes = {\n /**\n * The suggestion node unique identifier to be rendered by the editor as a `data-id` attribute.\n */\n id: number | string\n\n /**\n * The suggestion node label to be rendered by the editor as a `data-label` attribute and the\n * display text itself.\n */\n label: string\n}\n\n/**\n * A type that describes the minimal props that an autocomplete dropdown must receive.\n */\ntype SuggestionRendererProps<TSuggestionItem> = {\n /**\n * The list of suggestion items to be rendered by the autocomplete dropdown.\n */\n items: CoreSuggestionProps<TSuggestionItem>['items']\n\n /**\n * The function that must be invoked when a suggestion item is selected.\n */\n command: CoreSuggestionProps<TSuggestionItem, SuggestionNodeAttributes>['command']\n}\n\n/**\n * A type that describes the forwarded ref that an autocomplete dropdown must implement with\n * `useImperativeHandle` to handle `keydown` events in the dropdown render function.\n */\ntype SuggestionRendererRef = {\n onKeyDown: (props: CoreSuggestionKeyDownProps) => boolean\n}\n\n/**\n * The options available to customize the extension created by the factory function.\n */\ntype SuggestionOptions<TSuggestionItem> = {\n /**\n * The character that triggers the autocomplete dropdown.\n */\n triggerChar: string\n\n /**\n * Allows or disallows spaces in suggested items.\n */\n allowSpaces: CoreSuggestionOptions['allowSpaces']\n\n /**\n * The prefix characters that are allowed to trigger a suggestion.\n */\n allowedPrefixes: CoreSuggestionOptions['allowedPrefixes']\n\n /**\n * Trigger the autocomplete dropdown at the start of a line only.\n */\n startOfLine: CoreSuggestionOptions['startOfLine']\n\n /**\n * Define how the suggestion item `aria-label` attribute should be rendered.\n */\n renderAriaLabel?: (attrs: SuggestionNodeAttributes) => string\n\n /**\n * A render function for the autocomplete dropdown.\n */\n dropdownRenderFn?: CoreSuggestionOptions<TSuggestionItem>['render']\n\n /**\n * The event handler that is fired when the search string has changed.\n */\n onSearchChange?: (\n query: string,\n storage: SuggestionStorage<TSuggestionItem>,\n ) => TSuggestionItem[] | Promise<TSuggestionItem[]>\n\n /**\n * The event handler that is fired when a suggestion item is selected.\n */\n onItemSelect?: (item: TSuggestionItem) => void\n}\n\n/**\n * The storage holding the suggestion items original array, and a collection indexed by the item id.\n */\ntype SuggestionStorage<TSuggestionItem> = Readonly<{\n /**\n * The original array of suggestion items.\n */\n items: TSuggestionItem[]\n\n /**\n * A collection of suggestion items indexed by the item id.\n */\n itemsById: { readonly [id: SuggestionNodeAttributes['id']]: TSuggestionItem | undefined }\n}>\n\n/**\n * The return type for a suggestion extension created by the factory function.\n */\ntype SuggestionExtensionResult<TSuggestionItem> = Node<SuggestionOptions<TSuggestionItem>>\n\n/**\n * A factory function responsible for creating different types of suggestion extensions with\n * flexibility and customizability in mind.\n *\n * Extensions created by this factory function render editor nodes with internal `data-id` and\n * `data-label` attributes (as a way to save and restore the editor nodes data) based on properties\n * of the same name (minus the `data-` prefix) from the source item type. However, in the event of\n * unmatched properties between the internal attributes and the source item type, you should\n * specify the source item type, and use the optional `attributesMapping` option to map the\n * source properties to the internal `data-id` and `data-label` attributes.\n *\n * This factory function also stores the suggestion items internally in the editor storage (as-is,\n * and indexed by an identifier), as a way to make sure that if a previously referenced suggestion\n * changes its label, the editor will always render the most up-to-date label for the suggestion by\n * reading it from the storage. An example use case for this is when a user mention is added to the\n * editor, and the user changed its name afterwards, the editor will always render the most\n * up-to-date user name for the mention.\n *\n * @param type A unique identifier for the suggestion extension type.\n * @param items An array of suggestion items to be stored in the editor storage.\n * @param attributesMapping An object to map the `data-id` and `data-label` attributes with the\n * source item type properties.\n *\n * @returns A new suggestion extension tailored to a specific use case.\n */\nfunction createSuggestionExtension<\n TSuggestionItem extends {\n [id: SuggestionNodeAttributes['id']]: unknown\n } = SuggestionNodeAttributes,\n>(\n type: string,\n items: TSuggestionItem[] = [],\n\n // This type makes sure that if a generic type variable is specified, the `attributesMapping`\n // is also defined (and vice versa) along with making sure that at least one attribute is\n // specified, and that all constraints are satisfied.\n ...attributesMapping: TSuggestionItem extends SuggestionNodeAttributes\n ? []\n : [\n RequireAtLeastOne<{\n id: ConditionalKeys<TSuggestionItem, SuggestionNodeAttributes['id']>\n label: ConditionalKeys<TSuggestionItem, SuggestionNodeAttributes['label']>\n }>,\n ]\n): SuggestionExtensionResult<TSuggestionItem> {\n // Normalize the node type and add the `Suggestion` suffix so that it can be easily identified\n // when parsing the editor schema programatically (useful for Markdown/HTML serialization)\n const nodeType = `${camelCase(type)}Suggestion`\n\n // Normalize the node type to kebab-case to be used as a `data-*` HTML attribute\n const attributeType = kebabCase(type)\n\n // Get the specified attributes, if available, or use the defaults\n const idAttribute = String(attributesMapping[0]?.id ?? 'id')\n const labelAttribute = String(attributesMapping[0]?.label ?? 'label')\n\n // Create a personalized suggestion extension\n return Node.create<SuggestionOptions<TSuggestionItem>, SuggestionStorage<TSuggestionItem>>({\n name: nodeType,\n priority: SUGGESTION_EXTENSION_PRIORITY,\n inline: true,\n group: 'inline',\n selectable: false,\n atom: true,\n addOptions() {\n return {\n triggerChar: DEFAULT_SUGGESTION_TRIGGER_CHAR,\n // Disable option by default until the following Tiptap issue is fixed:\n // https://github.com/ueberdosis/tiptap/issues/2159\n allowSpaces: false,\n allowedPrefixes: [' '],\n startOfLine: false,\n }\n },\n addStorage() {\n return {\n items,\n itemsById: Object.fromEntries(\n items.map((item) => [String(item[idAttribute]), item]),\n ),\n }\n },\n // Expose the trigger character in the node spec so it can be read from the schema by\n // serializers and renderers that need to reconstruct the visible text for a suggestion\n // (e.g., `@username`, `#channel`), without depending on the editor instance.\n extendNodeSchema(extension) {\n return {\n triggerChar: extension.options.triggerChar,\n }\n },\n addAttributes() {\n return {\n id: {\n default: null,\n parseHTML: (element) => element.getAttribute('data-id'),\n renderHTML: (attributes) => ({\n 'data-id': String(attributes.id),\n }),\n },\n label: {\n default: null,\n parseHTML: (element: Element) => {\n const id = String(element.getAttribute('data-id'))\n const item = this.storage.itemsById[id]\n\n // Attempt to read the item label from the storage first (as a way to make\n // sure that a previously referenced suggestion always renders the most\n // up-to-date label for the suggestion), and fallback to the `data-label`\n // attribute if the item is not found in the storage\n const labelValue =\n item?.[labelAttribute] ?? element.getAttribute('data-label')\n return typeof labelValue === 'string' ? labelValue : ''\n },\n renderHTML: (attributes) => ({\n 'data-label': String(attributes.label),\n }),\n },\n }\n },\n parseHTML() {\n return [{ tag: `span[data-${attributeType}]` }]\n },\n renderHTML({ node, HTMLAttributes }) {\n return [\n 'span',\n mergeAttributes(\n {\n [`data-${attributeType}`]: '',\n 'aria-label': this.options.renderAriaLabel?.({\n id: String(node.attrs.id),\n label: String(node.attrs.label),\n }),\n },\n HTMLAttributes,\n ),\n `${String(this.options.triggerChar)}${String(node.attrs.label)}`,\n ]\n },\n renderText({ node }) {\n return `${String(this.options.triggerChar)}${String(node.attrs.label)}`\n },\n addProseMirrorPlugins() {\n const {\n options: {\n triggerChar,\n allowSpaces,\n allowedPrefixes,\n startOfLine,\n onSearchChange,\n onItemSelect,\n dropdownRenderFn,\n },\n storage,\n } = this\n\n return [\n TiptapSuggestion<TSuggestionItem, SuggestionNodeAttributes>({\n pluginKey: new PluginKey(nodeType),\n editor: this.editor,\n char: triggerChar,\n allowedPrefixes,\n allowSpaces,\n startOfLine,\n items({ query, editor }) {\n return (\n onSearchChange?.(\n query,\n editor.storage[nodeType] as SuggestionStorage<TSuggestionItem>,\n ) || []\n )\n },\n allow({ editor, range, state }) {\n return (\n canInsertNodeAt({ editor, nodeType, range }) &&\n canInsertSuggestion({ editor, state })\n )\n },\n command({ editor, range, props }) {\n const nodeAfter = editor.view.state.selection.$to.nodeAfter\n const overrideSpace = nodeAfter?.text?.startsWith(' ')\n\n if (overrideSpace) {\n range.to += 1\n }\n\n editor\n .chain()\n .focus()\n .insertContentAt(range, [\n {\n type: nodeType,\n attrs: props,\n },\n {\n type: 'text',\n text: ' ',\n },\n ])\n .run()\n\n const item = storage.itemsById[props.id]\n\n if (item) {\n onItemSelect?.(item)\n }\n },\n render: dropdownRenderFn,\n }),\n ]\n },\n })\n}\n\nexport { createSuggestionExtension }\n\nexport type {\n SuggestionExtensionResult,\n SuggestionOptions,\n SuggestionRendererProps,\n SuggestionRendererRef,\n SuggestionStorage,\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqJA,SAAS,0BAKL,MACA,QAA2B,EAAE,EAK7B,GAAG,mBAQuC;CAG1C,MAAM,WAAW,GAAG,UAAU,KAAK,CAAC;CAGpC,MAAM,gBAAgB,UAAU,KAAK;CAGrC,MAAM,cAAc,OAAO,kBAAkB,IAAI,MAAM,KAAK;CAC5D,MAAM,iBAAiB,OAAO,kBAAkB,IAAI,SAAS,QAAQ;CAGrE,OAAO,KAAK,OAA+E;EACvF,MAAM;EACN,UAAU;EACV,QAAQ;EACR,OAAO;EACP,YAAY;EACZ,MAAM;EACN,aAAa;GACT,OAAO;IACH,aAAA;IAGA,aAAa;IACb,iBAAiB,CAAC,IAAI;IACtB,aAAa;IAChB;;EAEL,aAAa;GACT,OAAO;IACH;IACA,WAAW,OAAO,YACd,MAAM,KAAK,SAAS,CAAC,OAAO,KAAK,aAAa,EAAE,KAAK,CAAC,CACzD;IACJ;;EAKL,iBAAiB,WAAW;GACxB,OAAO,EACH,aAAa,UAAU,QAAQ,aAClC;;EAEL,gBAAgB;GACZ,OAAO;IACH,IAAI;KACA,SAAS;KACT,YAAY,YAAY,QAAQ,aAAa,UAAU;KACvD,aAAa,gBAAgB,EACzB,WAAW,OAAO,WAAW,GAAG,EACnC;KACJ;IACD,OAAO;KACH,SAAS;KACT,YAAY,YAAqB;MAC7B,MAAM,KAAK,OAAO,QAAQ,aAAa,UAAU,CAAC;MAOlD,MAAM,aANO,KAAK,QAAQ,UAAU,MAOzB,mBAAmB,QAAQ,aAAa,aAAa;MAChE,OAAO,OAAO,eAAe,WAAW,aAAa;;KAEzD,aAAa,gBAAgB,EACzB,cAAc,OAAO,WAAW,MAAM,EACzC;KACJ;IACJ;;EAEL,YAAY;GACR,OAAO,CAAC,EAAE,KAAK,aAAa,cAAc,IAAI,CAAC;;EAEnD,WAAW,EAAE,MAAM,kBAAkB;GACjC,OAAO;IACH;IACA,gBACI;MACK,QAAQ,kBAAkB;KAC3B,cAAc,KAAK,QAAQ,kBAAkB;MACzC,IAAI,OAAO,KAAK,MAAM,GAAG;MACzB,OAAO,OAAO,KAAK,MAAM,MAAM;MAClC,CAAC;KACL,EACD,eACH;IACD,GAAG,OAAO,KAAK,QAAQ,YAAY,GAAG,OAAO,KAAK,MAAM,MAAM;IACjE;;EAEL,WAAW,EAAE,QAAQ;GACjB,OAAO,GAAG,OAAO,KAAK,QAAQ,YAAY,GAAG,OAAO,KAAK,MAAM,MAAM;;EAEzE,wBAAwB;GACpB,MAAM,EACF,SAAS,EACL,aACA,aACA,iBACA,aACA,gBACA,cACA,oBAEJ,YACA;GAEJ,OAAO,CACHA,WAA4D;IACxD,WAAW,IAAI,UAAU,SAAS;IAClC,QAAQ,KAAK;IACb,MAAM;IACN;IACA;IACA;IACA,MAAM,EAAE,OAAO,UAAU;KACrB,OACI,iBACI,OACA,OAAO,QAAQ,UAClB,IAAI,EAAE;;IAGf,MAAM,EAAE,QAAQ,OAAO,SAAS;KAC5B,OACI,gBAAgB;MAAE;MAAQ;MAAU;MAAO,CAAC,IAC5C,oBAAoB;MAAE;MAAQ;MAAO,CAAC;;IAG9C,QAAQ,EAAE,QAAQ,OAAO,SAAS;KAI9B,IAHkB,OAAO,KAAK,MAAM,UAAU,IAAI,WACjB,MAAM,WAAW,IAAI,EAGlD,MAAM,MAAM;KAGhB,OACK,OAAO,CACP,OAAO,CACP,gBAAgB,OAAO,CACpB;MACI,MAAM;MACN,OAAO;MACV,EACD;MACI,MAAM;MACN,MAAM;MACT,CACJ,CAAC,CACD,KAAK;KAEV,MAAM,OAAO,QAAQ,UAAU,MAAM;KAErC,IAAI,MACA,eAAe,KAAK;;IAG5B,QAAQ;IACX,CAAC,CACL;;EAER,CAAC"}
|
package/dist/helpers/dom.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dom.js","names":[],"sources":["../../src/helpers/dom.ts"],"sourcesContent":["/**\n * Parse a given HTML string and returns the `HTMLElement` for the document body.\n *\n * @param html The HTML string to parse.\n */\nfunction parseHtmlToElement(html: string) {\n return new DOMParser().parseFromString(html, 'text/html').body\n}\n\nexport { parseHtmlToElement }\n"],"mappings":";;;;;;AAKA,SAAS,mBAAmB,MAAc;
|
|
1
|
+
{"version":3,"file":"dom.js","names":[],"sources":["../../src/helpers/dom.ts"],"sourcesContent":["/**\n * Parse a given HTML string and returns the `HTMLElement` for the document body.\n *\n * @param html The HTML string to parse.\n */\nfunction parseHtmlToElement(html: string) {\n return new DOMParser().parseFromString(html, 'text/html').body\n}\n\nexport { parseHtmlToElement }\n"],"mappings":";;;;;;AAKA,SAAS,mBAAmB,MAAc;CACtC,OAAO,IAAI,WAAW,CAAC,gBAAgB,MAAM,YAAY,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.js","names":[],"sources":["../../src/helpers/schema.ts"],"sourcesContent":["import type { Schema } from '@tiptap/pm/model'\n\n/**\n * Check if the document schema accepts multiple lines of input.\n *\n * @param schema The current editor document schema.\n *\n * @returns True if the schema accepts multiple lines of input, false otherwise.\n */\nfunction isMultilineDocument(schema: Schema): boolean {\n return /(?:\\+|\\*)$/.test(schema.topNodeType.spec.content || '')\n}\n\n/**\n * Check if a document schema contains a plain-text document top node.\n *\n * @param schema The current editor document schema.\n *\n * @returns True if the schema contains a plain-text document, false otherwise.\n */\nfunction isPlainTextDocument(schema: Schema): boolean {\n return Boolean(schema.topNodeType.spec.content?.startsWith('paragraph'))\n}\n\n/**\n * Computes a string ID that identifies a given editor schema which can be used for object mapping.\n *\n * @param schema The current editor document schema.\n *\n * @returns A string ID matching the editor schema.\n */\nfunction computeSchemaId(schema: Schema) {\n return [...Object.keys(schema.marks), ...Object.keys(schema.nodes)].join()\n}\n\nexport { computeSchemaId, isMultilineDocument, isPlainTextDocument }\n"],"mappings":";;;;;;;;AASA,SAAS,oBAAoB,QAAyB;
|
|
1
|
+
{"version":3,"file":"schema.js","names":[],"sources":["../../src/helpers/schema.ts"],"sourcesContent":["import type { Schema } from '@tiptap/pm/model'\n\n/**\n * Check if the document schema accepts multiple lines of input.\n *\n * @param schema The current editor document schema.\n *\n * @returns True if the schema accepts multiple lines of input, false otherwise.\n */\nfunction isMultilineDocument(schema: Schema): boolean {\n return /(?:\\+|\\*)$/.test(schema.topNodeType.spec.content || '')\n}\n\n/**\n * Check if a document schema contains a plain-text document top node.\n *\n * @param schema The current editor document schema.\n *\n * @returns True if the schema contains a plain-text document, false otherwise.\n */\nfunction isPlainTextDocument(schema: Schema): boolean {\n return Boolean(schema.topNodeType.spec.content?.startsWith('paragraph'))\n}\n\n/**\n * Computes a string ID that identifies a given editor schema which can be used for object mapping.\n *\n * @param schema The current editor document schema.\n *\n * @returns A string ID matching the editor schema.\n */\nfunction computeSchemaId(schema: Schema) {\n return [...Object.keys(schema.marks), ...Object.keys(schema.nodes)].join()\n}\n\nexport { computeSchemaId, isMultilineDocument, isPlainTextDocument }\n"],"mappings":";;;;;;;;AASA,SAAS,oBAAoB,QAAyB;CAClD,OAAO,aAAa,KAAK,OAAO,YAAY,KAAK,WAAW,GAAG;;;;;;;;;AAUnE,SAAS,oBAAoB,QAAyB;CAClD,OAAO,QAAQ,OAAO,YAAY,KAAK,SAAS,WAAW,YAAY,CAAC;;;;;;;;;AAU5E,SAAS,gBAAgB,QAAgB;CACrC,OAAO,CAAC,GAAG,OAAO,KAAK,OAAO,MAAM,EAAE,GAAG,OAAO,KAAK,OAAO,MAAM,CAAC,CAAC,MAAM"}
|
|
@@ -1,18 +1,21 @@
|
|
|
1
1
|
import { kebabCase } from "lodash-es";
|
|
2
2
|
//#region src/helpers/serializer.ts
|
|
3
3
|
/**
|
|
4
|
-
* Builds
|
|
5
|
-
* suggestion nodes
|
|
4
|
+
* Builds the information derived from all the suggestion nodes available in the given editor
|
|
5
|
+
* schema, in a single iteration. Returns `null` if there are no suggestion nodes in the schema.
|
|
6
6
|
*
|
|
7
7
|
* @param schema The editor schema to be used for suggestion nodes detection.
|
|
8
8
|
*
|
|
9
|
-
* @returns A
|
|
10
|
-
* `null` if there are no suggestion nodes in the editor schema.
|
|
9
|
+
* @returns A `SuggestionSchemaInfo` object, or `null` if there are no suggestion nodes.
|
|
11
10
|
*/
|
|
12
|
-
function
|
|
11
|
+
function buildSuggestionSchemaInfo(schema) {
|
|
13
12
|
const suggestionNodes = Object.values(schema.nodes).filter((node) => node.name.endsWith("Suggestion"));
|
|
14
13
|
if (suggestionNodes.length === 0) return null;
|
|
15
|
-
|
|
14
|
+
const triggerCharByScheme = new Map(suggestionNodes.map((node) => [kebabCase(node.name.replace(/Suggestion$/, "")), String(node.spec.triggerChar ?? "@")]));
|
|
15
|
+
return {
|
|
16
|
+
urlSchemeRegex: `(?:${[...triggerCharByScheme.keys()].join("|")})://`,
|
|
17
|
+
triggerCharByScheme
|
|
18
|
+
};
|
|
16
19
|
}
|
|
17
20
|
/**
|
|
18
21
|
* Extract all tags from the given parse rules argument, and returns an array of said tags.
|
|
@@ -26,6 +29,6 @@ function extractTagsFromParseRules(parseRules) {
|
|
|
26
29
|
return parseRules.filter((rule) => rule.tag).map((rule) => rule.tag);
|
|
27
30
|
}
|
|
28
31
|
//#endregion
|
|
29
|
-
export {
|
|
32
|
+
export { buildSuggestionSchemaInfo, extractTagsFromParseRules };
|
|
30
33
|
|
|
31
34
|
//# sourceMappingURL=serializer.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"serializer.js","names":[],"sources":["../../src/helpers/serializer.ts"],"sourcesContent":["import { kebabCase } from 'lodash-es'\n\nimport type { ParseRule, Schema } from '@tiptap/pm/model'\n\n/**\n *
|
|
1
|
+
{"version":3,"file":"serializer.js","names":[],"sources":["../../src/helpers/serializer.ts"],"sourcesContent":["import { kebabCase } from 'lodash-es'\n\nimport { DEFAULT_SUGGESTION_TRIGGER_CHAR } from '../constants/suggestions'\n\nimport type { ParseRule, Schema } from '@tiptap/pm/model'\n\n/**\n * Information derived from the suggestion nodes available in the editor schema, used by the\n * HTML serializer to identify and transform suggestion links into spans.\n */\ntype SuggestionSchemaInfo = {\n /**\n * A partial regular expression that matches the URL schemes used by all the available\n * suggestion nodes (e.g. `(?:mention|channel)://`).\n */\n urlSchemeRegex: string\n\n /**\n * A map from each URL scheme (e.g. `mention`, `channel`) to its configured trigger character\n * (e.g. `@`, `#`).\n */\n triggerCharByScheme: Map<string, string>\n}\n\n/**\n * Builds the information derived from all the suggestion nodes available in the given editor\n * schema, in a single iteration. Returns `null` if there are no suggestion nodes in the schema.\n *\n * @param schema The editor schema to be used for suggestion nodes detection.\n *\n * @returns A `SuggestionSchemaInfo` object, or `null` if there are no suggestion nodes.\n */\nfunction buildSuggestionSchemaInfo(schema: Schema): SuggestionSchemaInfo | null {\n const suggestionNodes = Object.values(schema.nodes).filter((node) =>\n node.name.endsWith('Suggestion'),\n )\n\n if (suggestionNodes.length === 0) {\n return null\n }\n\n const triggerCharByScheme = new Map(\n suggestionNodes.map((node) => [\n kebabCase(node.name.replace(/Suggestion$/, '')),\n String(\n (node.spec as { triggerChar?: string }).triggerChar ??\n DEFAULT_SUGGESTION_TRIGGER_CHAR,\n ),\n ]),\n )\n\n const urlSchemes = [...triggerCharByScheme.keys()]\n\n return {\n urlSchemeRegex: `(?:${urlSchemes.join('|')})://`,\n triggerCharByScheme,\n }\n}\n\n/**\n * Extract all tags from the given parse rules argument, and returns an array of said tags.\n *\n * @param parseRules The parse rules for a DOM node or inline style.\n *\n * @returns An array of tags extracted from the parse rules.\n */\nfunction extractTagsFromParseRules(\n parseRules?: readonly ParseRule[],\n): (keyof HTMLElementTagNameMap)[] {\n if (!parseRules || parseRules.length === 0) {\n return []\n }\n\n return parseRules\n .filter((rule) => rule.tag)\n .map((rule) => rule.tag as keyof HTMLElementTagNameMap)\n}\n\nexport { buildSuggestionSchemaInfo, extractTagsFromParseRules }\n"],"mappings":";;;;;;;;;;AAgCA,SAAS,0BAA0B,QAA6C;CAC5E,MAAM,kBAAkB,OAAO,OAAO,OAAO,MAAM,CAAC,QAAQ,SACxD,KAAK,KAAK,SAAS,aAAa,CACnC;CAED,IAAI,gBAAgB,WAAW,GAC3B,OAAO;CAGX,MAAM,sBAAsB,IAAI,IAC5B,gBAAgB,KAAK,SAAS,CAC1B,UAAU,KAAK,KAAK,QAAQ,eAAe,GAAG,CAAC,EAC/C,OACK,KAAK,KAAkC,eAAA,IAE3C,CACJ,CAAC,CACL;CAID,OAAO;EACH,gBAAgB,MAAM,CAHN,GAAG,oBAAoB,MAAM,CAGb,CAAC,KAAK,IAAI,CAAC;EAC3C;EACH;;;;;;;;;AAUL,SAAS,0BACL,YAC+B;CAC/B,IAAI,CAAC,cAAc,WAAW,WAAW,GACrC,OAAO,EAAE;CAGb,OAAO,WACF,QAAQ,SAAS,KAAK,IAAI,CAC1B,KAAK,SAAS,KAAK,IAAmC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"unified.js","names":[],"sources":["../../src/helpers/unified.ts"],"sourcesContent":["import { is } from 'unist-util-is'\n\nimport type { Element, Node as HastNode, Text } from 'hast'\n\n/**\n * Determines whether a given hast node is an element node with a specific tag name.\n *\n * @param node The hast node to check.\n * @param tagName The tag name to check for.\n *\n * @returns `true` if the hast node is an element node with the specified tag name, `false`\n * otherwise.\n */\nfunction isHastElementNode(node: HastNode, tagName: Element['tagName']): node is Element {\n return is(node, { type: 'element', tagName })\n}\n\n/**\n * Determines whether a given hast node is a text node.\n *\n * @param node The hast node to check.\n *\n * @returns `true` if the hast node is a text node, `false` otherwise.\n */\nfunction isHastTextNode(node: HastNode): node is Text {\n return is(node, { type: 'text' })\n}\n\nexport { isHastElementNode, isHastTextNode }\n"],"mappings":";;;;;;;;;;;AAaA,SAAS,kBAAkB,MAAgB,SAA8C;
|
|
1
|
+
{"version":3,"file":"unified.js","names":[],"sources":["../../src/helpers/unified.ts"],"sourcesContent":["import { is } from 'unist-util-is'\n\nimport type { Element, Node as HastNode, Text } from 'hast'\n\n/**\n * Determines whether a given hast node is an element node with a specific tag name.\n *\n * @param node The hast node to check.\n * @param tagName The tag name to check for.\n *\n * @returns `true` if the hast node is an element node with the specified tag name, `false`\n * otherwise.\n */\nfunction isHastElementNode(node: HastNode, tagName: Element['tagName']): node is Element {\n return is(node, { type: 'element', tagName })\n}\n\n/**\n * Determines whether a given hast node is a text node.\n *\n * @param node The hast node to check.\n *\n * @returns `true` if the hast node is a text node, `false` otherwise.\n */\nfunction isHastTextNode(node: HastNode): node is Text {\n return is(node, { type: 'text' })\n}\n\nexport { isHastElementNode, isHastTextNode }\n"],"mappings":";;;;;;;;;;;AAaA,SAAS,kBAAkB,MAAgB,SAA8C;CACrF,OAAO,GAAG,MAAM;EAAE,MAAM;EAAW;EAAS,CAAC;;;;;;;;;AAUjD,SAAS,eAAe,MAA8B;CAClD,OAAO,GAAG,MAAM,EAAE,MAAM,QAAQ,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-editor.js","names":[],"sources":["../../src/hooks/use-editor.ts"],"sourcesContent":["import { DependencyList, useCallback, useEffect, useState } from 'react'\n\nimport { Editor } from '@tiptap/react'\n\nimport type { EditorOptions } from '@tiptap/core'\n\nfunction stateChanger(state: number) {\n return (state + 1) % Number.MAX_SAFE_INTEGER\n}\n\n/**\n * This is a copy of the `useRerender` hook from `@react-hookz/web`, which is a utility hook that\n * returns a function that can be called to force a re-render of the component.\n *\n * Turns out we don't have the need to use any of the other hooks from `@react-hookz/web`, which is\n * a peer dependency that often introduces breaking changes, causing upgrade issues across our\n * projects.\n */\nfunction useRerender(): () => void {\n const [, setState] = useState(0)\n\n return useCallback(() => {\n setState(stateChanger)\n }, [])\n}\n\n/**\n * This is a fork of the official `useEditor` hook with one key difference, which is to prevent a\n * `null` Editor object instance from being returned on the first render.\n *\n * This change was once fixed in the `@tiptap/react` package, but was reverted because it didn't\n * have support for server-side rendering ([ref](https://github.com/ueberdosis/tiptap/pull/2282)),\n * which is a problem we don't currently have.\n *\n * @param options The options to configure the editor component with.\n * @param dependencies If present, re-create the editor instance if the values in the list change.\n *\n * @returns A new editor instance with the given options.\n */\nfunction useEditor(\n options: Partial<EditorOptions> = {},\n dependencies: DependencyList = [],\n): Editor {\n const [editor, setEditor] = useState<Editor>(() => new Editor(options))\n\n const forceRerender = useRerender()\n\n useEffect(\n function initializeEditorInstance() {\n let instance: Editor\n\n if (editor.isDestroyed) {\n instance = new Editor(options)\n setEditor(instance)\n } else {\n instance = editor\n }\n\n instance.on('transaction', () => {\n requestAnimationFrame(() => {\n requestAnimationFrame(() => {\n if (!instance.isDestroyed) {\n forceRerender()\n }\n })\n })\n })\n\n return function destroyEditorInstance() {\n instance.destroy()\n }\n },\n //
|
|
1
|
+
{"version":3,"file":"use-editor.js","names":[],"sources":["../../src/hooks/use-editor.ts"],"sourcesContent":["import { DependencyList, useCallback, useEffect, useState } from 'react'\n\nimport { Editor } from '@tiptap/react'\n\nimport type { EditorOptions } from '@tiptap/core'\n\nfunction stateChanger(state: number) {\n return (state + 1) % Number.MAX_SAFE_INTEGER\n}\n\n/**\n * This is a copy of the `useRerender` hook from `@react-hookz/web`, which is a utility hook that\n * returns a function that can be called to force a re-render of the component.\n *\n * Turns out we don't have the need to use any of the other hooks from `@react-hookz/web`, which is\n * a peer dependency that often introduces breaking changes, causing upgrade issues across our\n * projects.\n */\nfunction useRerender(): () => void {\n const [, setState] = useState(0)\n\n return useCallback(() => {\n setState(stateChanger)\n }, [])\n}\n\n/**\n * This is a fork of the official `useEditor` hook with one key difference, which is to prevent a\n * `null` Editor object instance from being returned on the first render.\n *\n * This change was once fixed in the `@tiptap/react` package, but was reverted because it didn't\n * have support for server-side rendering ([ref](https://github.com/ueberdosis/tiptap/pull/2282)),\n * which is a problem we don't currently have.\n *\n * @param options The options to configure the editor component with.\n * @param dependencies If present, re-create the editor instance if the values in the list change.\n *\n * @returns A new editor instance with the given options.\n */\nfunction useEditor(\n options: Partial<EditorOptions> = {},\n dependencies: DependencyList = [],\n): Editor {\n const [editor, setEditor] = useState<Editor>(() => new Editor(options))\n\n const forceRerender = useRerender()\n\n // oxlint-disable-next-line react-hooks/exhaustive-deps\n useEffect(\n function initializeEditorInstance() {\n let instance: Editor\n\n if (editor.isDestroyed) {\n instance = new Editor(options)\n setEditor(instance)\n } else {\n instance = editor\n }\n\n instance.on('transaction', () => {\n requestAnimationFrame(() => {\n requestAnimationFrame(() => {\n if (!instance.isDestroyed) {\n forceRerender()\n }\n })\n })\n })\n\n return function destroyEditorInstance() {\n instance.destroy()\n }\n },\n // oxlint-disable-next-line react-hooks/exhaustive-deps\n dependencies,\n )\n\n return editor\n}\n\nexport { useEditor }\n"],"mappings":";;;AAMA,SAAS,aAAa,OAAe;CACjC,QAAQ,QAAQ,KAAK,OAAO;;;;;;;;;;AAWhC,SAAS,cAA0B;CAC/B,MAAM,GAAG,YAAY,SAAS,EAAE;CAEhC,OAAO,kBAAkB;EACrB,SAAS,aAAa;IACvB,EAAE,CAAC;;;;;;;;;;;;;;;AAgBV,SAAS,UACL,UAAkC,EAAE,EACpC,eAA+B,EAAE,EAC3B;CACN,MAAM,CAAC,QAAQ,aAAa,eAAuB,IAAI,OAAO,QAAQ,CAAC;CAEvE,MAAM,gBAAgB,aAAa;CAGnC,UACI,SAAS,2BAA2B;EAChC,IAAI;EAEJ,IAAI,OAAO,aAAa;GACpB,WAAW,IAAI,OAAO,QAAQ;GAC9B,UAAU,SAAS;SAEnB,WAAW;EAGf,SAAS,GAAG,qBAAqB;GAC7B,4BAA4B;IACxB,4BAA4B;KACxB,IAAI,CAAC,SAAS,aACV,eAAe;MAErB;KACJ;IACJ;EAEF,OAAO,SAAS,wBAAwB;GACpC,SAAS,SAAS;;IAI1B,aACH;CAED,OAAO"}
|
|
@@ -26,7 +26,7 @@ function createHTMLSerializerForPlainTextEditor(schema) {
|
|
|
26
26
|
let htmlResult = escape(markdown);
|
|
27
27
|
Object.values(schema.nodes).filter((node) => node.name.endsWith("Suggestion")).forEach((suggestionNode) => {
|
|
28
28
|
const linkSchema = kebabCase(suggestionNode.name.replace(/Suggestion$/, ""));
|
|
29
|
-
htmlResult = htmlResult.replace(new RegExp(`\\[([^\\[]+)\\]\\((?:${linkSchema}):\\/\\/(
|
|
29
|
+
htmlResult = htmlResult.replace(new RegExp(`\\[([^\\[]+)\\]\\((?:${linkSchema}):\\/\\/([^\\s)]+)\\)`, "gm"), `<span data-${linkSchema} data-id="$2" data-label="$1"></span>`);
|
|
30
30
|
});
|
|
31
31
|
return htmlResult.replace(/^([^\n]+)\n?|\n+/gm, `<p>$1</p>`);
|
|
32
32
|
} };
|