@doist/typist 11.0.0-next.1 → 11.0.0

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.
Files changed (48) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/CONTRIBUTING.md +0 -4
  3. package/README.md +0 -10
  4. package/dist/helpers/serializer.js +12 -1
  5. package/dist/helpers/serializer.js.map +1 -1
  6. package/dist/serializers/html/plugins/rehype-code-block.js +1 -1
  7. package/dist/serializers/html/plugins/rehype-code-block.js.map +1 -1
  8. package/dist/serializers/html/plugins/rehype-image.js +1 -1
  9. package/dist/serializers/html/plugins/rehype-image.js.map +1 -1
  10. package/dist/serializers/html/plugins/rehype-suggestions.js +1 -1
  11. package/dist/serializers/html/plugins/rehype-suggestions.js.map +1 -1
  12. package/dist/serializers/html/plugins/rehype-task-list.js +1 -1
  13. package/dist/serializers/html/plugins/rehype-task-list.js.map +1 -1
  14. package/dist/serializers/html/plugins/remark-autolink-literal.d.ts +2 -2
  15. package/dist/serializers/html/plugins/remark-autolink-literal.js +5 -5
  16. package/dist/serializers/html/plugins/remark-autolink-literal.js.map +1 -1
  17. package/dist/serializers/html/plugins/remark-disable-constructs.js +1 -1
  18. package/dist/serializers/html/plugins/remark-disable-constructs.js.map +1 -1
  19. package/dist/serializers/html/plugins/remark-strikethrough.d.ts +2 -2
  20. package/dist/serializers/html/plugins/remark-strikethrough.js +5 -5
  21. package/dist/serializers/html/plugins/remark-strikethrough.js.map +1 -1
  22. package/dist/serializers/markdown/markdown.d.ts +2 -2
  23. package/dist/serializers/markdown/markdown.d.ts.map +1 -1
  24. package/dist/serializers/markdown/markdown.js +54 -84
  25. package/dist/serializers/markdown/markdown.js.map +1 -1
  26. package/dist/serializers/markdown/plugins/image.js +34 -0
  27. package/dist/serializers/markdown/plugins/image.js.map +1 -0
  28. package/dist/serializers/markdown/plugins/list-item.js +37 -0
  29. package/dist/serializers/markdown/plugins/list-item.js.map +1 -0
  30. package/dist/serializers/markdown/plugins/paragraph.js +23 -0
  31. package/dist/serializers/markdown/plugins/paragraph.js.map +1 -0
  32. package/dist/serializers/markdown/plugins/strikethrough.js +27 -0
  33. package/dist/serializers/markdown/plugins/strikethrough.js.map +1 -0
  34. package/dist/serializers/markdown/plugins/suggestion.js +25 -0
  35. package/dist/serializers/markdown/plugins/suggestion.js.map +1 -0
  36. package/dist/serializers/markdown/plugins/task-item.js +36 -0
  37. package/dist/serializers/markdown/plugins/task-item.js.map +1 -0
  38. package/package.json +22 -30
  39. package/dist/serializers/markdown/plugins/rehype-image.js +0 -36
  40. package/dist/serializers/markdown/plugins/rehype-image.js.map +0 -1
  41. package/dist/serializers/markdown/plugins/rehype-suggestions.js +0 -38
  42. package/dist/serializers/markdown/plugins/rehype-suggestions.js.map +0 -1
  43. package/dist/serializers/markdown/plugins/rehype-task-list.js +0 -32
  44. package/dist/serializers/markdown/plugins/rehype-task-list.js.map +0 -1
  45. package/dist/serializers/markdown/plugins/remark-strikethrough.js +0 -20
  46. package/dist/serializers/markdown/plugins/remark-strikethrough.js.map +0 -1
  47. package/dist/serializers/markdown/plugins/remark-task-list.js +0 -20
  48. package/dist/serializers/markdown/plugins/remark-task-list.js.map +0 -1
package/CHANGELOG.md CHANGED
@@ -1,3 +1,22 @@
1
+ ## [11.0.0](https://github.com/Doist/typist/compare/v10.0.3...v11.0.0) (2026-05-26)
2
+
3
+ ### ⚠ BREAKING CHANGES
4
+
5
+ * **deps:** react and react-dom 17 are no longer supported. Consumers
6
+ must be on react/react-dom ^18.0.0.
7
+
8
+ * chore(deps): keep storybook-only deps as dev and drop unused entries
9
+
10
+ Address doistbot review:
11
+ - Move rehype-raw and remark-gfm back to devDependencies (only used in
12
+ stories/, which is not part of the published files).
13
+ - Drop rehype, remark, and hast-util-is-element entirely (no import sites
14
+ anywhere in the repo).
15
+
16
+ ### Miscellaneous Chores
17
+
18
+ * **deps:** drop React 17 support and move runtime peer deps into dependencies ([#1351](https://github.com/Doist/typist/issues/1351)) ([6bdd2fc](https://github.com/Doist/typist/commit/6bdd2fcfcbdd57ae4a811c20a6997a3573e62e3e))
19
+
1
20
  ## [10.0.3](https://github.com/Doist/typist/compare/v10.0.2...v10.0.3) (2026-05-23)
2
21
 
3
22
  ### Bug Fixes
package/CONTRIBUTING.md CHANGED
@@ -49,10 +49,6 @@ cd typist
49
49
 
50
50
  3. Install all dependencies by running `npm install`;
51
51
 
52
- > **Note**
53
- >
54
- > If you're not using **npm 7+**, install peer dependencies with `npx npm-install-peers`.
55
-
56
52
  ## Development Workflow
57
53
 
58
54
  After cloning Typist and installing all dependencies, several commands are at your disposal:
package/README.md CHANGED
@@ -32,16 +32,6 @@ Typist is the mighty [Tiptap](https://tiptap.dev/)-based rich-text editor React
32
32
  npm install --save @doist/typist
33
33
  ```
34
34
 
35
- ### Peer Dependencies
36
-
37
- If you are using **npm 7+** and the `legacy-peer-deps` options is not enabled, peer dependencies should have been automatically installed for you with the command above. Otherwise, you can install them with:
38
-
39
- ```sh
40
- npm info @doist/typist peerDependencies --json \
41
- | command sed 's/[\{\},]//g ; s/: /@/g' \
42
- | xargs npm install --save
43
- ```
44
-
45
35
  ## Usage
46
36
 
47
37
  ```tsx
@@ -53,7 +53,18 @@ function computeSuggestionTriggerCharsId(schema) {
53
53
  if (!suggestionSchemaInfo) return "";
54
54
  return [...suggestionSchemaInfo.triggerCharByScheme].map(([scheme, triggerChar]) => `${scheme}=${triggerChar}`).join();
55
55
  }
56
+ /**
57
+ * Extract all tags from the given parse rules argument, and returns an array of said tags.
58
+ *
59
+ * @param parseRules The parse rules for a DOM node or inline style.
60
+ *
61
+ * @returns An array of tags extracted from the parse rules.
62
+ */
63
+ function extractTagsFromParseRules(parseRules) {
64
+ if (!parseRules || parseRules.length === 0) return [];
65
+ return parseRules.filter((rule) => rule.tag).map((rule) => rule.tag);
66
+ }
56
67
  //#endregion
57
- export { buildSuggestionSchemaInfo, computeSuggestionTriggerCharsId };
68
+ export { buildSuggestionSchemaInfo, computeSuggestionTriggerCharsId, extractTagsFromParseRules, getSuggestionNodes, getSuggestionUrlScheme };
58
69
 
59
70
  //# 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 { DEFAULT_SUGGESTION_TRIGGER_CHAR } from '../constants/suggestions'\n\nimport type { NodeType, ParseRule, Schema } from '@tiptap/pm/model'\n\n/**\n * Extracts the URL scheme used by a suggestion node (e.g. `mention`, `channel`) from its node\n * name (e.g. `mentionSuggestion`, `channelSuggestion`).\n *\n * @param node The suggestion node type.\n *\n * @returns The URL scheme as a kebab-case string.\n */\nfunction getSuggestionUrlScheme(node: NodeType): string {\n return kebabCase(node.name.replace(/Suggestion$/, ''))\n}\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 * Returns all suggestion nodes available in the given editor schema (e.g. `mentionSuggestion`,\n * `channelSuggestion`).\n *\n * @param schema The editor schema to be used for suggestion nodes detection.\n *\n * @returns An array of `NodeType` objects for the available suggestion nodes.\n */\nfunction getSuggestionNodes(schema: Schema): NodeType[] {\n return Object.values(schema.nodes).filter((node) => node.name.endsWith('Suggestion'))\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 = getSuggestionNodes(schema)\n\n if (suggestionNodes.length === 0) {\n return null\n }\n\n const triggerCharByScheme = new Map(\n suggestionNodes.map((node) => [\n getSuggestionUrlScheme(node),\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 * Computes a string ID that identifies the configured trigger characters of all the suggestion\n * nodes in the given editor schema. Used to discriminate cache keys for serializers whose output\n * depends on the trigger character (e.g. the HTML serializer).\n *\n * @param schema The current editor document schema.\n *\n * @returns A string ID matching the suggestion trigger characters in the schema.\n */\nfunction computeSuggestionTriggerCharsId(schema: Schema): string {\n const suggestionSchemaInfo = buildSuggestionSchemaInfo(schema)\n\n if (!suggestionSchemaInfo) {\n return ''\n }\n\n return [...suggestionSchemaInfo.triggerCharByScheme]\n .map(([scheme, triggerChar]) => `${scheme}=${triggerChar}`)\n .join()\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 {\n buildSuggestionSchemaInfo,\n computeSuggestionTriggerCharsId,\n extractTagsFromParseRules,\n getSuggestionNodes,\n getSuggestionUrlScheme,\n}\n"],"mappings":";;;;;;;;;;AAcA,SAAS,uBAAuB,MAAwB;CACpD,OAAO,UAAU,KAAK,KAAK,QAAQ,eAAe,GAAG,CAAC;;;;;;;;;;AA6B1D,SAAS,mBAAmB,QAA4B;CACpD,OAAO,OAAO,OAAO,OAAO,MAAM,CAAC,QAAQ,SAAS,KAAK,KAAK,SAAS,aAAa,CAAC;;;;;;;;;;AAWzF,SAAS,0BAA0B,QAA6C;CAC5E,MAAM,kBAAkB,mBAAmB,OAAO;CAElD,IAAI,gBAAgB,WAAW,GAC3B,OAAO;CAGX,MAAM,sBAAsB,IAAI,IAC5B,gBAAgB,KAAK,SAAS,CAC1B,uBAAuB,KAAK,EAC5B,OACK,KAAK,KAAkC,eAAA,IAE3C,CACJ,CAAC,CACL;CAID,OAAO;EACH,gBAAgB,MAAM,CAHN,GAAG,oBAAoB,MAAM,CAGb,CAAC,KAAK,IAAI,CAAC;EAC3C;EACH;;;;;;;;;;;AAYL,SAAS,gCAAgC,QAAwB;CAC7D,MAAM,uBAAuB,0BAA0B,OAAO;CAE9D,IAAI,CAAC,sBACD,OAAO;CAGX,OAAO,CAAC,GAAG,qBAAqB,oBAAoB,CAC/C,KAAK,CAAC,QAAQ,iBAAiB,GAAG,OAAO,GAAG,cAAc,CAC1D,MAAM"}
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 { NodeType, ParseRule, Schema } from '@tiptap/pm/model'\n\n/**\n * Extracts the URL scheme used by a suggestion node (e.g. `mention`, `channel`) from its node\n * name (e.g. `mentionSuggestion`, `channelSuggestion`).\n *\n * @param node The suggestion node type.\n *\n * @returns The URL scheme as a kebab-case string.\n */\nfunction getSuggestionUrlScheme(node: NodeType): string {\n return kebabCase(node.name.replace(/Suggestion$/, ''))\n}\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 * Returns all suggestion nodes available in the given editor schema (e.g. `mentionSuggestion`,\n * `channelSuggestion`).\n *\n * @param schema The editor schema to be used for suggestion nodes detection.\n *\n * @returns An array of `NodeType` objects for the available suggestion nodes.\n */\nfunction getSuggestionNodes(schema: Schema): NodeType[] {\n return Object.values(schema.nodes).filter((node) => node.name.endsWith('Suggestion'))\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 = getSuggestionNodes(schema)\n\n if (suggestionNodes.length === 0) {\n return null\n }\n\n const triggerCharByScheme = new Map(\n suggestionNodes.map((node) => [\n getSuggestionUrlScheme(node),\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 * Computes a string ID that identifies the configured trigger characters of all the suggestion\n * nodes in the given editor schema. Used to discriminate cache keys for serializers whose output\n * depends on the trigger character (e.g. the HTML serializer).\n *\n * @param schema The current editor document schema.\n *\n * @returns A string ID matching the suggestion trigger characters in the schema.\n */\nfunction computeSuggestionTriggerCharsId(schema: Schema): string {\n const suggestionSchemaInfo = buildSuggestionSchemaInfo(schema)\n\n if (!suggestionSchemaInfo) {\n return ''\n }\n\n return [...suggestionSchemaInfo.triggerCharByScheme]\n .map(([scheme, triggerChar]) => `${scheme}=${triggerChar}`)\n .join()\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 {\n buildSuggestionSchemaInfo,\n computeSuggestionTriggerCharsId,\n extractTagsFromParseRules,\n getSuggestionNodes,\n getSuggestionUrlScheme,\n}\n"],"mappings":";;;;;;;;;;AAcA,SAAS,uBAAuB,MAAwB;CACpD,OAAO,UAAU,KAAK,KAAK,QAAQ,eAAe,GAAG,CAAC;;;;;;;;;;AA6B1D,SAAS,mBAAmB,QAA4B;CACpD,OAAO,OAAO,OAAO,OAAO,MAAM,CAAC,QAAQ,SAAS,KAAK,KAAK,SAAS,aAAa,CAAC;;;;;;;;;;AAWzF,SAAS,0BAA0B,QAA6C;CAC5E,MAAM,kBAAkB,mBAAmB,OAAO;CAElD,IAAI,gBAAgB,WAAW,GAC3B,OAAO;CAGX,MAAM,sBAAsB,IAAI,IAC5B,gBAAgB,KAAK,SAAS,CAC1B,uBAAuB,KAAK,EAC5B,OACK,KAAK,KAAkC,eAAA,IAE3C,CACJ,CAAC,CACL;CAID,OAAO;EACH,gBAAgB,MAAM,CAHN,GAAG,oBAAoB,MAAM,CAGb,CAAC,KAAK,IAAI,CAAC;EAC3C;EACH;;;;;;;;;;;AAYL,SAAS,gCAAgC,QAAwB;CAC7D,MAAM,uBAAuB,0BAA0B,OAAO;CAE9D,IAAI,CAAC,sBACD,OAAO;CAGX,OAAO,CAAC,GAAG,qBAAqB,oBAAoB,CAC/C,KAAK,CAAC,QAAQ,iBAAiB,GAAG,OAAO,GAAG,cAAc,CAC1D,MAAM;;;;;;;;;AAUf,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"}
@@ -8,7 +8,7 @@ import { visit } from "unist-util-visit";
8
8
  * the end of the code block.
9
9
  */
10
10
  function rehypeCodeBlock() {
11
- return (tree) => {
11
+ return (...[tree]) => {
12
12
  visit(tree, "element", (node) => {
13
13
  if (isHastElementNode(node, "pre") && isHastElementNode(node.children[0], "code") && isHastTextNode(node.children[0].children[0])) node.children[0].children[0].value = node.children[0].children[0].value.replace(/\n$/, "");
14
14
  });
@@ -1 +1 @@
1
- {"version":3,"file":"rehype-code-block.js","names":[],"sources":["../../../../src/serializers/html/plugins/rehype-code-block.ts"],"sourcesContent":["import { visit } from 'unist-util-visit'\n\nimport { isHastElementNode, isHastTextNode } from '../../../helpers/unified'\n\nimport type { Node as HastNode } from 'hast'\nimport type { Transformer } from 'unified'\n\n/**\n * A rehype plugin to remove the trailing newline from code blocks (i.e. the newline between the\n * last code line and `</code></pre>`). Although that newline is part of the CommonMark\n * specification, this custom plugin is required to prevent Tiptap from rendering a blank line at\n * the end of the code block.\n */\nfunction rehypeCodeBlock(): Transformer {\n return (tree: HastNode) => {\n visit(tree, 'element', (node: HastNode) => {\n if (\n isHastElementNode(node, 'pre') &&\n isHastElementNode(node.children[0], 'code') &&\n isHastTextNode(node.children[0].children[0])\n ) {\n node.children[0].children[0].value = node.children[0].children[0].value.replace(\n /\\n$/,\n '',\n )\n }\n })\n\n return tree\n }\n}\n\nexport { rehypeCodeBlock }\n"],"mappings":";;;;;;;;;AAaA,SAAS,kBAA+B;CACpC,QAAQ,SAAmB;EACvB,MAAM,MAAM,YAAY,SAAmB;GACvC,IACI,kBAAkB,MAAM,MAAM,IAC9B,kBAAkB,KAAK,SAAS,IAAI,OAAO,IAC3C,eAAe,KAAK,SAAS,GAAG,SAAS,GAAG,EAE5C,KAAK,SAAS,GAAG,SAAS,GAAG,QAAQ,KAAK,SAAS,GAAG,SAAS,GAAG,MAAM,QACpE,OACA,GACH;IAEP;EAEF,OAAO"}
1
+ {"version":3,"file":"rehype-code-block.js","names":[],"sources":["../../../../src/serializers/html/plugins/rehype-code-block.ts"],"sourcesContent":["import { visit } from 'unist-util-visit'\n\nimport { isHastElementNode, isHastTextNode } from '../../../helpers/unified'\n\nimport type { Node as HastNode } from 'hast'\nimport type { Transformer } from 'unified'\n\n/**\n * A rehype plugin to remove the trailing newline from code blocks (i.e. the newline between the\n * last code line and `</code></pre>`). Although that newline is part of the CommonMark\n * specification, this custom plugin is required to prevent Tiptap from rendering a blank line at\n * the end of the code block.\n */\nfunction rehypeCodeBlock(): Transformer {\n return (...[tree]: Parameters<Transformer>): ReturnType<Transformer> => {\n visit(tree, 'element', (node: HastNode) => {\n if (\n isHastElementNode(node, 'pre') &&\n isHastElementNode(node.children[0], 'code') &&\n isHastTextNode(node.children[0].children[0])\n ) {\n node.children[0].children[0].value = node.children[0].children[0].value.replace(\n /\\n$/,\n '',\n )\n }\n })\n\n return tree\n }\n}\n\nexport { rehypeCodeBlock }\n"],"mappings":";;;;;;;;;AAaA,SAAS,kBAA+B;CACpC,QAAQ,GAAG,CAAC,UAA4D;EACpE,MAAM,MAAM,YAAY,SAAmB;GACvC,IACI,kBAAkB,MAAM,MAAM,IAC9B,kBAAkB,KAAK,SAAS,IAAI,OAAO,IAC3C,eAAe,KAAK,SAAS,GAAG,SAAS,GAAG,EAE5C,KAAK,SAAS,GAAG,SAAS,GAAG,QAAQ,KAAK,SAAS,GAAG,SAAS,GAAG,MAAM,QACpE,OACA,GACH;IAEP;EAEF,OAAO"}
@@ -10,7 +10,7 @@ import { remove } from "unist-util-remove";
10
10
  */
11
11
  function rehypeImage(schema) {
12
12
  if (schema.nodes.image ? schema.nodes.image.spec.inline : false) return (tree) => tree;
13
- return (tree) => {
13
+ return (...[tree]) => {
14
14
  visit(tree, "element", (node, index, parent) => {
15
15
  if (isHastElementNode(node, "p")) if (node.children.every((c) => isHastElementNode(c, "img"))) parent.children.splice(index, 1, ...node.children);
16
16
  else remove(node, (n) => isHastElementNode(n, "img"));
@@ -1 +1 @@
1
- {"version":3,"file":"rehype-image.js","names":[],"sources":["../../../../src/serializers/html/plugins/rehype-image.ts"],"sourcesContent":["import { remove } from 'unist-util-remove'\nimport { visit } from 'unist-util-visit'\n\nimport { isHastElementNode } from '../../../helpers/unified'\n\nimport type { Schema } from '@tiptap/pm/model'\nimport type { Node as HastNode, Parent as HastParent } from 'hast'\nimport type { Transformer } from 'unified'\n\n/**\n * A rehype plugin to remove the wrapping paragraph from images and to remove all inline images if\n * the editor was configured without inline image support (Tiptap default).\n *\n * @param schema The editor schema to be used for nodes and marks detection.\n */\nfunction rehypeImage(schema: Schema): Transformer {\n const allowInlineImages = schema.nodes.image ? schema.nodes.image.spec.inline : false\n\n // Return the tree as-is if the editor does not support inline images\n if (allowInlineImages) {\n return (tree) => tree\n }\n\n return (tree: HastNode) => {\n visit(tree, 'element', (node: HastNode, index: number, parent: HastParent) => {\n if (isHastElementNode(node, 'p')) {\n const areAllChildrenImages = node.children.every((c) => isHastElementNode(c, 'img'))\n\n // Replace the paragraph with the image children if all children are images, or\n // remove all images from the paragraph if it contains non-image children since the\n // editor does not support inline images\n if (areAllChildrenImages) {\n parent.children.splice(index, 1, ...node.children)\n } else {\n remove(node, (n) => isHastElementNode(n, 'img'))\n }\n }\n })\n\n return tree\n }\n}\n\nexport { rehypeImage }\n"],"mappings":";;;;;;;;;;AAeA,SAAS,YAAY,QAA6B;CAI9C,IAH0B,OAAO,MAAM,QAAQ,OAAO,MAAM,MAAM,KAAK,SAAS,OAI5E,QAAQ,SAAS;CAGrB,QAAQ,SAAmB;EACvB,MAAM,MAAM,YAAY,MAAgB,OAAe,WAAuB;GAC1E,IAAI,kBAAkB,MAAM,IAAI,EAM5B,IAL6B,KAAK,SAAS,OAAO,MAAM,kBAAkB,GAAG,MAAM,CAK3D,EACpB,OAAO,SAAS,OAAO,OAAO,GAAG,GAAG,KAAK,SAAS;QAElD,OAAO,OAAO,MAAM,kBAAkB,GAAG,MAAM,CAAC;IAG1D;EAEF,OAAO"}
1
+ {"version":3,"file":"rehype-image.js","names":[],"sources":["../../../../src/serializers/html/plugins/rehype-image.ts"],"sourcesContent":["import { remove } from 'unist-util-remove'\nimport { visit } from 'unist-util-visit'\n\nimport { isHastElementNode } from '../../../helpers/unified'\n\nimport type { Schema } from '@tiptap/pm/model'\nimport type { Node as HastNode, Parent as HastParent } from 'hast'\nimport type { Transformer } from 'unified'\n\n/**\n * A rehype plugin to remove the wrapping paragraph from images and to remove all inline images if\n * the editor was configured without inline image support (Tiptap default).\n *\n * @param schema The editor schema to be used for nodes and marks detection.\n */\nfunction rehypeImage(schema: Schema): Transformer {\n const allowInlineImages = schema.nodes.image ? schema.nodes.image.spec.inline : false\n\n // Return the tree as-is if the editor does not support inline images\n if (allowInlineImages) {\n return (tree) => tree\n }\n\n return (...[tree]: Parameters<Transformer>): ReturnType<Transformer> => {\n visit(tree, 'element', (node: HastNode, index: number, parent: HastParent) => {\n if (isHastElementNode(node, 'p')) {\n const areAllChildrenImages = node.children.every((c) => isHastElementNode(c, 'img'))\n\n // Replace the paragraph with the image children if all children are images, or\n // remove all images from the paragraph if it contains non-image children since the\n // editor does not support inline images\n if (areAllChildrenImages) {\n parent.children.splice(index, 1, ...node.children)\n } else {\n remove(node, (n) => isHastElementNode(n, 'img'))\n }\n }\n })\n\n return tree\n }\n}\n\nexport { rehypeImage }\n"],"mappings":";;;;;;;;;;AAeA,SAAS,YAAY,QAA6B;CAI9C,IAH0B,OAAO,MAAM,QAAQ,OAAO,MAAM,MAAM,KAAK,SAAS,OAI5E,QAAQ,SAAS;CAGrB,QAAQ,GAAG,CAAC,UAA4D;EACpE,MAAM,MAAM,YAAY,MAAgB,OAAe,WAAuB;GAC1E,IAAI,kBAAkB,MAAM,IAAI,EAM5B,IAL6B,KAAK,SAAS,OAAO,MAAM,kBAAkB,GAAG,MAAM,CAK3D,EACpB,OAAO,SAAS,OAAO,OAAO,GAAG,GAAG,KAAK,SAAS;QAElD,OAAO,OAAO,MAAM,kBAAkB,GAAG,MAAM,CAAC;IAG1D;EAEF,OAAO"}
@@ -11,7 +11,7 @@ function rehypeSuggestions(schema) {
11
11
  const suggestionSchemaInfo = buildSuggestionSchemaInfo(schema);
12
12
  if (!suggestionSchemaInfo) return (tree) => tree;
13
13
  const suggestionSchemaRegex = new RegExp(`^${suggestionSchemaInfo.urlSchemeRegex}`);
14
- return (tree) => {
14
+ return (...[tree]) => {
15
15
  visit(tree, "element", (node) => {
16
16
  if (isHastElementNode(node, "a") && suggestionSchemaRegex.test(String(node.properties?.href))) {
17
17
  const [, urlScheme, id] = /^([a-z-]+):\/\/(\S+)$/i.exec(String(node.properties?.href)) || [];
@@ -1 +1 @@
1
- {"version":3,"file":"rehype-suggestions.js","names":[],"sources":["../../../../src/serializers/html/plugins/rehype-suggestions.ts"],"sourcesContent":["import { visit } from 'unist-util-visit'\n\nimport { buildSuggestionSchemaInfo } from '../../../helpers/serializer'\nimport { isHastElementNode, isHastTextNode } from '../../../helpers/unified'\n\nimport type { Schema } from '@tiptap/pm/model'\nimport type { Node as HastNode } from 'hast'\nimport type { Transformer } from 'unified'\n\n/**\n * A rehype plugin to add support for suggestions nodes (e.g., `@username` or `#channel).\n *\n * @param schema The editor schema to be used for suggestion nodes detection.\n */\nfunction rehypeSuggestions(schema: Schema): Transformer {\n const suggestionSchemaInfo = buildSuggestionSchemaInfo(schema)\n\n // Return the tree as-is if the editor does not support suggestions\n if (!suggestionSchemaInfo) {\n return (tree: HastNode) => tree\n }\n\n const suggestionSchemaRegex = new RegExp(`^${suggestionSchemaInfo.urlSchemeRegex}`)\n\n return (tree: HastNode) => {\n visit(tree, 'element', (node: HastNode) => {\n if (\n isHastElementNode(node, 'a') &&\n suggestionSchemaRegex.test(String(node.properties?.href))\n ) {\n const [, urlScheme, id] =\n /^([a-z-]+):\\/\\/(\\S+)$/i.exec(String(node.properties?.href)) || []\n\n // Replace the link element with a span containing the suggestion attributes,\n // keeping the visible label (prefixed with the trigger character) as text content\n // so the span renders correctly when used outside of an editor.\n if (urlScheme && id && isHastTextNode(node.children[0])) {\n const label = node.children[0].value\n\n // The URL scheme was matched against the regex built from the same map of\n // suggestion nodes, so the trigger character is guaranteed to exist\n const triggerChar = suggestionSchemaInfo.triggerCharByScheme.get(\n urlScheme,\n ) as string\n\n node.tagName = 'span'\n node.properties = {\n [`data-${urlScheme}`]: '',\n 'data-id': id,\n 'data-label': label,\n }\n node.children[0].value = `${triggerChar}${label}`\n }\n }\n })\n\n return tree\n }\n}\n\nexport { rehypeSuggestions }\n"],"mappings":";;;;;;;;;AAcA,SAAS,kBAAkB,QAA6B;CACpD,MAAM,uBAAuB,0BAA0B,OAAO;CAG9D,IAAI,CAAC,sBACD,QAAQ,SAAmB;CAG/B,MAAM,wBAAwB,IAAI,OAAO,IAAI,qBAAqB,iBAAiB;CAEnF,QAAQ,SAAmB;EACvB,MAAM,MAAM,YAAY,SAAmB;GACvC,IACI,kBAAkB,MAAM,IAAI,IAC5B,sBAAsB,KAAK,OAAO,KAAK,YAAY,KAAK,CAAC,EAC3D;IACE,MAAM,GAAG,WAAW,MAChB,yBAAyB,KAAK,OAAO,KAAK,YAAY,KAAK,CAAC,IAAI,EAAE;IAKtE,IAAI,aAAa,MAAM,eAAe,KAAK,SAAS,GAAG,EAAE;KACrD,MAAM,QAAQ,KAAK,SAAS,GAAG;KAI/B,MAAM,cAAc,qBAAqB,oBAAoB,IACzD,UACH;KAED,KAAK,UAAU;KACf,KAAK,aAAa;OACb,QAAQ,cAAc;MACvB,WAAW;MACX,cAAc;MACjB;KACD,KAAK,SAAS,GAAG,QAAQ,GAAG,cAAc;;;IAGpD;EAEF,OAAO"}
1
+ {"version":3,"file":"rehype-suggestions.js","names":[],"sources":["../../../../src/serializers/html/plugins/rehype-suggestions.ts"],"sourcesContent":["import { visit } from 'unist-util-visit'\n\nimport { buildSuggestionSchemaInfo } from '../../../helpers/serializer'\nimport { isHastElementNode, isHastTextNode } from '../../../helpers/unified'\n\nimport type { Schema } from '@tiptap/pm/model'\nimport type { Node as HastNode } from 'hast'\nimport type { Transformer } from 'unified'\n\n/**\n * A rehype plugin to add support for suggestions nodes (e.g., `@username` or `#channel).\n *\n * @param schema The editor schema to be used for suggestion nodes detection.\n */\nfunction rehypeSuggestions(schema: Schema): Transformer {\n const suggestionSchemaInfo = buildSuggestionSchemaInfo(schema)\n\n // Return the tree as-is if the editor does not support suggestions\n if (!suggestionSchemaInfo) {\n return (tree: HastNode) => tree\n }\n\n const suggestionSchemaRegex = new RegExp(`^${suggestionSchemaInfo.urlSchemeRegex}`)\n\n return (...[tree]: Parameters<Transformer>): ReturnType<Transformer> => {\n visit(tree, 'element', (node: HastNode) => {\n if (\n isHastElementNode(node, 'a') &&\n suggestionSchemaRegex.test(String(node.properties?.href))\n ) {\n const [, urlScheme, id] =\n /^([a-z-]+):\\/\\/(\\S+)$/i.exec(String(node.properties?.href)) || []\n\n // Replace the link element with a span containing the suggestion attributes,\n // keeping the visible label (prefixed with the trigger character) as text content\n // so the span renders correctly when used outside of an editor.\n if (urlScheme && id && isHastTextNode(node.children[0])) {\n const label = node.children[0].value\n\n // The URL scheme was matched against the regex built from the same map of\n // suggestion nodes, so the trigger character is guaranteed to exist\n const triggerChar = suggestionSchemaInfo.triggerCharByScheme.get(\n urlScheme,\n ) as string\n\n node.tagName = 'span'\n node.properties = {\n [`data-${urlScheme}`]: '',\n 'data-id': id,\n 'data-label': label,\n }\n node.children[0].value = `${triggerChar}${label}`\n }\n }\n })\n\n return tree\n }\n}\n\nexport { rehypeSuggestions }\n"],"mappings":";;;;;;;;;AAcA,SAAS,kBAAkB,QAA6B;CACpD,MAAM,uBAAuB,0BAA0B,OAAO;CAG9D,IAAI,CAAC,sBACD,QAAQ,SAAmB;CAG/B,MAAM,wBAAwB,IAAI,OAAO,IAAI,qBAAqB,iBAAiB;CAEnF,QAAQ,GAAG,CAAC,UAA4D;EACpE,MAAM,MAAM,YAAY,SAAmB;GACvC,IACI,kBAAkB,MAAM,IAAI,IAC5B,sBAAsB,KAAK,OAAO,KAAK,YAAY,KAAK,CAAC,EAC3D;IACE,MAAM,GAAG,WAAW,MAChB,yBAAyB,KAAK,OAAO,KAAK,YAAY,KAAK,CAAC,IAAI,EAAE;IAKtE,IAAI,aAAa,MAAM,eAAe,KAAK,SAAS,GAAG,EAAE;KACrD,MAAM,QAAQ,KAAK,SAAS,GAAG;KAI/B,MAAM,cAAc,qBAAqB,oBAAoB,IACzD,UACH;KAED,KAAK,UAAU;KACf,KAAK,aAAa;OACb,QAAQ,cAAc;MACvB,WAAW;MACX,cAAc;MACjB;KACD,KAAK,SAAS,GAAG,QAAQ,GAAG,cAAc;;;IAGpD;EAEF,OAAO"}
@@ -5,7 +5,7 @@ import { visit } from "unist-util-visit";
5
5
  * A rehype plugin to add support for Tiptap task lists (i.e., `* [ ] Task`).
6
6
  */
7
7
  function rehypeTaskList() {
8
- return (tree) => {
8
+ return (...[tree]) => {
9
9
  visit(tree, "element", (node) => {
10
10
  if (isHastElementNode(node, "ul")) {
11
11
  if (node.children.every((c) => isHastElementNode(c, "li") && isHastTextNode(c.children[0]) && /^\[[ x]\] /i.test(c.children[0].value))) {
@@ -1 +1 @@
1
- {"version":3,"file":"rehype-task-list.js","names":[],"sources":["../../../../src/serializers/html/plugins/rehype-task-list.ts"],"sourcesContent":["import { visit } from 'unist-util-visit'\n\nimport { isHastElementNode, isHastTextNode } from '../../../helpers/unified'\n\nimport type { Node as HastNode } from 'hast'\nimport type { Transformer } from 'unified'\n\n/**\n * A rehype plugin to add support for Tiptap task lists (i.e., `* [ ] Task`).\n */\nfunction rehypeTaskList(): Transformer {\n return (tree: HastNode) => {\n visit(tree, 'element', (node: HastNode) => {\n if (isHastElementNode(node, 'ul')) {\n const areAllChildrenTaskItems = node.children.every(\n (c) =>\n isHastElementNode(c, 'li') &&\n isHastTextNode(c.children[0]) &&\n /^\\[[ x]\\] /i.test(c.children[0].value),\n )\n\n // Add the required attributes to the list and list items if all children are tasks,\n // removing the `[ ] ` or `[x] ` at the beginning of the task item text\n if (areAllChildrenTaskItems) {\n node.properties = {\n ...node.properties,\n 'data-type': 'taskList',\n }\n\n node.children.forEach((c) => {\n if (isHastElementNode(c, 'li') && isHastTextNode(c.children[0])) {\n c.properties = {\n ...c.properties,\n 'data-type': 'taskItem',\n 'data-checked': String(/^\\[x\\]/i.test(c.children[0].value)),\n }\n\n c.children[0].value = c.children[0].value.substring(4).trim()\n }\n })\n }\n }\n })\n\n return tree\n }\n}\n\nexport { rehypeTaskList }\n"],"mappings":";;;;;;AAUA,SAAS,iBAA8B;CACnC,QAAQ,SAAmB;EACvB,MAAM,MAAM,YAAY,SAAmB;GACvC,IAAI,kBAAkB,MAAM,KAAK;QACG,KAAK,SAAS,OACzC,MACG,kBAAkB,GAAG,KAAK,IAC1B,eAAe,EAAE,SAAS,GAAG,IAC7B,cAAc,KAAK,EAAE,SAAS,GAAG,MAAM,CAKpB,EAAE;KACzB,KAAK,aAAa;MACd,GAAG,KAAK;MACR,aAAa;MAChB;KAED,KAAK,SAAS,SAAS,MAAM;MACzB,IAAI,kBAAkB,GAAG,KAAK,IAAI,eAAe,EAAE,SAAS,GAAG,EAAE;OAC7D,EAAE,aAAa;QACX,GAAG,EAAE;QACL,aAAa;QACb,gBAAgB,OAAO,UAAU,KAAK,EAAE,SAAS,GAAG,MAAM,CAAC;QAC9D;OAED,EAAE,SAAS,GAAG,QAAQ,EAAE,SAAS,GAAG,MAAM,UAAU,EAAE,CAAC,MAAM;;OAEnE;;;IAGZ;EAEF,OAAO"}
1
+ {"version":3,"file":"rehype-task-list.js","names":[],"sources":["../../../../src/serializers/html/plugins/rehype-task-list.ts"],"sourcesContent":["import { visit } from 'unist-util-visit'\n\nimport { isHastElementNode, isHastTextNode } from '../../../helpers/unified'\n\nimport type { Node as HastNode } from 'hast'\nimport type { Transformer } from 'unified'\n\n/**\n * A rehype plugin to add support for Tiptap task lists (i.e., `* [ ] Task`).\n */\nfunction rehypeTaskList(): Transformer {\n return (...[tree]: Parameters<Transformer>): ReturnType<Transformer> => {\n visit(tree, 'element', (node: HastNode) => {\n if (isHastElementNode(node, 'ul')) {\n const areAllChildrenTaskItems = node.children.every(\n (c) =>\n isHastElementNode(c, 'li') &&\n isHastTextNode(c.children[0]) &&\n /^\\[[ x]\\] /i.test(c.children[0].value),\n )\n\n // Add the required attributes to the list and list items if all children are tasks,\n // removing the `[ ] ` or `[x] ` at the beginning of the task item text\n if (areAllChildrenTaskItems) {\n node.properties = {\n ...node.properties,\n 'data-type': 'taskList',\n }\n\n node.children.forEach((c) => {\n if (isHastElementNode(c, 'li') && isHastTextNode(c.children[0])) {\n c.properties = {\n ...c.properties,\n 'data-type': 'taskItem',\n 'data-checked': String(/^\\[x\\]/i.test(c.children[0].value)),\n }\n\n c.children[0].value = c.children[0].value.substring(4).trim()\n }\n })\n }\n }\n })\n\n return tree\n }\n}\n\nexport { rehypeTaskList }\n"],"mappings":";;;;;;AAUA,SAAS,iBAA8B;CACnC,QAAQ,GAAG,CAAC,UAA4D;EACpE,MAAM,MAAM,YAAY,SAAmB;GACvC,IAAI,kBAAkB,MAAM,KAAK;QACG,KAAK,SAAS,OACzC,MACG,kBAAkB,GAAG,KAAK,IAC1B,eAAe,EAAE,SAAS,GAAG,IAC7B,cAAc,KAAK,EAAE,SAAS,GAAG,MAAM,CAKpB,EAAE;KACzB,KAAK,aAAa;MACd,GAAG,KAAK;MACR,aAAa;MAChB;KAED,KAAK,SAAS,SAAS,MAAM;MACzB,IAAI,kBAAkB,GAAG,KAAK,IAAI,eAAe,EAAE,SAAS,GAAG,EAAE;OAC7D,EAAE,aAAa;QACX,GAAG,EAAE;QACL,aAAa;QACb,gBAAgB,OAAO,UAAU,KAAK,EAAE,SAAS,GAAG,MAAM,CAAC;QAC9D;OAED,EAAE,SAAS,GAAG,QAAQ,EAAE,SAAS,GAAG,MAAM,UAAU,EAAE,CAAC,MAAM;;OAEnE;;;IAGZ;EAEF,OAAO"}
@@ -9,8 +9,8 @@ import { Processor } from "unified";
9
9
  * `micromark-extension-gfm-autolink-literal` packages, and the implementation is inspired by the
10
10
  * third-party `remark-gfm` plugin.
11
11
  *
12
- * The reason why we don't use `remark-gfm` directly is because we only want to opt into the
13
- * specific GFM features the editor supports, rather than enabling all of them at once.
12
+ * The reason why we don't use `remark-gfm` directly is because we don't want to support all other
13
+ * GFM features (footnotes, tables, tagfilter, and tasklists).
14
14
  *
15
15
  * @param options Configuration options for the plugin.
16
16
  */
@@ -9,16 +9,16 @@ import { gfmAutolinkLiteral } from "micromark-extension-gfm-autolink-literal";
9
9
  * `micromark-extension-gfm-autolink-literal` packages, and the implementation is inspired by the
10
10
  * third-party `remark-gfm` plugin.
11
11
  *
12
- * The reason why we don't use `remark-gfm` directly is because we only want to opt into the
13
- * specific GFM features the editor supports, rather than enabling all of them at once.
12
+ * The reason why we don't use `remark-gfm` directly is because we don't want to support all other
13
+ * GFM features (footnotes, tables, tagfilter, and tasklists).
14
14
  *
15
15
  * @param options Configuration options for the plugin.
16
16
  */
17
17
  function remarkAutolinkLiteral() {
18
18
  const data = this.data();
19
- const micromarkExtensions = data.micromarkExtensions ?? (data.micromarkExtensions = []);
20
- const fromMarkdownExtensions = data.fromMarkdownExtensions ?? (data.fromMarkdownExtensions = []);
21
- const toMarkdownExtensions = data.toMarkdownExtensions ?? (data.toMarkdownExtensions = []);
19
+ const micromarkExtensions = data.micromarkExtensions || (data.micromarkExtensions = []);
20
+ const fromMarkdownExtensions = data.fromMarkdownExtensions || (data.fromMarkdownExtensions = []);
21
+ const toMarkdownExtensions = data.toMarkdownExtensions || (data.toMarkdownExtensions = []);
22
22
  micromarkExtensions.push(gfmAutolinkLiteral());
23
23
  fromMarkdownExtensions.push(gfmAutolinkLiteralFromMarkdown());
24
24
  toMarkdownExtensions.push(gfmAutolinkLiteralToMarkdown());
@@ -1 +1 @@
1
- {"version":3,"file":"remark-autolink-literal.js","names":[],"sources":["../../../../src/serializers/html/plugins/remark-autolink-literal.ts"],"sourcesContent":["import {\n gfmAutolinkLiteralFromMarkdown,\n gfmAutolinkLiteralToMarkdown,\n} from 'mdast-util-gfm-autolink-literal'\nimport { gfmAutolinkLiteral } from 'micromark-extension-gfm-autolink-literal'\n\nimport type { Processor } from 'unified'\n\n/**\n * A remark plugin to add support for the autolink literals extension extension from the GitHub\n * Flavored Markdown (GFM) specification.\n *\n * This is an standalone plugin which makes use of both the `mdast-util-gfm-autolink-literal` and\n * `micromark-extension-gfm-autolink-literal` packages, and the implementation is inspired by the\n * third-party `remark-gfm` plugin.\n *\n * The reason why we don't use `remark-gfm` directly is because we only want to opt into the\n * specific GFM features the editor supports, rather than enabling all of them at once.\n *\n * @param options Configuration options for the plugin.\n */\nfunction remarkAutolinkLiteral(this: Processor) {\n const data = this.data()\n\n const micromarkExtensions = data.micromarkExtensions ?? (data.micromarkExtensions = [])\n const fromMarkdownExtensions = data.fromMarkdownExtensions ?? (data.fromMarkdownExtensions = [])\n const toMarkdownExtensions = data.toMarkdownExtensions ?? (data.toMarkdownExtensions = [])\n\n micromarkExtensions.push(gfmAutolinkLiteral())\n fromMarkdownExtensions.push(gfmAutolinkLiteralFromMarkdown())\n toMarkdownExtensions.push(gfmAutolinkLiteralToMarkdown())\n}\n\nexport { remarkAutolinkLiteral }\n"],"mappings":";;;;;;;;;;;;;;;;AAqBA,SAAS,wBAAuC;CAC5C,MAAM,OAAO,KAAK,MAAM;CAExB,MAAM,sBAAsB,KAAK,wBAAwB,KAAK,sBAAsB,EAAE;CACtF,MAAM,yBAAyB,KAAK,2BAA2B,KAAK,yBAAyB,EAAE;CAC/F,MAAM,uBAAuB,KAAK,yBAAyB,KAAK,uBAAuB,EAAE;CAEzF,oBAAoB,KAAK,oBAAoB,CAAC;CAC9C,uBAAuB,KAAK,gCAAgC,CAAC;CAC7D,qBAAqB,KAAK,8BAA8B,CAAC"}
1
+ {"version":3,"file":"remark-autolink-literal.js","names":[],"sources":["../../../../src/serializers/html/plugins/remark-autolink-literal.ts"],"sourcesContent":["import {\n gfmAutolinkLiteralFromMarkdown,\n gfmAutolinkLiteralToMarkdown,\n} from 'mdast-util-gfm-autolink-literal'\nimport { gfmAutolinkLiteral } from 'micromark-extension-gfm-autolink-literal'\n\nimport type { Processor } from 'unified'\n\n/**\n * A remark plugin to add support for the autolink literals extension extension from the GitHub\n * Flavored Markdown (GFM) specification.\n *\n * This is an standalone plugin which makes use of both the `mdast-util-gfm-autolink-literal` and\n * `micromark-extension-gfm-autolink-literal` packages, and the implementation is inspired by the\n * third-party `remark-gfm` plugin.\n *\n * The reason why we don't use `remark-gfm` directly is because we don't want to support all other\n * GFM features (footnotes, tables, tagfilter, and tasklists).\n *\n * @param options Configuration options for the plugin.\n */\nfunction remarkAutolinkLiteral(this: Processor) {\n const data = this.data()\n\n const micromarkExtensions = data.micromarkExtensions || (data.micromarkExtensions = [])\n const fromMarkdownExtensions = data.fromMarkdownExtensions || (data.fromMarkdownExtensions = [])\n const toMarkdownExtensions = data.toMarkdownExtensions || (data.toMarkdownExtensions = [])\n\n micromarkExtensions.push(gfmAutolinkLiteral())\n fromMarkdownExtensions.push(gfmAutolinkLiteralFromMarkdown())\n toMarkdownExtensions.push(gfmAutolinkLiteralToMarkdown())\n}\n\nexport { remarkAutolinkLiteral }\n"],"mappings":";;;;;;;;;;;;;;;;AAqBA,SAAS,wBAAuC;CAC5C,MAAM,OAAO,KAAK,MAAM;CAExB,MAAM,sBAAsB,KAAK,wBAAwB,KAAK,sBAAsB,EAAE;CACtF,MAAM,yBAAyB,KAAK,2BAA2B,KAAK,yBAAyB,EAAE;CAC/F,MAAM,uBAAuB,KAAK,yBAAyB,KAAK,uBAAuB,EAAE;CAEzF,oBAAoB,KAAK,oBAAoB,CAAC;CAC9C,uBAAuB,KAAK,gCAAgC,CAAC;CAC7D,qBAAqB,KAAK,8BAA8B,CAAC"}
@@ -17,7 +17,7 @@ function remarkDisableConstructs(schema) {
17
17
  if (!schema.nodes.horizontalRule) disabledConstructs.push("thematicBreak");
18
18
  if (!schema.nodes.image) disabledConstructs.push("labelStartImage");
19
19
  if (!schema.marks.link) disabledConstructs.push("labelStartLink");
20
- (data.micromarkExtensions ?? (data.micromarkExtensions = [])).push({ disable: { null: disabledConstructs } });
20
+ (data.micromarkExtensions || (data.micromarkExtensions = [])).push({ disable: { null: disabledConstructs } });
21
21
  }
22
22
  //#endregion
23
23
  export { remarkDisableConstructs };
@@ -1 +1 @@
1
- {"version":3,"file":"remark-disable-constructs.js","names":[],"sources":["../../../../src/serializers/html/plugins/remark-disable-constructs.ts"],"sourcesContent":["import type { Schema } from '@tiptap/pm/model'\nimport type { Processor } from 'unified'\n\n/**\n * A remark plugin to disable multiple language constructs based on the availability of marks and/or\n * nodes in the given editor schema.\n *\n * @param schema The editor schema to be used for nodes and marks detection.\n */\nfunction remarkDisableConstructs(this: Processor, schema: Schema) {\n const data = this.data()\n\n const disabledConstructs: string[] = []\n\n if (!schema.nodes.blockquote) {\n disabledConstructs.push('blockQuote')\n }\n\n if (!schema.marks.bold || !schema.marks.italic) {\n disabledConstructs.push('attention')\n }\n\n if (!schema.nodes.bulletList || !schema.nodes.orderedList) {\n disabledConstructs.push('list')\n }\n\n if (!schema.marks.code) {\n disabledConstructs.push('codeText')\n }\n\n if (!schema.nodes.codeBlock) {\n disabledConstructs.push('codeFenced', 'codeIndented')\n }\n\n if (!schema.nodes.heading) {\n disabledConstructs.push('headingAtx')\n }\n\n if (!schema.nodes.horizontalRule) {\n disabledConstructs.push('thematicBreak')\n }\n\n if (!schema.nodes.image) {\n disabledConstructs.push('labelStartImage')\n }\n\n if (!schema.marks.link) {\n disabledConstructs.push('labelStartLink')\n }\n\n const micromarkExtensions = data.micromarkExtensions ?? (data.micromarkExtensions = [])\n\n // https://github.com/micromark/micromark#case-turn-off-constructs\n micromarkExtensions.push({\n disable: {\n null: disabledConstructs,\n },\n })\n}\n\nexport { remarkDisableConstructs }\n"],"mappings":";;;;;;;AASA,SAAS,wBAAyC,QAAgB;CAC9D,MAAM,OAAO,KAAK,MAAM;CAExB,MAAM,qBAA+B,EAAE;CAEvC,IAAI,CAAC,OAAO,MAAM,YACd,mBAAmB,KAAK,aAAa;CAGzC,IAAI,CAAC,OAAO,MAAM,QAAQ,CAAC,OAAO,MAAM,QACpC,mBAAmB,KAAK,YAAY;CAGxC,IAAI,CAAC,OAAO,MAAM,cAAc,CAAC,OAAO,MAAM,aAC1C,mBAAmB,KAAK,OAAO;CAGnC,IAAI,CAAC,OAAO,MAAM,MACd,mBAAmB,KAAK,WAAW;CAGvC,IAAI,CAAC,OAAO,MAAM,WACd,mBAAmB,KAAK,cAAc,eAAe;CAGzD,IAAI,CAAC,OAAO,MAAM,SACd,mBAAmB,KAAK,aAAa;CAGzC,IAAI,CAAC,OAAO,MAAM,gBACd,mBAAmB,KAAK,gBAAgB;CAG5C,IAAI,CAAC,OAAO,MAAM,OACd,mBAAmB,KAAK,kBAAkB;CAG9C,IAAI,CAAC,OAAO,MAAM,MACd,mBAAmB,KAAK,iBAAiB;CAM7C,CAH4B,KAAK,wBAAwB,KAAK,sBAAsB,EAAE,GAGlE,KAAK,EACrB,SAAS,EACL,MAAM,oBACT,EACJ,CAAC"}
1
+ {"version":3,"file":"remark-disable-constructs.js","names":[],"sources":["../../../../src/serializers/html/plugins/remark-disable-constructs.ts"],"sourcesContent":["import type { Schema } from '@tiptap/pm/model'\nimport type { Processor } from 'unified'\n\n/**\n * A remark plugin to disable multiple language constructs based on the availability of marks and/or\n * nodes in the given editor schema.\n *\n * @param schema The editor schema to be used for nodes and marks detection.\n */\nfunction remarkDisableConstructs(this: Processor, schema: Schema) {\n const data = this.data()\n\n const disabledConstructs: string[] = []\n\n if (!schema.nodes.blockquote) {\n disabledConstructs.push('blockQuote')\n }\n\n if (!schema.marks.bold || !schema.marks.italic) {\n disabledConstructs.push('attention')\n }\n\n if (!schema.nodes.bulletList || !schema.nodes.orderedList) {\n disabledConstructs.push('list')\n }\n\n if (!schema.marks.code) {\n disabledConstructs.push('codeText')\n }\n\n if (!schema.nodes.codeBlock) {\n disabledConstructs.push('codeFenced', 'codeIndented')\n }\n\n if (!schema.nodes.heading) {\n disabledConstructs.push('headingAtx')\n }\n\n if (!schema.nodes.horizontalRule) {\n disabledConstructs.push('thematicBreak')\n }\n\n if (!schema.nodes.image) {\n disabledConstructs.push('labelStartImage')\n }\n\n if (!schema.marks.link) {\n disabledConstructs.push('labelStartLink')\n }\n\n const micromarkExtensions = data.micromarkExtensions || (data.micromarkExtensions = [])\n\n // https://github.com/micromark/micromark#case-turn-off-constructs\n micromarkExtensions.push({\n disable: {\n null: disabledConstructs,\n },\n })\n}\n\nexport { remarkDisableConstructs }\n"],"mappings":";;;;;;;AASA,SAAS,wBAAyC,QAAgB;CAC9D,MAAM,OAAO,KAAK,MAAM;CAExB,MAAM,qBAA+B,EAAE;CAEvC,IAAI,CAAC,OAAO,MAAM,YACd,mBAAmB,KAAK,aAAa;CAGzC,IAAI,CAAC,OAAO,MAAM,QAAQ,CAAC,OAAO,MAAM,QACpC,mBAAmB,KAAK,YAAY;CAGxC,IAAI,CAAC,OAAO,MAAM,cAAc,CAAC,OAAO,MAAM,aAC1C,mBAAmB,KAAK,OAAO;CAGnC,IAAI,CAAC,OAAO,MAAM,MACd,mBAAmB,KAAK,WAAW;CAGvC,IAAI,CAAC,OAAO,MAAM,WACd,mBAAmB,KAAK,cAAc,eAAe;CAGzD,IAAI,CAAC,OAAO,MAAM,SACd,mBAAmB,KAAK,aAAa;CAGzC,IAAI,CAAC,OAAO,MAAM,gBACd,mBAAmB,KAAK,gBAAgB;CAG5C,IAAI,CAAC,OAAO,MAAM,OACd,mBAAmB,KAAK,kBAAkB;CAG9C,IAAI,CAAC,OAAO,MAAM,MACd,mBAAmB,KAAK,iBAAiB;CAM7C,CAH4B,KAAK,wBAAwB,KAAK,sBAAsB,EAAE,GAGlE,KAAK,EACrB,SAAS,EACL,MAAM,oBACT,EACJ,CAAC"}
@@ -10,8 +10,8 @@ import { Options } from "micromark-extension-gfm-strikethrough";
10
10
  * `micromark-extension-gfm-strikethrough` packages, and the implementation is inspired by the
11
11
  * third-party `remark-gfm` plugin.
12
12
  *
13
- * The reason why we don't use `remark-gfm` directly is because we only want to opt into the
14
- * specific GFM features the editor supports, rather than enabling all of them at once.
13
+ * The reason why we don't use `remark-gfm` directly is because we don't want to support all other
14
+ * GFM features (footnotes, tables, tagfilter, and tasklists).
15
15
  *
16
16
  * @param options Configuration options for the plugin.
17
17
  */
@@ -9,16 +9,16 @@ import { gfmStrikethrough } from "micromark-extension-gfm-strikethrough";
9
9
  * `micromark-extension-gfm-strikethrough` packages, and the implementation is inspired by the
10
10
  * third-party `remark-gfm` plugin.
11
11
  *
12
- * The reason why we don't use `remark-gfm` directly is because we only want to opt into the
13
- * specific GFM features the editor supports, rather than enabling all of them at once.
12
+ * The reason why we don't use `remark-gfm` directly is because we don't want to support all other
13
+ * GFM features (footnotes, tables, tagfilter, and tasklists).
14
14
  *
15
15
  * @param options Configuration options for the plugin.
16
16
  */
17
17
  function remarkStrikethrough(options = {}) {
18
18
  const data = this.data();
19
- const micromarkExtensions = data.micromarkExtensions ?? (data.micromarkExtensions = []);
20
- const fromMarkdownExtensions = data.fromMarkdownExtensions ?? (data.fromMarkdownExtensions = []);
21
- const toMarkdownExtensions = data.toMarkdownExtensions ?? (data.toMarkdownExtensions = []);
19
+ const micromarkExtensions = data.micromarkExtensions || (data.micromarkExtensions = []);
20
+ const fromMarkdownExtensions = data.fromMarkdownExtensions || (data.fromMarkdownExtensions = []);
21
+ const toMarkdownExtensions = data.toMarkdownExtensions || (data.toMarkdownExtensions = []);
22
22
  micromarkExtensions.push(gfmStrikethrough(options));
23
23
  fromMarkdownExtensions.push(gfmStrikethroughFromMarkdown());
24
24
  toMarkdownExtensions.push(gfmStrikethroughToMarkdown());
@@ -1 +1 @@
1
- {"version":3,"file":"remark-strikethrough.js","names":[],"sources":["../../../../src/serializers/html/plugins/remark-strikethrough.ts"],"sourcesContent":["import {\n gfmStrikethroughFromMarkdown,\n gfmStrikethroughToMarkdown,\n} from 'mdast-util-gfm-strikethrough'\nimport { gfmStrikethrough } from 'micromark-extension-gfm-strikethrough'\n\nimport type { Options } from 'micromark-extension-gfm-strikethrough'\nimport type { Processor } from 'unified'\n\n/**\n * A remark plugin to add support for the strikethrough extension from the GitHub Flavored Markdown\n * (GFM) specification.\n *\n * This is an standalone plugin which makes use of both the `mdast-util-gfm-strikethrough` and\n * `micromark-extension-gfm-strikethrough` packages, and the implementation is inspired by the\n * third-party `remark-gfm` plugin.\n *\n * The reason why we don't use `remark-gfm` directly is because we only want to opt into the\n * specific GFM features the editor supports, rather than enabling all of them at once.\n *\n * @param options Configuration options for the plugin.\n */\nfunction remarkStrikethrough(this: Processor, options: Options = {}) {\n const data = this.data()\n\n const micromarkExtensions = data.micromarkExtensions ?? (data.micromarkExtensions = [])\n const fromMarkdownExtensions = data.fromMarkdownExtensions ?? (data.fromMarkdownExtensions = [])\n const toMarkdownExtensions = data.toMarkdownExtensions ?? (data.toMarkdownExtensions = [])\n\n micromarkExtensions.push(gfmStrikethrough(options))\n fromMarkdownExtensions.push(gfmStrikethroughFromMarkdown())\n toMarkdownExtensions.push(gfmStrikethroughToMarkdown())\n}\n\nexport { remarkStrikethrough }\n"],"mappings":";;;;;;;;;;;;;;;;AAsBA,SAAS,oBAAqC,UAAmB,EAAE,EAAE;CACjE,MAAM,OAAO,KAAK,MAAM;CAExB,MAAM,sBAAsB,KAAK,wBAAwB,KAAK,sBAAsB,EAAE;CACtF,MAAM,yBAAyB,KAAK,2BAA2B,KAAK,yBAAyB,EAAE;CAC/F,MAAM,uBAAuB,KAAK,yBAAyB,KAAK,uBAAuB,EAAE;CAEzF,oBAAoB,KAAK,iBAAiB,QAAQ,CAAC;CACnD,uBAAuB,KAAK,8BAA8B,CAAC;CAC3D,qBAAqB,KAAK,4BAA4B,CAAC"}
1
+ {"version":3,"file":"remark-strikethrough.js","names":[],"sources":["../../../../src/serializers/html/plugins/remark-strikethrough.ts"],"sourcesContent":["import {\n gfmStrikethroughFromMarkdown,\n gfmStrikethroughToMarkdown,\n} from 'mdast-util-gfm-strikethrough'\nimport { gfmStrikethrough } from 'micromark-extension-gfm-strikethrough'\n\nimport type { Options } from 'micromark-extension-gfm-strikethrough'\nimport type { Processor } from 'unified'\n\n/**\n * A remark plugin to add support for the strikethrough extension from the GitHub Flavored Markdown\n * (GFM) specification.\n *\n * This is an standalone plugin which makes use of both the `mdast-util-gfm-strikethrough` and\n * `micromark-extension-gfm-strikethrough` packages, and the implementation is inspired by the\n * third-party `remark-gfm` plugin.\n *\n * The reason why we don't use `remark-gfm` directly is because we don't want to support all other\n * GFM features (footnotes, tables, tagfilter, and tasklists).\n *\n * @param options Configuration options for the plugin.\n */\nfunction remarkStrikethrough(this: Processor, options: Options = {}) {\n const data = this.data()\n\n const micromarkExtensions = data.micromarkExtensions || (data.micromarkExtensions = [])\n const fromMarkdownExtensions = data.fromMarkdownExtensions || (data.fromMarkdownExtensions = [])\n const toMarkdownExtensions = data.toMarkdownExtensions || (data.toMarkdownExtensions = [])\n\n micromarkExtensions.push(gfmStrikethrough(options))\n fromMarkdownExtensions.push(gfmStrikethroughFromMarkdown())\n toMarkdownExtensions.push(gfmStrikethroughToMarkdown())\n}\n\nexport { remarkStrikethrough }\n"],"mappings":";;;;;;;;;;;;;;;;AAsBA,SAAS,oBAAqC,UAAmB,EAAE,EAAE;CACjE,MAAM,OAAO,KAAK,MAAM;CAExB,MAAM,sBAAsB,KAAK,wBAAwB,KAAK,sBAAsB,EAAE;CACtF,MAAM,yBAAyB,KAAK,2BAA2B,KAAK,yBAAyB,EAAE;CAC/F,MAAM,uBAAuB,KAAK,yBAAyB,KAAK,uBAAuB,EAAE;CAEzF,oBAAoB,KAAK,iBAAiB,QAAQ,CAAC;CACnD,uBAAuB,KAAK,8BAA8B,CAAC;CAC3D,qBAAqB,KAAK,4BAA4B,CAAC"}
@@ -18,9 +18,9 @@ type MarkdownSerializerReturnType = {
18
18
  * The bullet list marker for both standard and task list items.
19
19
  */
20
20
  /**
21
- * Create an HTML to Markdown serializer with the unified ecosystem for both a rich-text editor, and
21
+ * Create an HTML to Markdown serializer with the Turndown library for both a rich-text editor, and
22
22
  * a plain-text editor. The editor schema is used to detect which nodes and marks are available in
23
- * the editor, and only parses the input with the minimal required plugins.
23
+ * the editor, and only parses the input with the minimal required rules.
24
24
  *
25
25
  * **Note:** Unlike the HTML serializer, built-in rules that are not supported by the schema are not
26
26
  * disabled because if the schema does not support certain nodes/marks, the parsing rules don't have
@@ -1 +1 @@
1
- {"version":3,"file":"markdown.d.ts","names":[],"sources":["../../../src/serializers/markdown/markdown.ts"],"mappings":";;;;;AAc8C;KAUzC,4BAAA;;;;AAqBmB;;;;EAbpB,SAAA,GAAY,IAAA;AAAA;;;;;;;;;;;;;;;;;iBAgJP,wBAAA,CAAyB,MAAA,EAAQ,MAAA,GAAS,4BAAA;;;;;;;;iBAuE1C,6BAAA,CAA8B,MAAA,EAAQ,MAAA,GAAM,4BAAA"}
1
+ {"version":3,"file":"markdown.d.ts","names":[],"sources":["../../../src/serializers/markdown/markdown.ts"],"mappings":";;;;;AAa8C;KAKzC,4BAAA;;;;AAqBmB;;;;EAbpB,SAAA,GAAY,IAAA;AAAA;;;;;;;;;;;;;;;;;iBAuEP,wBAAA,CAAyB,MAAA,EAAQ,MAAA,GAAS,4BAAA;;;;;;;;iBA6G1C,6BAAA,CAA8B,MAAA,EAAQ,MAAA,GAAM,4BAAA"}
@@ -1,83 +1,47 @@
1
1
  import { computeSchemaId, isPlainTextDocument } from "../../helpers/schema.js";
2
+ import { getSuggestionNodes } from "../../helpers/serializer.js";
2
3
  import { REGEX_PUNCTUATION } from "../../constants/regular-expressions.js";
3
- import { rehypeImage } from "./plugins/rehype-image.js";
4
- import { rehypeSuggestions } from "./plugins/rehype-suggestions.js";
5
- import { rehypeTaskList } from "./plugins/rehype-task-list.js";
6
- import { remarkStrikethrough } from "./plugins/remark-strikethrough.js";
7
- import { remarkTaskList } from "./plugins/remark-task-list.js";
8
- import { unified } from "unified";
9
- import rehypeParse from "rehype-parse";
10
- import rehypeRemark from "rehype-remark";
11
- import remarkStringify from "remark-stringify";
4
+ import { image } from "./plugins/image.js";
5
+ import { listItem } from "./plugins/list-item.js";
6
+ import { paragraph } from "./plugins/paragraph.js";
7
+ import { strikethrough } from "./plugins/strikethrough.js";
8
+ import { suggestion } from "./plugins/suggestion.js";
9
+ import { taskItem } from "./plugins/task-item.js";
10
+ import Turndown from "turndown";
12
11
  /**
13
- * Regular expression matching a backslash followed by any punctuation mark. Used to escape the
14
- * backslash itself so it isn't interpreted as an escape sequence for the subsequent character.
15
- */
16
- const REGEX_ESCAPED_PUNCTUATION = new RegExp(`(\\\\${REGEX_PUNCTUATION.source})`, "g");
17
- /**
18
- * Regular expression matching text that looks like an ordered list item at the start of a line
19
- * (e.g. `1. Foo`). Used to escape the period so the text isn't interpreted as a list.
20
- */
21
- const REGEX_ORDERED_LIST_PREFIX = /^(\d+)\.(\s.+|$)/;
22
- /**
23
- * Builds the rehype-remark options for the given editor schema. Element handlers are added
24
- * conditionally based on which marks and nodes are available, so unsupported elements are
25
- * preserved as their text content rather than being dropped or causing errors.
12
+ * Sensible default options to initialize the Turndown with.
26
13
  *
27
- * @see https://github.com/rehypejs/rehype-remark#api
28
- *
29
- * @param schema The editor schema to be used for nodes and marks detection.
14
+ * @see https://github.com/mixmark-io/turndown#options
30
15
  */
31
- function getRehypeRemarkOptions(schema) {
32
- const handlers = {};
33
- if (!schema.marks.strike) {
34
- handlers.s = (state, node) => state.all(node);
35
- handlers.del = (state, node) => state.all(node);
36
- handlers.strike = (state, node) => state.all(node);
16
+ const INITIAL_TURNDOWN_OPTIONS = {
17
+ headingStyle: "atx",
18
+ hr: "---",
19
+ bulletListMarker: "-",
20
+ codeBlockStyle: "fenced",
21
+ fence: "```",
22
+ emDelimiter: "*",
23
+ strongDelimiter: "**",
24
+ linkStyle: "inlined",
25
+ /**
26
+ * Special rule to handle blank elements (overrides EVERY rule).
27
+ *
28
+ * @see https://github.com/mixmark-io/turndown#special-rules
29
+ */
30
+ blankReplacement(_, node) {
31
+ const parentNode = node.parentNode;
32
+ if (node.nodeName === "UL" || parentNode?.nodeName === "UL" && node.nodeName === "LI") return `- \n`;
33
+ if (node.nodeName === "OL" || parentNode?.nodeName === "OL" && node.nodeName === "LI") {
34
+ const start = node.nodeName === "LI" ? parentNode.getAttribute("start") : node.getAttribute("start");
35
+ const index = Array.prototype.indexOf.call(parentNode.children, node);
36
+ return `${start ? Number(start) + index : index + 1}. \n`;
37
+ }
38
+ return node.isBlock ? "\n\n" : "";
37
39
  }
38
- return { handlers };
39
- }
40
+ };
40
41
  /**
41
- * Builds the remark-stringify options for the given editor mode. For plain-text editors, Markdown
42
- * escaping is disabled (so user-authored content is preserved as-is) and consecutive paragraphs
43
- * are separated by a single newline instead of a blank line.
44
- *
45
- * @see https://github.com/remarkjs/remark/tree/main/packages/remark-stringify#options
46
- *
47
- * @param isPlainText Whether the editor is a plain-text editor.
48
- */
49
- function getRemarkStringifyOptions(isPlainText) {
50
- return {
51
- bullet: "-",
52
- emphasis: "*",
53
- strong: "*",
54
- rule: "-",
55
- ruleRepetition: 3,
56
- fences: true,
57
- fence: "`",
58
- listItemIndent: "one",
59
- resourceLink: true,
60
- handlers: {
61
- break() {
62
- return "\n";
63
- },
64
- text(node) {
65
- if (isPlainText) return node.value;
66
- return node.value.replace(REGEX_ESCAPED_PUNCTUATION, "\\$1").replace(REGEX_ORDERED_LIST_PREFIX, "$1\\.$2");
67
- }
68
- },
69
- join: [(left, right, parent) => {
70
- if (parent.type === "list") return 0;
71
- if (parent.type === "listItem" && !(left.type === "paragraph" && right.type === "paragraph")) return 0;
72
- }, ...isPlainText ? [((left, right) => {
73
- if (left.type === "paragraph" && right.type === "paragraph") return 0;
74
- })] : []]
75
- };
76
- }
77
- /**
78
- * Create an HTML to Markdown serializer with the unified ecosystem for both a rich-text editor, and
42
+ * Create an HTML to Markdown serializer with the Turndown library for both a rich-text editor, and
79
43
  * a plain-text editor. The editor schema is used to detect which nodes and marks are available in
80
- * the editor, and only parses the input with the minimal required plugins.
44
+ * the editor, and only parses the input with the minimal required rules.
81
45
  *
82
46
  * **Note:** Unlike the HTML serializer, built-in rules that are not supported by the schema are not
83
47
  * disabled because if the schema does not support certain nodes/marks, the parsing rules don't have
@@ -88,19 +52,25 @@ function getRemarkStringifyOptions(isPlainText) {
88
52
  * @returns A normalized object for the Markdown serializer.
89
53
  */
90
54
  function createMarkdownSerializer(schema) {
91
- const isPlainText = isPlainTextDocument(schema);
92
- const unifiedProcessor = unified().use(rehypeParse, { fragment: true });
93
- if (schema.nodes.image) unifiedProcessor.use(rehypeImage);
94
- if (schema.nodes.taskList && schema.nodes.taskItem) unifiedProcessor.use(rehypeTaskList);
95
- unifiedProcessor.use(rehypeSuggestions, schema);
96
- unifiedProcessor.use(rehypeRemark, getRehypeRemarkOptions(schema));
97
- if (schema.marks.strike) unifiedProcessor.use(remarkStrikethrough);
98
- if (schema.nodes.taskList && schema.nodes.taskItem) unifiedProcessor.use(remarkTaskList);
99
- unifiedProcessor.use(remarkStringify, getRemarkStringifyOptions(isPlainText));
55
+ const turndown = new Turndown(INITIAL_TURNDOWN_OPTIONS);
56
+ if (isPlainTextDocument(schema)) turndown.escape = (str) => str;
57
+ else turndown.escape = (str) => {
58
+ return str.replace(new RegExp(`(\\\\${REGEX_PUNCTUATION.source})`, "g"), "\\$1").replace(/^(\d+)\.(\s.+|$)/, "$1\\.$2");
59
+ };
60
+ turndown.use(paragraph(schema.nodes.paragraph, isPlainTextDocument(schema)));
61
+ if (schema.nodes.image) turndown.use(image(schema.nodes.image));
62
+ if ((schema.nodes.bulletList || schema.nodes.orderedList) && schema.nodes.listItem) turndown.use(listItem(schema.nodes.listItem));
63
+ if (schema.marks.strike) turndown.use(strikethrough(schema.marks.strike));
64
+ if (schema.nodes.taskList && schema.nodes.taskItem) turndown.use(taskItem(schema.nodes.taskItem));
65
+ getSuggestionNodes(schema).forEach((suggestionNode) => {
66
+ turndown.use(suggestion(suggestionNode));
67
+ });
100
68
  return { serialize(html) {
101
- const input = isPlainText ? html.replace(/ {2,}/g, (match) => match.replace(/ /g, "\xA0")) : html;
102
- const result = unifiedProcessor.processSync(input).toString().trimEnd();
103
- return isPlainText ? result.replace(/\u00a0/g, " ") : result;
69
+ let markdownResult = html;
70
+ if (isPlainTextDocument(schema)) markdownResult = markdownResult.replace(/ {2,}/g, (m) => m.replace(/ /g, "\xA0"));
71
+ markdownResult = turndown.turndown(markdownResult);
72
+ if (isPlainTextDocument(schema)) markdownResult = markdownResult.replace(/\u00a0/g, " ");
73
+ return markdownResult.replace(/ +$/gm, "");
104
74
  } };
105
75
  }
106
76
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"markdown.js","names":[],"sources":["../../../src/serializers/markdown/markdown.ts"],"sourcesContent":["import rehypeParse from 'rehype-parse'\nimport rehypeRemark from 'rehype-remark'\nimport remarkStringify from 'remark-stringify'\nimport { unified } from 'unified'\n\nimport { REGEX_PUNCTUATION } from '../../constants/regular-expressions'\nimport { computeSchemaId, isPlainTextDocument } from '../../helpers/schema'\n\nimport { rehypeImage } from './plugins/rehype-image'\nimport { rehypeSuggestions } from './plugins/rehype-suggestions'\nimport { rehypeTaskList } from './plugins/rehype-task-list'\nimport { remarkStrikethrough } from './plugins/remark-strikethrough'\nimport { remarkTaskList } from './plugins/remark-task-list'\n\nimport type { Schema } from '@tiptap/pm/model'\nimport type { Options as RemarkStringifyOptions } from 'remark-stringify'\n\ntype RehypeRemarkOptions = NonNullable<Parameters<typeof rehypeRemark>[1]>\ntype Handle = NonNullable<NonNullable<RehypeRemarkOptions['handlers']>[string]>\ntype Join = NonNullable<RemarkStringifyOptions['join']>[number]\n\n/**\n * The return type for the `createMarkdownSerializer` function.\n */\ntype MarkdownSerializerReturnType = {\n /**\n * Serializes an input HTML string to an output Markdown string.\n *\n * @param html The HTML string to serialize.\n *\n * @returns The serialized Markdown.\n */\n serialize: (html: string) => string\n}\n\n/**\n * The type for the object that holds multiple Markdown serializer instances.\n */\ntype MarkdownSerializerInstanceById = {\n [id: string]: MarkdownSerializerReturnType\n}\n\n/**\n * The bullet list marker for both standard and task list items.\n */\nconst BULLET_LIST_MARKER = '-'\n\n/**\n * Regular expression matching a backslash followed by any punctuation mark. Used to escape the\n * backslash itself so it isn't interpreted as an escape sequence for the subsequent character.\n */\nconst REGEX_ESCAPED_PUNCTUATION = new RegExp(`(\\\\\\\\${REGEX_PUNCTUATION.source})`, 'g')\n\n/**\n * Regular expression matching text that looks like an ordered list item at the start of a line\n * (e.g. `1. Foo`). Used to escape the period so the text isn't interpreted as a list.\n */\nconst REGEX_ORDERED_LIST_PREFIX = /^(\\d+)\\.(\\s.+|$)/\n\n/**\n * Builds the rehype-remark options for the given editor schema. Element handlers are added\n * conditionally based on which marks and nodes are available, so unsupported elements are\n * preserved as their text content rather than being dropped or causing errors.\n *\n * @see https://github.com/rehypejs/rehype-remark#api\n *\n * @param schema The editor schema to be used for nodes and marks detection.\n */\nfunction getRehypeRemarkOptions(schema: Schema): RehypeRemarkOptions {\n const handlers: Record<string, Handle> = {}\n\n // When the schema does not support strikethrough, unwrap the strikethrough tags into their\n // children so the inner text is preserved without a `delete` mdast node being created\n // (`hast-util-to-mdast` maps all three tags to the same node type)\n if (!schema.marks.strike) {\n handlers.s = (state, node) => state.all(node)\n handlers.del = (state, node) => state.all(node)\n handlers.strike = (state, node) => state.all(node)\n }\n\n return { handlers }\n}\n\n/**\n * Builds the remark-stringify options for the given editor mode. For plain-text editors, Markdown\n * escaping is disabled (so user-authored content is preserved as-is) and consecutive paragraphs\n * are separated by a single newline instead of a blank line.\n *\n * @see https://github.com/remarkjs/remark/tree/main/packages/remark-stringify#options\n *\n * @param isPlainText Whether the editor is a plain-text editor.\n */\nfunction getRemarkStringifyOptions(isPlainText: boolean): RemarkStringifyOptions {\n return {\n bullet: BULLET_LIST_MARKER,\n emphasis: '*',\n strong: '*',\n rule: '-',\n ruleRepetition: 3,\n fences: true,\n fence: '`',\n listItemIndent: 'one',\n resourceLink: true,\n handlers: {\n // Output a plain newline for hard breaks instead of the CommonMark backslash syntax\n // (i.e. `\\\\\\n`), since `remark-breaks` treats plain newlines as hard breaks\n break() {\n return '\\n'\n },\n // Apply the minimal custom escape rules required for rich-text content. Plain-text\n // mode returns text values as-is, so user-authored content is preserved without any\n // Markdown escaping.\n text(node) {\n if (isPlainText) {\n return node.value\n }\n\n return (\n node.value\n // Escape backslashes that precede any punctuation mark, to prevent the\n // backslash itself from being interpreted as an escape sequence for the\n // subsequent character. It's important to apply this rule first to avoid\n // double escaping.\n .replace(REGEX_ESCAPED_PUNCTUATION, '\\\\$1')\n // Escape text content that looks like an ordered list item at the start of\n // a line, to prevent it from being interpreted as a list whenserialized\n .replace(REGEX_ORDERED_LIST_PREFIX, '$1\\\\.$2')\n )\n },\n },\n join: [\n // Force tight list rendering regardless of the `spread` attribute on lists or list\n // items, so that simple list items are not separated by blank lines (matching the\n // previous behavior and the common user expectation). Consecutive paragraphs inside\n // a list item keep the default blank-line separation, so multi-paragraph items\n // serialize as valid loose-list content and survive round-tripping.\n (left, right, parent) => {\n if (parent.type === 'list') {\n return 0\n }\n\n if (\n parent.type === 'listItem' &&\n !(left.type === 'paragraph' && right.type === 'paragraph')\n ) {\n return 0\n }\n },\n // Separate consecutive paragraphs with a single newline instead of a blank line\n // (plain-text only)\n ...(isPlainText\n ? [\n ((left, right) => {\n if (left.type === 'paragraph' && right.type === 'paragraph') {\n return 0\n }\n }) as Join,\n ]\n : []),\n ],\n }\n}\n\n/**\n * Create an HTML to Markdown serializer with the unified ecosystem for both a rich-text editor, and\n * a plain-text editor. The editor schema is used to detect which nodes and marks are available in\n * the editor, and only parses the input with the minimal required plugins.\n *\n * **Note:** Unlike the HTML serializer, built-in rules that are not supported by the schema are not\n * disabled because if the schema does not support certain nodes/marks, the parsing rules don't have\n * valid HTML elements to match in the editor HTML output.\n *\n * @param schema The editor schema to be used for nodes and marks detection.\n *\n * @returns A normalized object for the Markdown serializer.\n */\nfunction createMarkdownSerializer(schema: Schema): MarkdownSerializerReturnType {\n const isPlainText = isPlainTextDocument(schema)\n\n // Initialize a unified processor with a rehype plugin for parsing HTML fragments\n const unifiedProcessor = unified().use(rehypeParse, { fragment: true })\n\n // Configure the unified processor with a custom plugin to handle edge cases for images\n // (must run before `rehypeRemark` since it transforms hast nodes)\n if (schema.nodes.image) {\n unifiedProcessor.use(rehypeImage)\n }\n\n // Configure the unified processor with a custom plugin to add support for Tiptap task lists\n // (must run before `rehypeRemark` since it transforms hast nodes)\n if (schema.nodes.taskList && schema.nodes.taskItem) {\n unifiedProcessor.use(rehypeTaskList)\n }\n\n // Configure the unified processor with a custom plugin to add support for suggestions nodes\n // (must run before `rehypeRemark` since it transforms hast nodes)\n unifiedProcessor.use(rehypeSuggestions, schema)\n\n // Configure the unified processor with an official plugin to convert HTML into Markdown to\n // support remark (a tool that transforms Markdown with plugins)\n unifiedProcessor.use(rehypeRemark, getRehypeRemarkOptions(schema))\n\n // Configure the unified processor with a custom plugin to add support for the strikethrough\n // extension from the GitHub Flavored Markdown (GFM) specification\n if (schema.marks.strike) {\n unifiedProcessor.use(remarkStrikethrough)\n }\n\n // Configure the unified processor with a custom plugin to add support for the task list\n // extension from the GitHub Flavored Markdown (GFM) specification\n if (schema.nodes.taskList && schema.nodes.taskItem) {\n unifiedProcessor.use(remarkTaskList)\n }\n\n // Configure the unified processor with an official plugin that defines how to take a syntax\n // tree as input and turn it into serialized Markdown\n unifiedProcessor.use(remarkStringify, getRemarkStringifyOptions(isPlainText))\n\n return {\n serialize(html: string) {\n // For plain-text editors, preserve runs of 2+ spaces by replacing them with\n // non-breaking spaces before parsing (otherwise `rehype-parse` collapses them), and\n // restoring them after serialization. This is required to preserve syntax like\n // nested lists where leading indentation matters.\n const input = isPlainText\n ? html.replace(/ {2,}/g, (match) => match.replace(/ /g, '\\u00a0'))\n : html\n\n const result = unifiedProcessor.processSync(input).toString().trimEnd()\n\n return isPlainText ? result.replace(/\\u00a0/g, ' ') : result\n },\n }\n}\n\n/**\n * Object that holds multiple Markdown serializer instances based on a given ID.\n */\nconst markdownSerializerInstanceById: MarkdownSerializerInstanceById = {}\n\n/**\n * Returns a singleton instance of a Markdown serializer based on the provided editor schema.\n *\n * @param schema The editor schema connected to the Markdown serializer instance.\n *\n * @returns The Markdown serializer instance for the given editor schema.\n */\nfunction getMarkdownSerializerInstance(schema: Schema) {\n const id = computeSchemaId(schema)\n\n if (!markdownSerializerInstanceById[id]) {\n markdownSerializerInstanceById[id] = createMarkdownSerializer(schema)\n }\n\n return markdownSerializerInstanceById[id]\n}\n\nexport { BULLET_LIST_MARKER, createMarkdownSerializer, getMarkdownSerializerInstance }\n\nexport type { MarkdownSerializerReturnType }\n"],"mappings":";;;;;;;;;;;;;;;AAmDA,MAAM,4BAA4B,IAAI,OAAO,QAAQ,kBAAkB,OAAO,IAAI,IAAI;;;;;AAMtF,MAAM,4BAA4B;;;;;;;;;;AAWlC,SAAS,uBAAuB,QAAqC;CACjE,MAAM,WAAmC,EAAE;CAK3C,IAAI,CAAC,OAAO,MAAM,QAAQ;EACtB,SAAS,KAAK,OAAO,SAAS,MAAM,IAAI,KAAK;EAC7C,SAAS,OAAO,OAAO,SAAS,MAAM,IAAI,KAAK;EAC/C,SAAS,UAAU,OAAO,SAAS,MAAM,IAAI,KAAK;;CAGtD,OAAO,EAAE,UAAU;;;;;;;;;;;AAYvB,SAAS,0BAA0B,aAA8C;CAC7E,OAAO;EACH,QAAA;EACA,UAAU;EACV,QAAQ;EACR,MAAM;EACN,gBAAgB;EAChB,QAAQ;EACR,OAAO;EACP,gBAAgB;EAChB,cAAc;EACd,UAAU;GAGN,QAAQ;IACJ,OAAO;;GAKX,KAAK,MAAM;IACP,IAAI,aACA,OAAO,KAAK;IAGhB,OACI,KAAK,MAKA,QAAQ,2BAA2B,OAAO,CAG1C,QAAQ,2BAA2B,UAAU;;GAG7D;EACD,MAAM,EAMD,MAAM,OAAO,WAAW;GACrB,IAAI,OAAO,SAAS,QAChB,OAAO;GAGX,IACI,OAAO,SAAS,cAChB,EAAE,KAAK,SAAS,eAAe,MAAM,SAAS,cAE9C,OAAO;KAKf,GAAI,cACE,GACM,MAAM,UAAU;GACd,IAAI,KAAK,SAAS,eAAe,MAAM,SAAS,aAC5C,OAAO;KAGlB,GACD,EAAE,CACX;EACJ;;;;;;;;;;;;;;;AAgBL,SAAS,yBAAyB,QAA8C;CAC5E,MAAM,cAAc,oBAAoB,OAAO;CAG/C,MAAM,mBAAmB,SAAS,CAAC,IAAI,aAAa,EAAE,UAAU,MAAM,CAAC;CAIvE,IAAI,OAAO,MAAM,OACb,iBAAiB,IAAI,YAAY;CAKrC,IAAI,OAAO,MAAM,YAAY,OAAO,MAAM,UACtC,iBAAiB,IAAI,eAAe;CAKxC,iBAAiB,IAAI,mBAAmB,OAAO;CAI/C,iBAAiB,IAAI,cAAc,uBAAuB,OAAO,CAAC;CAIlE,IAAI,OAAO,MAAM,QACb,iBAAiB,IAAI,oBAAoB;CAK7C,IAAI,OAAO,MAAM,YAAY,OAAO,MAAM,UACtC,iBAAiB,IAAI,eAAe;CAKxC,iBAAiB,IAAI,iBAAiB,0BAA0B,YAAY,CAAC;CAE7E,OAAO,EACH,UAAU,MAAc;EAKpB,MAAM,QAAQ,cACR,KAAK,QAAQ,WAAW,UAAU,MAAM,QAAQ,MAAM,OAAS,CAAC,GAChE;EAEN,MAAM,SAAS,iBAAiB,YAAY,MAAM,CAAC,UAAU,CAAC,SAAS;EAEvE,OAAO,cAAc,OAAO,QAAQ,WAAW,IAAI,GAAG;IAE7D;;;;;AAML,MAAM,iCAAiE,EAAE;;;;;;;;AASzE,SAAS,8BAA8B,QAAgB;CACnD,MAAM,KAAK,gBAAgB,OAAO;CAElC,IAAI,CAAC,+BAA+B,KAChC,+BAA+B,MAAM,yBAAyB,OAAO;CAGzE,OAAO,+BAA+B"}
1
+ {"version":3,"file":"markdown.js","names":[],"sources":["../../../src/serializers/markdown/markdown.ts"],"sourcesContent":["import Turndown from 'turndown'\n\nimport { REGEX_PUNCTUATION } from '../../constants/regular-expressions'\nimport { computeSchemaId, isPlainTextDocument } from '../../helpers/schema'\nimport { getSuggestionNodes } from '../../helpers/serializer'\n\nimport { image } from './plugins/image'\nimport { listItem } from './plugins/list-item'\nimport { paragraph } from './plugins/paragraph'\nimport { strikethrough } from './plugins/strikethrough'\nimport { suggestion } from './plugins/suggestion'\nimport { taskItem } from './plugins/task-item'\n\nimport type { Schema } from '@tiptap/pm/model'\n\n/**\n * The return type for the `createMarkdownSerializer` function.\n */\ntype MarkdownSerializerReturnType = {\n /**\n * Serializes an input HTML string to an output Markdown string.\n *\n * @param html The HTML string to serialize.\n *\n * @returns The serialized Markdown.\n */\n serialize: (html: string) => string\n}\n\n/**\n * The type for the object that holds multiple Markdown serializer instances.\n */\ntype MarkdownSerializerInstanceById = {\n [id: string]: MarkdownSerializerReturnType\n}\n\n/**\n * The bullet list marker for both standard and task list items.\n */\nconst BULLET_LIST_MARKER = '-'\n\n/**\n * Sensible default options to initialize the Turndown with.\n *\n * @see https://github.com/mixmark-io/turndown#options\n */\nconst INITIAL_TURNDOWN_OPTIONS: Turndown.Options = {\n headingStyle: 'atx',\n hr: '---',\n bulletListMarker: BULLET_LIST_MARKER,\n codeBlockStyle: 'fenced',\n fence: '```',\n emDelimiter: '*',\n strongDelimiter: '**',\n linkStyle: 'inlined',\n /**\n * Special rule to handle blank elements (overrides EVERY rule).\n *\n * @see https://github.com/mixmark-io/turndown#special-rules\n */\n blankReplacement(_, node) {\n const parentNode = node.parentNode as HTMLElement\n\n // Return the list marker for empty bullet list items\n if (node.nodeName === 'UL' || (parentNode?.nodeName === 'UL' && node.nodeName === 'LI')) {\n return `${BULLET_LIST_MARKER} \\n`\n }\n\n // Return the list marker for empty ordered list items\n if (node.nodeName === 'OL' || (parentNode?.nodeName === 'OL' && node.nodeName === 'LI')) {\n const start =\n node.nodeName === 'LI'\n ? parentNode.getAttribute('start')\n : node.getAttribute('start')\n const index = Array.prototype.indexOf.call(parentNode.children, node)\n\n return `${start ? Number(start) + index : index + 1}. \\n`\n }\n\n // @ts-ignore: The `Turndown.Node` type does not include `isBlock`\n return node.isBlock ? '\\n\\n' : ''\n },\n}\n\n/**\n * Create an HTML to Markdown serializer with the Turndown library for both a rich-text editor, and\n * a plain-text editor. The editor schema is used to detect which nodes and marks are available in\n * the editor, and only parses the input with the minimal required rules.\n *\n * **Note:** Unlike the HTML serializer, built-in rules that are not supported by the schema are not\n * disabled because if the schema does not support certain nodes/marks, the parsing rules don't have\n * valid HTML elements to match in the editor HTML output.\n *\n * @param schema The editor schema to be used for nodes and marks detection.\n *\n * @returns A normalized object for the Markdown serializer.\n */\nfunction createMarkdownSerializer(schema: Schema): MarkdownSerializerReturnType {\n // Initialize Turndown with custom options\n const turndown = new Turndown(INITIAL_TURNDOWN_OPTIONS)\n\n // Turndown ensures Markdown characters are escaped (i.e. `\\`) by default, so they are not\n // interpreted as Markdown when the output is compiled back to HTML. However, for plain-text\n // editors, we need to override the `escape` function to return the input as-is (effectively\n // disabling the escaping behaviour), so that all characters are interpreted as Markdown.\n if (isPlainTextDocument(schema)) {\n turndown.escape = (str) => str\n }\n\n // As for rich-text editors, we need to override the built-in escaping behaviour with a custom\n // implementation to suit our requirements. Please note that the `escape` function takes the\n // text content of each HTML element, with the exception of code elements, so we can be sure\n // that the escaping behaviour will only touch relevant Markdown characters.\n else {\n turndown.escape = (str) => {\n return (\n str\n // Escape all backslash characters that precede any punctuation marks, to\n // prevent the backslash itself from being interpreted as an escape sequence\n // for the subsequent character. It's important to apply this rule first to\n // avoid double escaping.\n .replace(new RegExp(`(\\\\\\\\${REGEX_PUNCTUATION.source})`, 'g'), '\\\\$1')\n\n // Although the CommonMark specification allows for bulleted or ordered lists\n // inside other bulleted or ordered lists (i.e. `- 1. - 1. Item`), the markup\n // generated by Markdown compilers is not supported by Tiptap, and we need to\n // make sure that text context that matches the ordered list syntax is\n // correctly escaped in order to be interpreted as text.\n .replace(/^(\\d+)\\.(\\s.+|$)/, '$1\\\\.$2')\n )\n }\n }\n\n // Overwrite some built-in rules for handling of special behaviours\n // (see documentation for each extension for more details)\n turndown.use(paragraph(schema.nodes.paragraph, isPlainTextDocument(schema)))\n\n // Overwrite the built-in `image` rule if the corresponding node exists in the schema\n if (schema.nodes.image) {\n turndown.use(image(schema.nodes.image))\n }\n\n // Overwrite the built-in `listItem` rule if the corresponding node exists in the schema\n if ((schema.nodes.bulletList || schema.nodes.orderedList) && schema.nodes.listItem) {\n turndown.use(listItem(schema.nodes.listItem))\n }\n\n // Add a rule for `strikethrough` if the corresponding node exists in the schema\n if (schema.marks.strike) {\n turndown.use(strikethrough(schema.marks.strike))\n }\n\n // Add a rule for `taskItem` if the corresponding nodes exists in the schema\n if (schema.nodes.taskList && schema.nodes.taskItem) {\n turndown.use(taskItem(schema.nodes.taskItem))\n }\n\n // Add a custom rule for all suggestion nodes available in the schema\n getSuggestionNodes(schema).forEach((suggestionNode) => {\n turndown.use(suggestion(suggestionNode))\n })\n\n // Return a normalized `serialize` function\n return {\n serialize(html: string) {\n let markdownResult = html\n\n // Turndown was built to convert HTML into Markdown, expecting the input to be\n // standard-compliant HTML. As such, it collapses all whitespace by default, and there's\n // currently no way to opt-out of this behavior. However, for plain-text editors, we\n // need to preserve Markdown whitespace (otherwise we lose syntax like nested lists) by\n // replacing all instances of the space character (but only if it's preceded by another\n // space character) by the non-breaking space character, and after processing the input\n // with Turndown, we restore the original space character.\n if (isPlainTextDocument(schema)) {\n markdownResult = markdownResult.replace(/ {2,}/g, (m) => m.replace(/ /g, '\\u00a0'))\n }\n\n // Get the serialized Markdown parsed with Turndown\n markdownResult = turndown.turndown(markdownResult)\n\n // Restore the original space character for plain-text editors (as mentioned above),\n // after Markdown serialization has been performed\n if (isPlainTextDocument(schema)) {\n markdownResult = markdownResult.replace(/\\u00a0/g, ' ')\n }\n\n // Return the serialized Markdown parsed with Turndown, and with trailing space\n // characters removed\n return markdownResult.replace(/ +$/gm, '')\n },\n }\n}\n\n/**\n * Object that holds multiple Markdown serializer instances based on a given ID.\n */\nconst markdownSerializerInstanceById: MarkdownSerializerInstanceById = {}\n\n/**\n * Returns a singleton instance of a Markdown serializer based on the provided editor schema.\n *\n * @param schema The editor schema connected to the Markdown serializer instance.\n *\n * @returns The Markdown serializer instance for the given editor schema.\n */\nfunction getMarkdownSerializerInstance(schema: Schema) {\n const id = computeSchemaId(schema)\n\n if (!markdownSerializerInstanceById[id]) {\n markdownSerializerInstanceById[id] = createMarkdownSerializer(schema)\n }\n\n return markdownSerializerInstanceById[id]\n}\n\nexport { BULLET_LIST_MARKER, createMarkdownSerializer, getMarkdownSerializerInstance }\n\nexport type { MarkdownSerializerReturnType }\n"],"mappings":";;;;;;;;;;;;;;;AA8CA,MAAM,2BAA6C;CAC/C,cAAc;CACd,IAAI;CACJ,kBAAA;CACA,gBAAgB;CAChB,OAAO;CACP,aAAa;CACb,iBAAiB;CACjB,WAAW;;;;;;CAMX,iBAAiB,GAAG,MAAM;EACtB,MAAM,aAAa,KAAK;EAGxB,IAAI,KAAK,aAAa,QAAS,YAAY,aAAa,QAAQ,KAAK,aAAa,MAC9E,OAAO;EAIX,IAAI,KAAK,aAAa,QAAS,YAAY,aAAa,QAAQ,KAAK,aAAa,MAAO;GACrF,MAAM,QACF,KAAK,aAAa,OACZ,WAAW,aAAa,QAAQ,GAChC,KAAK,aAAa,QAAQ;GACpC,MAAM,QAAQ,MAAM,UAAU,QAAQ,KAAK,WAAW,UAAU,KAAK;GAErE,OAAO,GAAG,QAAQ,OAAO,MAAM,GAAG,QAAQ,QAAQ,EAAE;;EAIxD,OAAO,KAAK,UAAU,SAAS;;CAEtC;;;;;;;;;;;;;;AAeD,SAAS,yBAAyB,QAA8C;CAE5E,MAAM,WAAW,IAAI,SAAS,yBAAyB;CAMvD,IAAI,oBAAoB,OAAO,EAC3B,SAAS,UAAU,QAAQ;MAQ3B,SAAS,UAAU,QAAQ;EACvB,OACI,IAKK,QAAQ,IAAI,OAAO,QAAQ,kBAAkB,OAAO,IAAI,IAAI,EAAE,OAAO,CAOrE,QAAQ,oBAAoB,UAAU;;CAOvD,SAAS,IAAI,UAAU,OAAO,MAAM,WAAW,oBAAoB,OAAO,CAAC,CAAC;CAG5E,IAAI,OAAO,MAAM,OACb,SAAS,IAAI,MAAM,OAAO,MAAM,MAAM,CAAC;CAI3C,KAAK,OAAO,MAAM,cAAc,OAAO,MAAM,gBAAgB,OAAO,MAAM,UACtE,SAAS,IAAI,SAAS,OAAO,MAAM,SAAS,CAAC;CAIjD,IAAI,OAAO,MAAM,QACb,SAAS,IAAI,cAAc,OAAO,MAAM,OAAO,CAAC;CAIpD,IAAI,OAAO,MAAM,YAAY,OAAO,MAAM,UACtC,SAAS,IAAI,SAAS,OAAO,MAAM,SAAS,CAAC;CAIjD,mBAAmB,OAAO,CAAC,SAAS,mBAAmB;EACnD,SAAS,IAAI,WAAW,eAAe,CAAC;GAC1C;CAGF,OAAO,EACH,UAAU,MAAc;EACpB,IAAI,iBAAiB;EASrB,IAAI,oBAAoB,OAAO,EAC3B,iBAAiB,eAAe,QAAQ,WAAW,MAAM,EAAE,QAAQ,MAAM,OAAS,CAAC;EAIvF,iBAAiB,SAAS,SAAS,eAAe;EAIlD,IAAI,oBAAoB,OAAO,EAC3B,iBAAiB,eAAe,QAAQ,WAAW,IAAI;EAK3D,OAAO,eAAe,QAAQ,SAAS,GAAG;IAEjD;;;;;AAML,MAAM,iCAAiE,EAAE;;;;;;;;AASzE,SAAS,8BAA8B,QAAgB;CACnD,MAAM,KAAK,gBAAgB,OAAO;CAElC,IAAI,CAAC,+BAA+B,KAChC,+BAA+B,MAAM,yBAAyB,OAAO;CAGzE,OAAO,+BAA+B"}
@@ -0,0 +1,34 @@
1
+ //#region src/serializers/markdown/plugins/image.ts
2
+ /**
3
+ * Cleans an attribute value by replacing multiple newlines with a single one.
4
+ *
5
+ * @param attribute The attribute value to clean.
6
+ */
7
+ function cleanAttribute(attribute) {
8
+ return attribute ? attribute.replace(/(\n+\s*)+/g, "\n") : "";
9
+ }
10
+ /**
11
+ * A Turndown plugin which adds a custom rule for images. This custom rule is required to disable
12
+ * support for Data URLs (URLs prefixed with the `data: scheme`), while displaying an explicit
13
+ * message in the Markdown output (for debugging and testing).
14
+ *
15
+ * @param nodeType The node object that matches this rule.
16
+ */
17
+ function image(nodeType) {
18
+ return (turndown) => {
19
+ turndown.addRule(nodeType.name, {
20
+ filter: "img",
21
+ replacement(_, node) {
22
+ const src = node.getAttribute("src") ?? "";
23
+ const link = src.startsWith("data:") ? `${src.split(",")[0]},NOT_SUPPORTED` : src;
24
+ const alt = cleanAttribute(node.getAttribute("alt"));
25
+ const title = cleanAttribute(node.getAttribute("title"));
26
+ return src ? `![${alt}](${link}${title.length > 0 ? ` "${title}"` : ""})` : "";
27
+ }
28
+ });
29
+ };
30
+ }
31
+ //#endregion
32
+ export { image };
33
+
34
+ //# sourceMappingURL=image.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"image.js","names":[],"sources":["../../../../src/serializers/markdown/plugins/image.ts"],"sourcesContent":["import type { NodeType } from '@tiptap/pm/model'\nimport type Turndown from 'turndown'\n\n/**\n * Cleans an attribute value by replacing multiple newlines with a single one.\n *\n * @param attribute The attribute value to clean.\n */\nfunction cleanAttribute(attribute: string | null): string {\n return attribute ? attribute.replace(/(\\n+\\s*)+/g, '\\n') : ''\n}\n\n/**\n * A Turndown plugin which adds a custom rule for images. This custom rule is required to disable\n * support for Data URLs (URLs prefixed with the `data: scheme`), while displaying an explicit\n * message in the Markdown output (for debugging and testing).\n *\n * @param nodeType The node object that matches this rule.\n */\nfunction image(nodeType: NodeType): Turndown.Plugin {\n return (turndown: Turndown) => {\n turndown.addRule(nodeType.name, {\n filter: 'img',\n replacement(_, node) {\n const src = (node as Element).getAttribute('src') ?? ''\n\n // Preserve Data URL image prefix with message about base64 being unsupported\n const link = src.startsWith('data:') ? `${src.split(',')[0]},NOT_SUPPORTED` : src\n\n const alt = cleanAttribute((node as Element).getAttribute('alt'))\n const title = cleanAttribute((node as Element).getAttribute('title'))\n\n return src ? `![${alt}](${link}${title.length > 0 ? ` \"${title}\"` : ''})` : ''\n },\n })\n }\n}\n\nexport { image }\n"],"mappings":";;;;;;AAQA,SAAS,eAAe,WAAkC;CACtD,OAAO,YAAY,UAAU,QAAQ,cAAc,KAAK,GAAG;;;;;;;;;AAU/D,SAAS,MAAM,UAAqC;CAChD,QAAQ,aAAuB;EAC3B,SAAS,QAAQ,SAAS,MAAM;GAC5B,QAAQ;GACR,YAAY,GAAG,MAAM;IACjB,MAAM,MAAO,KAAiB,aAAa,MAAM,IAAI;IAGrD,MAAM,OAAO,IAAI,WAAW,QAAQ,GAAG,GAAG,IAAI,MAAM,IAAI,CAAC,GAAG,kBAAkB;IAE9E,MAAM,MAAM,eAAgB,KAAiB,aAAa,MAAM,CAAC;IACjE,MAAM,QAAQ,eAAgB,KAAiB,aAAa,QAAQ,CAAC;IAErE,OAAO,MAAM,KAAK,IAAI,IAAI,OAAO,MAAM,SAAS,IAAI,KAAK,MAAM,KAAK,GAAG,KAAK;;GAEnF,CAAC"}
@@ -0,0 +1,37 @@
1
+ import { extractTagsFromParseRules } from "../../../helpers/serializer.js";
2
+ import "../markdown.js";
3
+ //#region src/serializers/markdown/plugins/list-item.ts
4
+ /**
5
+ * A Turndown plugin which adds a custom rule for standard list items (i.e., not task list items),
6
+ * based on the original list item rule. This custom rule is required to avoid conflicts with task
7
+ * list items, and to normalize the Markdown output.
8
+ *
9
+ * @see https://github.com/mixmark-io/turndown/blob/v7.1.1/src/commonmark-rules.js#L61
10
+ *
11
+ * @param nodeType The node object that matches this rule.
12
+ */
13
+ function listItem(nodeType) {
14
+ const tags = extractTagsFromParseRules(nodeType.spec.parseDOM);
15
+ return (turndown) => {
16
+ turndown.addRule(nodeType.name, {
17
+ filter(node) {
18
+ return tags.some((tag) => tag.toUpperCase() === node.nodeName) && node.getAttribute("data-type") !== "taskItem";
19
+ },
20
+ replacement(content, node) {
21
+ const parentNode = node.parentNode;
22
+ let listItemMarker = `- `;
23
+ if (parentNode?.nodeName === "OL") {
24
+ const start = parentNode.getAttribute("start");
25
+ const index = Array.prototype.indexOf.call(parentNode.children, node);
26
+ listItemMarker = `${start ? Number(start) + index : index + 1}. `;
27
+ }
28
+ const newContent = content.replace(/^\n+/, "").replace(/\n+$/, "\n").replace(/\n/gm, "\n ");
29
+ return `${listItemMarker}${newContent.trim()}${node.nextSibling && !newContent.endsWith("\n") ? "\n" : ""}`;
30
+ }
31
+ });
32
+ };
33
+ }
34
+ //#endregion
35
+ export { listItem };
36
+
37
+ //# sourceMappingURL=list-item.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"list-item.js","names":[],"sources":["../../../../src/serializers/markdown/plugins/list-item.ts"],"sourcesContent":["import { extractTagsFromParseRules } from '../../../helpers/serializer'\nimport { BULLET_LIST_MARKER } from '../markdown'\n\nimport type { NodeType } from '@tiptap/pm/model'\nimport type Turndown from 'turndown'\n\n/**\n * A Turndown plugin which adds a custom rule for standard list items (i.e., not task list items),\n * based on the original list item rule. This custom rule is required to avoid conflicts with task\n * list items, and to normalize the Markdown output.\n *\n * @see https://github.com/mixmark-io/turndown/blob/v7.1.1/src/commonmark-rules.js#L61\n *\n * @param nodeType The node object that matches this rule.\n */\nfunction listItem(nodeType: NodeType): Turndown.Plugin {\n const tags = extractTagsFromParseRules(nodeType.spec.parseDOM)\n\n return (turndown: Turndown) => {\n turndown.addRule(nodeType.name, {\n filter(node) {\n return (\n tags.some((tag) => tag.toUpperCase() === node.nodeName) &&\n node.getAttribute('data-type') !== 'taskItem'\n )\n },\n replacement(content, node) {\n const parentNode = node.parentNode as HTMLElement\n let listItemMarker = `${BULLET_LIST_MARKER} `\n\n // Use a sequence of 1–9 digits for the ordered list marker (CommonMark specification)\n if (parentNode?.nodeName === 'OL') {\n const start = parentNode.getAttribute('start')\n const index = Array.prototype.indexOf.call(parentNode.children, node)\n\n listItemMarker = `${start ? Number(start) + index : index + 1}. `\n }\n\n const newContent = content\n // Remove leading newlines\n .replace(/^\\n+/, '')\n // Replace trailing newlines with a single one\n .replace(/\\n+$/, '\\n')\n // Indent list items with 4 spaces\n .replace(/\\n/gm, '\\n ')\n\n return `${listItemMarker}${newContent.trim()}${\n node.nextSibling && !newContent.endsWith('\\n') ? '\\n' : ''\n }`\n },\n })\n }\n}\n\nexport { listItem }\n"],"mappings":";;;;;;;;;;;;AAeA,SAAS,SAAS,UAAqC;CACnD,MAAM,OAAO,0BAA0B,SAAS,KAAK,SAAS;CAE9D,QAAQ,aAAuB;EAC3B,SAAS,QAAQ,SAAS,MAAM;GAC5B,OAAO,MAAM;IACT,OACI,KAAK,MAAM,QAAQ,IAAI,aAAa,KAAK,KAAK,SAAS,IACvD,KAAK,aAAa,YAAY,KAAK;;GAG3C,YAAY,SAAS,MAAM;IACvB,MAAM,aAAa,KAAK;IACxB,IAAI,iBAAiB;IAGrB,IAAI,YAAY,aAAa,MAAM;KAC/B,MAAM,QAAQ,WAAW,aAAa,QAAQ;KAC9C,MAAM,QAAQ,MAAM,UAAU,QAAQ,KAAK,WAAW,UAAU,KAAK;KAErE,iBAAiB,GAAG,QAAQ,OAAO,MAAM,GAAG,QAAQ,QAAQ,EAAE;;IAGlE,MAAM,aAAa,QAEd,QAAQ,QAAQ,GAAG,CAEnB,QAAQ,QAAQ,KAAK,CAErB,QAAQ,QAAQ,SAAS;IAE9B,OAAO,GAAG,iBAAiB,WAAW,MAAM,GACxC,KAAK,eAAe,CAAC,WAAW,SAAS,KAAK,GAAG,OAAO;;GAGnE,CAAC"}
@@ -0,0 +1,23 @@
1
+ //#region src/serializers/markdown/plugins/paragraph.ts
2
+ /**
3
+ * A Turndown plugin which adds a custom rule for paragraphs. This custom rule is required to avoid
4
+ * adding unnecessary blank lines between paragraphs to plain-text documents, and to list items in
5
+ * rich-text documents.
6
+ *
7
+ * @param nodeType The node object that matches this rule.
8
+ * @param isPlainText Specifies if the schema represents a plain-text document.
9
+ */
10
+ function paragraph(nodeType, isPlainText) {
11
+ return (turndown) => {
12
+ turndown.addRule(nodeType.name, {
13
+ filter: "p",
14
+ replacement(content, node) {
15
+ return isPlainText || !isPlainText && node.parentNode?.nodeName === "LI" ? `\n${content}\n` : `\n\n${content}\n\n`;
16
+ }
17
+ });
18
+ };
19
+ }
20
+ //#endregion
21
+ export { paragraph };
22
+
23
+ //# sourceMappingURL=paragraph.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paragraph.js","names":[],"sources":["../../../../src/serializers/markdown/plugins/paragraph.ts"],"sourcesContent":["import type { NodeType } from '@tiptap/pm/model'\nimport type Turndown from 'turndown'\n\n/**\n * A Turndown plugin which adds a custom rule for paragraphs. This custom rule is required to avoid\n * adding unnecessary blank lines between paragraphs to plain-text documents, and to list items in\n * rich-text documents.\n *\n * @param nodeType The node object that matches this rule.\n * @param isPlainText Specifies if the schema represents a plain-text document.\n */\nfunction paragraph(nodeType: NodeType, isPlainText: boolean): Turndown.Plugin {\n return (turndown: Turndown) => {\n turndown.addRule(nodeType.name, {\n filter: 'p',\n replacement(content, node) {\n const useSingleLineSpacing =\n isPlainText ||\n // Paragraphs within list items should be wrapped with a single line feed to\n // maintain proper list formatting in rich-text documents.\n (!isPlainText && node.parentNode?.nodeName === 'LI')\n\n return useSingleLineSpacing ? `\\n${content}\\n` : `\\n\\n${content}\\n\\n`\n },\n })\n }\n}\n\nexport { paragraph }\n"],"mappings":";;;;;;;;;AAWA,SAAS,UAAU,UAAoB,aAAuC;CAC1E,QAAQ,aAAuB;EAC3B,SAAS,QAAQ,SAAS,MAAM;GAC5B,QAAQ;GACR,YAAY,SAAS,MAAM;IAOvB,OALI,eAGC,CAAC,eAAe,KAAK,YAAY,aAAa,OAErB,KAAK,QAAQ,MAAM,OAAO,QAAQ;;GAEvE,CAAC"}
@@ -0,0 +1,27 @@
1
+ import { extractTagsFromParseRules } from "../../../helpers/serializer.js";
2
+ //#region src/serializers/markdown/plugins/strikethrough.ts
3
+ /**
4
+ * A Turndown plugin which adds a rule for strikethrough text, based on the original plugin. This
5
+ * rules forces two tildes instead of one.
6
+ *
7
+ * @see https://github.com/mixmark-io/turndown-plugin-gfm/blob/v1.0.1/src/strikethrough.js
8
+ *
9
+ * @param markType The mark object that matches this rule.
10
+ */
11
+ function strikethrough(markType) {
12
+ const tags = extractTagsFromParseRules(markType.spec.parseDOM);
13
+ return (turndown) => {
14
+ turndown.addRule(markType.name, {
15
+ filter(node) {
16
+ return tags.some((tag) => tag.toUpperCase() === node.nodeName);
17
+ },
18
+ replacement(content) {
19
+ return `~~${content}~~`;
20
+ }
21
+ });
22
+ };
23
+ }
24
+ //#endregion
25
+ export { strikethrough };
26
+
27
+ //# sourceMappingURL=strikethrough.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"strikethrough.js","names":[],"sources":["../../../../src/serializers/markdown/plugins/strikethrough.ts"],"sourcesContent":["import { extractTagsFromParseRules } from '../../../helpers/serializer'\n\nimport type { MarkType } from '@tiptap/pm/model'\nimport type Turndown from 'turndown'\n\n/**\n * A Turndown plugin which adds a rule for strikethrough text, based on the original plugin. This\n * rules forces two tildes instead of one.\n *\n * @see https://github.com/mixmark-io/turndown-plugin-gfm/blob/v1.0.1/src/strikethrough.js\n *\n * @param markType The mark object that matches this rule.\n */\nfunction strikethrough(markType: MarkType): Turndown.Plugin {\n const tags = extractTagsFromParseRules(markType.spec.parseDOM)\n\n return (turndown: Turndown) => {\n turndown.addRule(markType.name, {\n filter(node) {\n return tags.some((tag) => tag.toUpperCase() === node.nodeName)\n },\n replacement(content) {\n return `~~${content}~~`\n },\n })\n }\n}\n\nexport { strikethrough }\n"],"mappings":";;;;;;;;;;AAaA,SAAS,cAAc,UAAqC;CACxD,MAAM,OAAO,0BAA0B,SAAS,KAAK,SAAS;CAE9D,QAAQ,aAAuB;EAC3B,SAAS,QAAQ,SAAS,MAAM;GAC5B,OAAO,MAAM;IACT,OAAO,KAAK,MAAM,QAAQ,IAAI,aAAa,KAAK,KAAK,SAAS;;GAElE,YAAY,SAAS;IACjB,OAAO,KAAK,QAAQ;;GAE3B,CAAC"}
@@ -0,0 +1,25 @@
1
+ import { getSuggestionUrlScheme } from "../../../helpers/serializer.js";
2
+ //#region src/serializers/markdown/plugins/suggestion.ts
3
+ /**
4
+ * A Turndown plugin which adds a custom rule for suggestion nodes created by the suggestion
5
+ * extension factory function.
6
+ *
7
+ * @param nodeType The node object that matches this rule.
8
+ */
9
+ function suggestion(nodeType) {
10
+ const attributeType = getSuggestionUrlScheme(nodeType);
11
+ return (turndown) => {
12
+ turndown.addRule(nodeType.name, {
13
+ filter(node) {
14
+ return node.hasAttribute(`data-${attributeType}`);
15
+ },
16
+ replacement(_, node) {
17
+ return `[${String(node.getAttribute("data-label"))}](${attributeType}://${String(node.getAttribute("data-id"))})`;
18
+ }
19
+ });
20
+ };
21
+ }
22
+ //#endregion
23
+ export { suggestion };
24
+
25
+ //# sourceMappingURL=suggestion.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"suggestion.js","names":[],"sources":["../../../../src/serializers/markdown/plugins/suggestion.ts"],"sourcesContent":["import { getSuggestionUrlScheme } from '../../../helpers/serializer'\n\nimport type { NodeType } from '@tiptap/pm/model'\nimport type Turndown from 'turndown'\n\n/**\n * A Turndown plugin which adds a custom rule for suggestion nodes created by the suggestion\n * extension factory function.\n *\n * @param nodeType The node object that matches this rule.\n */\nfunction suggestion(nodeType: NodeType): Turndown.Plugin {\n const attributeType = getSuggestionUrlScheme(nodeType)\n\n return (turndown: Turndown) => {\n turndown.addRule(nodeType.name, {\n filter(node: Element) {\n return node.hasAttribute(`data-${attributeType}`)\n },\n replacement(_, node) {\n const label = String((node as Element).getAttribute('data-label'))\n const id = String((node as Element).getAttribute('data-id'))\n\n return `[${label}](${attributeType}://${id})`\n },\n })\n }\n}\n\nexport { suggestion }\n"],"mappings":";;;;;;;;AAWA,SAAS,WAAW,UAAqC;CACrD,MAAM,gBAAgB,uBAAuB,SAAS;CAEtD,QAAQ,aAAuB;EAC3B,SAAS,QAAQ,SAAS,MAAM;GAC5B,OAAO,MAAe;IAClB,OAAO,KAAK,aAAa,QAAQ,gBAAgB;;GAErD,YAAY,GAAG,MAAM;IAIjB,OAAO,IAHO,OAAQ,KAAiB,aAAa,aAAa,CAGjD,CAAC,IAAI,cAAc,KAFxB,OAAQ,KAAiB,aAAa,UAAU,CAEjB,CAAC;;GAElD,CAAC"}
@@ -0,0 +1,36 @@
1
+ import { extractTagsFromParseRules } from "../../../helpers/serializer.js";
2
+ import "../markdown.js";
3
+ //#region src/serializers/markdown/plugins/task-item.ts
4
+ /**
5
+ * A Turndown plugin which adds a rule for task list items (i.e., `* [ ] Task`), based on the
6
+ * original list item rule. This rule not only avoids conflicts with standard list items, but also
7
+ * normalizes the Markdown output.
8
+ *
9
+ * @see https://github.com/mixmark-io/turndown/blob/v7.1.1/src/commonmark-rules.js#L61
10
+ *
11
+ * @param nodeType The node object that matches this rule.
12
+ */
13
+ function taskItem(nodeType) {
14
+ const tags = extractTagsFromParseRules(nodeType.spec.parseDOM);
15
+ return (turndown) => {
16
+ turndown.addRule(nodeType.name, {
17
+ filter(node) {
18
+ return tags.some((tag) => tag.toUpperCase().startsWith(node.nodeName)) && node.getAttribute("data-type") === "taskItem";
19
+ },
20
+ replacement(content, node) {
21
+ const parentNode = node.parentNode;
22
+ let listItemMarker = `- `;
23
+ if (parentNode?.nodeName === "UL") {
24
+ const checked = node.getAttribute("data-checked");
25
+ listItemMarker = `${listItemMarker}${checked === "true" ? "[x]" : "[ ]"} `;
26
+ }
27
+ const newContent = content.replace(/^\n+/, "").replace(/\n+$/, "\n").replace(/\s*\n/gm, "\n ");
28
+ return `${listItemMarker}${newContent.trim()}${node.nextSibling && !newContent.endsWith("\n") ? "\n" : ""}`;
29
+ }
30
+ });
31
+ };
32
+ }
33
+ //#endregion
34
+ export { taskItem };
35
+
36
+ //# sourceMappingURL=task-item.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"task-item.js","names":[],"sources":["../../../../src/serializers/markdown/plugins/task-item.ts"],"sourcesContent":["import { extractTagsFromParseRules } from '../../../helpers/serializer'\nimport { BULLET_LIST_MARKER } from '../markdown'\n\nimport type { NodeType } from '@tiptap/pm/model'\nimport type Turndown from 'turndown'\n\n/**\n * A Turndown plugin which adds a rule for task list items (i.e., `* [ ] Task`), based on the\n * original list item rule. This rule not only avoids conflicts with standard list items, but also\n * normalizes the Markdown output.\n *\n * @see https://github.com/mixmark-io/turndown/blob/v7.1.1/src/commonmark-rules.js#L61\n *\n * @param nodeType The node object that matches this rule.\n */\nfunction taskItem(nodeType: NodeType): Turndown.Plugin {\n const tags = extractTagsFromParseRules(nodeType.spec.parseDOM)\n\n return (turndown: Turndown) => {\n turndown.addRule(nodeType.name, {\n filter(node) {\n return (\n tags.some((tag) => tag.toUpperCase().startsWith(node.nodeName)) &&\n node.getAttribute('data-type') === 'taskItem'\n )\n },\n replacement(content, node) {\n const parentNode = node.parentNode as HTMLElement\n let listItemMarker = `${BULLET_LIST_MARKER} `\n\n if (parentNode?.nodeName === 'UL') {\n const checked = (node as HTMLLIElement).getAttribute('data-checked')\n listItemMarker = `${listItemMarker}${checked === 'true' ? '[x]' : '[ ]'} `\n }\n\n const newContent = content\n // Remove leading newlines\n .replace(/^\\n+/, '')\n // Replace trailing newlines with a single one\n .replace(/\\n+$/, '\\n')\n // Indent list items with 4 spaces\n .replace(/\\s*\\n/gm, '\\n ')\n\n return `${listItemMarker}${newContent.trim()}${\n node.nextSibling && !newContent.endsWith('\\n') ? '\\n' : ''\n }`\n },\n })\n }\n}\n\nexport { taskItem }\n"],"mappings":";;;;;;;;;;;;AAeA,SAAS,SAAS,UAAqC;CACnD,MAAM,OAAO,0BAA0B,SAAS,KAAK,SAAS;CAE9D,QAAQ,aAAuB;EAC3B,SAAS,QAAQ,SAAS,MAAM;GAC5B,OAAO,MAAM;IACT,OACI,KAAK,MAAM,QAAQ,IAAI,aAAa,CAAC,WAAW,KAAK,SAAS,CAAC,IAC/D,KAAK,aAAa,YAAY,KAAK;;GAG3C,YAAY,SAAS,MAAM;IACvB,MAAM,aAAa,KAAK;IACxB,IAAI,iBAAiB;IAErB,IAAI,YAAY,aAAa,MAAM;KAC/B,MAAM,UAAW,KAAuB,aAAa,eAAe;KACpE,iBAAiB,GAAG,iBAAiB,YAAY,SAAS,QAAQ,MAAM;;IAG5E,MAAM,aAAa,QAEd,QAAQ,QAAQ,GAAG,CAEnB,QAAQ,QAAQ,KAAK,CAErB,QAAQ,WAAW,SAAS;IAEjC,OAAO,GAAG,iBAAiB,WAAW,MAAM,GACxC,KAAK,eAAe,CAAC,WAAW,SAAS,KAAK,GAAG,OAAO;;GAGnE,CAAC"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@doist/typist",
3
3
  "description": "The mighty Tiptap-based rich-text editor React component that powers Doist products.",
4
- "version": "11.0.0-next.1",
4
+ "version": "11.0.0",
5
5
  "license": "MIT",
6
6
  "homepage": "https://typist.doist.dev/",
7
7
  "repository": {
@@ -87,7 +87,24 @@
87
87
  "@tiptap/pm": "2.27.2",
88
88
  "@tiptap/react": "2.27.2",
89
89
  "@tiptap/suggestion": "2.27.2",
90
- "prosemirror-codemark": "0.4.2"
90
+ "emoji-regex": "10.6.0",
91
+ "linkifyjs": "4.3.2",
92
+ "lodash-es": "4.17.23",
93
+ "mdast-util-gfm-autolink-literal": "2.0.1",
94
+ "mdast-util-gfm-strikethrough": "2.0.0",
95
+ "micromark-extension-gfm-autolink-literal": "2.1.0",
96
+ "micromark-extension-gfm-strikethrough": "2.1.0",
97
+ "prosemirror-codemark": "0.4.2",
98
+ "rehype-minify-whitespace": "6.0.2",
99
+ "rehype-stringify": "10.0.1",
100
+ "remark-breaks": "4.0.0",
101
+ "remark-parse": "11.0.0",
102
+ "remark-rehype": "11.1.2",
103
+ "turndown": "7.2.2",
104
+ "unified": "11.0.5",
105
+ "unist-util-is": "6.0.1",
106
+ "unist-util-remove": "4.0.0",
107
+ "unist-util-visit": "5.1.0"
91
108
  },
92
109
  "devDependencies": {
93
110
  "@doist/reactist": "30.1.4",
@@ -106,12 +123,12 @@
106
123
  "@types/react": "18.3.29",
107
124
  "@types/react-dom": "18.3.7",
108
125
  "@types/react-syntax-highlighter": "15.5.13",
126
+ "@types/turndown": "5.0.6",
109
127
  "@types/unist": "3.0.3",
110
128
  "@vitejs/plugin-react": "6.0.2",
111
129
  "boring-avatars": "2.0.4",
112
130
  "classnames": "2.5.1",
113
131
  "conventional-changelog-conventionalcommits": "9.3.1",
114
- "emoji-regex": "10.6.0",
115
132
  "github-markdown-css": "5.9.0",
116
133
  "jsdom": "29.1.1",
117
134
  "lefthook": "2.1.8",
@@ -139,32 +156,7 @@
139
156
  "vitest": "4.1.7"
140
157
  },
141
158
  "peerDependencies": {
142
- "emoji-regex": "^10.2.1",
143
- "hast-util-is-element": "^3.0.0",
144
- "linkifyjs": "^4.1.1",
145
- "lodash-es": "^4.17.21",
146
- "mdast-util-gfm-autolink-literal": "^2.0.0",
147
- "mdast-util-gfm-strikethrough": "^2.0.0",
148
- "mdast-util-gfm-task-list-item": "^2.0.0",
149
- "micromark-extension-gfm-autolink-literal": "^2.0.0",
150
- "micromark-extension-gfm-strikethrough": "^2.0.0",
151
- "react": "^17.0.0 || ^18.0.0",
152
- "react-dom": "^17.0.0 || ^18.0.0",
153
- "rehype": "^13.0.0",
154
- "rehype-minify-whitespace": "^6.0.0",
155
- "rehype-parse": "^9.0.0",
156
- "rehype-raw": "^7.0.0",
157
- "rehype-remark": "^10.0.0",
158
- "rehype-stringify": "^10.0.0",
159
- "remark": "^15.0.0",
160
- "remark-breaks": "^4.0.0",
161
- "remark-gfm": "^4.0.0",
162
- "remark-parse": "^11.0.0",
163
- "remark-rehype": "^11.0.0",
164
- "remark-stringify": "^11.0.0",
165
- "unified": "^11.0.0",
166
- "unist-util-is": "^6.0.0",
167
- "unist-util-remove": "^4.0.0",
168
- "unist-util-visit": "^5.0.0"
159
+ "react": "^18.0.0",
160
+ "react-dom": "^18.0.0"
169
161
  }
170
162
  }
@@ -1,36 +0,0 @@
1
- import { isHastElementNode } from "../../../helpers/unified.js";
2
- import { SKIP, visit } from "unist-util-visit";
3
- //#region src/serializers/markdown/plugins/rehype-image.ts
4
- /**
5
- * A rehype plugin to handle two edge cases for images that the default `rehype-remark` and
6
- * `remark-stringify` handlers do not handle correctly:
7
- *
8
- * 1. Images with a Data URL source have their base64 content replaced with `NOT_SUPPORTED`,
9
- * to prevent enormous base64 strings from being dumped into the Markdown output.
10
- * 2. Images with an empty or missing source are removed entirely, to prevent broken Markdown
11
- * syntax (i.e. `![]()` from being generated.
12
- */
13
- function rehypeImage() {
14
- return (tree) => {
15
- visit(tree, "element", (node, index, parent) => {
16
- if (!isHastElementNode(node, "img")) return;
17
- const src = String(node.properties?.src ?? "");
18
- if (!src) {
19
- if (parent && typeof index === "number") {
20
- parent.children.splice(index, 1);
21
- return [SKIP, index];
22
- }
23
- return;
24
- }
25
- if (src.startsWith("data:")) node.properties = {
26
- ...node.properties,
27
- src: `${src.split(",")[0]},NOT_SUPPORTED`
28
- };
29
- });
30
- return tree;
31
- };
32
- }
33
- //#endregion
34
- export { rehypeImage };
35
-
36
- //# sourceMappingURL=rehype-image.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"rehype-image.js","names":[],"sources":["../../../../src/serializers/markdown/plugins/rehype-image.ts"],"sourcesContent":["import { SKIP, visit } from 'unist-util-visit'\n\nimport { isHastElementNode } from '../../../helpers/unified'\n\nimport type { Node as HastNode, Parent as HastParent } from 'hast'\nimport type { Transformer } from 'unified'\n\n/**\n * A rehype plugin to handle two edge cases for images that the default `rehype-remark` and\n * `remark-stringify` handlers do not handle correctly:\n *\n * 1. Images with a Data URL source have their base64 content replaced with `NOT_SUPPORTED`,\n * to prevent enormous base64 strings from being dumped into the Markdown output.\n * 2. Images with an empty or missing source are removed entirely, to prevent broken Markdown\n * syntax (i.e. `![]()` from being generated.\n */\nfunction rehypeImage(): Transformer {\n return (tree: HastNode) => {\n visit(tree, 'element', (node: HastNode, index, parent: HastParent | undefined) => {\n if (!isHastElementNode(node, 'img')) {\n return\n }\n\n const src = String(node.properties?.src ?? '')\n\n // Remove images with an empty or missing source (prevents `![]()` output)\n if (!src) {\n if (parent && typeof index === 'number') {\n parent.children.splice(index, 1)\n return [SKIP, index]\n }\n\n return\n }\n\n // Replace base64 content with `NOT_SUPPORTED` for Data URL images\n if (src.startsWith('data:')) {\n node.properties = {\n ...node.properties,\n src: `${src.split(',')[0]},NOT_SUPPORTED`,\n }\n }\n })\n\n return tree\n }\n}\n\nexport { rehypeImage }\n"],"mappings":";;;;;;;;;;;;AAgBA,SAAS,cAA2B;CAChC,QAAQ,SAAmB;EACvB,MAAM,MAAM,YAAY,MAAgB,OAAO,WAAmC;GAC9E,IAAI,CAAC,kBAAkB,MAAM,MAAM,EAC/B;GAGJ,MAAM,MAAM,OAAO,KAAK,YAAY,OAAO,GAAG;GAG9C,IAAI,CAAC,KAAK;IACN,IAAI,UAAU,OAAO,UAAU,UAAU;KACrC,OAAO,SAAS,OAAO,OAAO,EAAE;KAChC,OAAO,CAAC,MAAM,MAAM;;IAGxB;;GAIJ,IAAI,IAAI,WAAW,QAAQ,EACvB,KAAK,aAAa;IACd,GAAG,KAAK;IACR,KAAK,GAAG,IAAI,MAAM,IAAI,CAAC,GAAG;IAC7B;IAEP;EAEF,OAAO"}
@@ -1,38 +0,0 @@
1
- import { buildSuggestionSchemaInfo } from "../../../helpers/serializer.js";
2
- import { isHastElementNode } from "../../../helpers/unified.js";
3
- import { camelCase } from "lodash-es";
4
- import { visit } from "unist-util-visit";
5
- //#region src/serializers/markdown/plugins/rehype-suggestions.ts
6
- /**
7
- * A rehype plugin to add support for suggestions nodes (e.g., `@username` or `#channel`) by
8
- * transforming `<span data-{scheme}>` elements back into `<a href="{scheme}://{id}">{label}</a>`
9
- * link elements, so that `rehype-remark` can serialize them as standard Markdown links.
10
- *
11
- * @param schema The editor schema to be used for suggestion nodes detection.
12
- */
13
- function rehypeSuggestions(schema) {
14
- const suggestionSchemaInfo = buildSuggestionSchemaInfo(schema);
15
- if (!suggestionSchemaInfo) return (tree) => tree;
16
- const urlSchemeByDataAttribute = new Map([...suggestionSchemaInfo.triggerCharByScheme.keys()].map((scheme) => [camelCase(`data-${scheme}`), scheme]));
17
- return (tree) => {
18
- visit(tree, "element", (node) => {
19
- if (!isHastElementNode(node, "span")) return;
20
- const urlScheme = [...urlSchemeByDataAttribute.entries()].find(([dataAttribute]) => dataAttribute in (node.properties ?? {}))?.[1];
21
- if (!urlScheme) return;
22
- const id = String(node.properties?.dataId ?? "");
23
- const label = String(node.properties?.dataLabel ?? "");
24
- if (!id || !label) return;
25
- node.tagName = "a";
26
- node.properties = { href: `${urlScheme}://${id}` };
27
- node.children = [{
28
- type: "text",
29
- value: label
30
- }];
31
- });
32
- return tree;
33
- };
34
- }
35
- //#endregion
36
- export { rehypeSuggestions };
37
-
38
- //# sourceMappingURL=rehype-suggestions.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"rehype-suggestions.js","names":[],"sources":["../../../../src/serializers/markdown/plugins/rehype-suggestions.ts"],"sourcesContent":["import { camelCase } from 'lodash-es'\nimport { visit } from 'unist-util-visit'\n\nimport { buildSuggestionSchemaInfo } from '../../../helpers/serializer'\nimport { isHastElementNode } from '../../../helpers/unified'\n\nimport type { Schema } from '@tiptap/pm/model'\nimport type { Node as HastNode } from 'hast'\nimport type { Transformer } from 'unified'\n\n/**\n * A rehype plugin to add support for suggestions nodes (e.g., `@username` or `#channel`) by\n * transforming `<span data-{scheme}>` elements back into `<a href=\"{scheme}://{id}\">{label}</a>`\n * link elements, so that `rehype-remark` can serialize them as standard Markdown links.\n *\n * @param schema The editor schema to be used for suggestion nodes detection.\n */\nfunction rehypeSuggestions(schema: Schema): Transformer {\n const suggestionSchemaInfo = buildSuggestionSchemaInfo(schema)\n\n // Return the tree as-is if the editor does not support suggestions\n if (!suggestionSchemaInfo) {\n return (tree: HastNode) => tree\n }\n\n // Precompute a map from each suggestion data attribute (as `rehype-parse` exposes it on the\n // hast node `properties`) to its corresponding URL scheme. `rehype-parse` normalizes data\n // attributes to camelCase, so `data-mention` becomes `dataMention`.\n const urlSchemeByDataAttribute = new Map(\n [...suggestionSchemaInfo.triggerCharByScheme.keys()].map(\n (scheme) => [camelCase(`data-${scheme}`), scheme] as const,\n ),\n )\n\n return (tree: HastNode) => {\n visit(tree, 'element', (node: HastNode) => {\n if (!isHastElementNode(node, 'span')) {\n return\n }\n\n const urlScheme = [...urlSchemeByDataAttribute.entries()].find(\n ([dataAttribute]) => dataAttribute in (node.properties ?? {}),\n )?.[1]\n\n if (!urlScheme) {\n return\n }\n\n const id = String(node.properties?.dataId ?? '')\n const label = String(node.properties?.dataLabel ?? '')\n\n if (!id || !label) {\n return\n }\n\n // Replace the span with an anchor element so `rehype-remark` serializes it as a\n // standard Markdown link (i.e. `[label](scheme://id)`)\n node.tagName = 'a'\n node.properties = {\n href: `${urlScheme}://${id}`,\n }\n node.children = [{ type: 'text', value: label }]\n })\n\n return tree\n }\n}\n\nexport { rehypeSuggestions }\n"],"mappings":";;;;;;;;;;;;AAiBA,SAAS,kBAAkB,QAA6B;CACpD,MAAM,uBAAuB,0BAA0B,OAAO;CAG9D,IAAI,CAAC,sBACD,QAAQ,SAAmB;CAM/B,MAAM,2BAA2B,IAAI,IACjC,CAAC,GAAG,qBAAqB,oBAAoB,MAAM,CAAC,CAAC,KAChD,WAAW,CAAC,UAAU,QAAQ,SAAS,EAAE,OAAO,CACpD,CACJ;CAED,QAAQ,SAAmB;EACvB,MAAM,MAAM,YAAY,SAAmB;GACvC,IAAI,CAAC,kBAAkB,MAAM,OAAO,EAChC;GAGJ,MAAM,YAAY,CAAC,GAAG,yBAAyB,SAAS,CAAC,CAAC,MACrD,CAAC,mBAAmB,kBAAkB,KAAK,cAAc,EAAE,EAC/D,GAAG;GAEJ,IAAI,CAAC,WACD;GAGJ,MAAM,KAAK,OAAO,KAAK,YAAY,UAAU,GAAG;GAChD,MAAM,QAAQ,OAAO,KAAK,YAAY,aAAa,GAAG;GAEtD,IAAI,CAAC,MAAM,CAAC,OACR;GAKJ,KAAK,UAAU;GACf,KAAK,aAAa,EACd,MAAM,GAAG,UAAU,KAAK,MAC3B;GACD,KAAK,WAAW,CAAC;IAAE,MAAM;IAAQ,OAAO;IAAO,CAAC;IAClD;EAEF,OAAO"}
@@ -1,32 +0,0 @@
1
- import { isHastElementNode } from "../../../helpers/unified.js";
2
- import { visit } from "unist-util-visit";
3
- //#region src/serializers/markdown/plugins/rehype-task-list.ts
4
- /**
5
- * A rehype plugin to transform Tiptap-style task list items (i.e. `<li data-type="taskItem"
6
- * data-checked="true|false">`) into standard GFM task list items (i.e. `<li><input type="checkbox"
7
- * disabled checked> ...</li>`), so that `hast-util-to-mdast` and `remark-gfm` can serialize them
8
- * as standard Markdown task list items (i.e. `- [ ]` and `- [x]`).
9
- */
10
- function rehypeTaskList() {
11
- return (tree) => {
12
- visit(tree, "element", (node) => {
13
- if (!isHastElementNode(node, "li")) return;
14
- if (node.properties?.dataType !== "taskItem") return;
15
- node.children = [{
16
- type: "element",
17
- tagName: "input",
18
- properties: {
19
- type: "checkbox",
20
- disabled: true,
21
- ...node.properties?.dataChecked === "true" ? { checked: true } : {}
22
- },
23
- children: []
24
- }, ...node.children];
25
- });
26
- return tree;
27
- };
28
- }
29
- //#endregion
30
- export { rehypeTaskList };
31
-
32
- //# sourceMappingURL=rehype-task-list.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"rehype-task-list.js","names":[],"sources":["../../../../src/serializers/markdown/plugins/rehype-task-list.ts"],"sourcesContent":["import { visit } from 'unist-util-visit'\n\nimport { isHastElementNode } from '../../../helpers/unified'\n\nimport type { Element, Node as HastNode } from 'hast'\nimport type { Transformer } from 'unified'\n\n/**\n * A rehype plugin to transform Tiptap-style task list items (i.e. `<li data-type=\"taskItem\"\n * data-checked=\"true|false\">`) into standard GFM task list items (i.e. `<li><input type=\"checkbox\"\n * disabled checked> ...</li>`), so that `hast-util-to-mdast` and `remark-gfm` can serialize them\n * as standard Markdown task list items (i.e. `- [ ]` and `- [x]`).\n */\nfunction rehypeTaskList(): Transformer {\n return (tree: HastNode) => {\n visit(tree, 'element', (node: HastNode) => {\n if (!isHastElementNode(node, 'li')) {\n return\n }\n\n if (node.properties?.dataType !== 'taskItem') {\n return\n }\n\n const isChecked = node.properties?.dataChecked === 'true'\n\n const checkbox: Element = {\n type: 'element',\n tagName: 'input',\n properties: {\n type: 'checkbox',\n disabled: true,\n ...(isChecked ? { checked: true } : {}),\n },\n children: [],\n }\n\n node.children = [checkbox, ...node.children]\n })\n\n return tree\n }\n}\n\nexport { rehypeTaskList }\n"],"mappings":";;;;;;;;;AAaA,SAAS,iBAA8B;CACnC,QAAQ,SAAmB;EACvB,MAAM,MAAM,YAAY,SAAmB;GACvC,IAAI,CAAC,kBAAkB,MAAM,KAAK,EAC9B;GAGJ,IAAI,KAAK,YAAY,aAAa,YAC9B;GAgBJ,KAAK,WAAW,CAAC;IAVb,MAAM;IACN,SAAS;IACT,YAAY;KACR,MAAM;KACN,UAAU;KACV,GARU,KAAK,YAAY,gBAAgB,SAQ3B,EAAE,SAAS,MAAM,GAAG,EAAE;KACzC;IACD,UAAU,EAAE;IAGS,EAAE,GAAG,KAAK,SAAS;IAC9C;EAEF,OAAO"}
@@ -1,20 +0,0 @@
1
- import { gfmStrikethroughToMarkdown } from "mdast-util-gfm-strikethrough";
2
- //#region src/serializers/markdown/plugins/remark-strikethrough.ts
3
- /**
4
- * A remark plugin to add support for serializing the strikethrough extension from the GitHub
5
- * Flavored Markdown (GFM) specification (i.e., `~~text~~`).
6
- *
7
- * This is a standalone plugin which makes use of the `mdast-util-gfm-strikethrough` package, and
8
- * the implementation is inspired by the third-party `remark-gfm` plugin.
9
- *
10
- * The reason why we don't use `remark-gfm` directly is because we only want to opt into the
11
- * specific GFM features the editor supports, rather than enabling all of them at once.
12
- */
13
- function remarkStrikethrough() {
14
- const data = this.data();
15
- (data.toMarkdownExtensions ?? (data.toMarkdownExtensions = [])).push(gfmStrikethroughToMarkdown());
16
- }
17
- //#endregion
18
- export { remarkStrikethrough };
19
-
20
- //# sourceMappingURL=remark-strikethrough.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"remark-strikethrough.js","names":[],"sources":["../../../../src/serializers/markdown/plugins/remark-strikethrough.ts"],"sourcesContent":["import { gfmStrikethroughToMarkdown } from 'mdast-util-gfm-strikethrough'\n\nimport type { Processor } from 'unified'\n\n/**\n * A remark plugin to add support for serializing the strikethrough extension from the GitHub\n * Flavored Markdown (GFM) specification (i.e., `~~text~~`).\n *\n * This is a standalone plugin which makes use of the `mdast-util-gfm-strikethrough` package, and\n * the implementation is inspired by the third-party `remark-gfm` plugin.\n *\n * The reason why we don't use `remark-gfm` directly is because we only want to opt into the\n * specific GFM features the editor supports, rather than enabling all of them at once.\n */\nfunction remarkStrikethrough(this: Processor) {\n const data = this.data()\n\n const toMarkdownExtensions = data.toMarkdownExtensions ?? (data.toMarkdownExtensions = [])\n\n toMarkdownExtensions.push(gfmStrikethroughToMarkdown())\n}\n\nexport { remarkStrikethrough }\n"],"mappings":";;;;;;;;;;;;AAcA,SAAS,sBAAqC;CAC1C,MAAM,OAAO,KAAK,MAAM;CAIxB,CAF6B,KAAK,yBAAyB,KAAK,uBAAuB,EAAE,GAEpE,KAAK,4BAA4B,CAAC"}
@@ -1,20 +0,0 @@
1
- import { gfmTaskListItemToMarkdown } from "mdast-util-gfm-task-list-item";
2
- //#region src/serializers/markdown/plugins/remark-task-list.ts
3
- /**
4
- * A remark plugin to add support for serializing the task list extension from the GitHub Flavored
5
- * Markdown (GFM) specification (i.e., `- [ ]` and `- [x]`).
6
- *
7
- * This is a standalone plugin which makes use of the `mdast-util-gfm-task-list-item` package, and
8
- * the implementation is inspired by the third-party `remark-gfm` plugin.
9
- *
10
- * The reason why we don't use `remark-gfm` directly is because we only want to opt into the
11
- * specific GFM features the editor supports, rather than enabling all of them at once.
12
- */
13
- function remarkTaskList() {
14
- const data = this.data();
15
- (data.toMarkdownExtensions ?? (data.toMarkdownExtensions = [])).push(gfmTaskListItemToMarkdown());
16
- }
17
- //#endregion
18
- export { remarkTaskList };
19
-
20
- //# sourceMappingURL=remark-task-list.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"remark-task-list.js","names":[],"sources":["../../../../src/serializers/markdown/plugins/remark-task-list.ts"],"sourcesContent":["import { gfmTaskListItemToMarkdown } from 'mdast-util-gfm-task-list-item'\n\nimport type { Processor } from 'unified'\n\n/**\n * A remark plugin to add support for serializing the task list extension from the GitHub Flavored\n * Markdown (GFM) specification (i.e., `- [ ]` and `- [x]`).\n *\n * This is a standalone plugin which makes use of the `mdast-util-gfm-task-list-item` package, and\n * the implementation is inspired by the third-party `remark-gfm` plugin.\n *\n * The reason why we don't use `remark-gfm` directly is because we only want to opt into the\n * specific GFM features the editor supports, rather than enabling all of them at once.\n */\nfunction remarkTaskList(this: Processor) {\n const data = this.data()\n\n const toMarkdownExtensions = data.toMarkdownExtensions ?? (data.toMarkdownExtensions = [])\n\n toMarkdownExtensions.push(gfmTaskListItemToMarkdown())\n}\n\nexport { remarkTaskList }\n"],"mappings":";;;;;;;;;;;;AAcA,SAAS,iBAAgC;CACrC,MAAM,OAAO,KAAK,MAAM;CAIxB,CAF6B,KAAK,yBAAyB,KAAK,uBAAuB,EAAE,GAEpE,KAAK,2BAA2B,CAAC"}