@doist/typist 1.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 (147) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/CODE_OF_CONDUCT.md +83 -0
  3. package/CONTRIBUTING.md +123 -0
  4. package/LICENSE +21 -0
  5. package/README.md +74 -0
  6. package/dist/components/typist-editor.d.ts +173 -0
  7. package/dist/components/typist-editor.d.ts.map +1 -0
  8. package/dist/components/typist-editor.helper.d.ts +22 -0
  9. package/dist/components/typist-editor.helper.d.ts.map +1 -0
  10. package/dist/components/typist-editor.helper.js +26 -0
  11. package/dist/components/typist-editor.js +160 -0
  12. package/dist/constants/common.d.ts +10 -0
  13. package/dist/constants/common.d.ts.map +1 -0
  14. package/dist/constants/common.js +9 -0
  15. package/dist/constants/extension-priorities.d.ts +26 -0
  16. package/dist/constants/extension-priorities.d.ts.map +1 -0
  17. package/dist/constants/extension-priorities.js +25 -0
  18. package/dist/constants/regular-expressions.d.ts +6 -0
  19. package/dist/constants/regular-expressions.d.ts.map +1 -0
  20. package/dist/constants/regular-expressions.js +5 -0
  21. package/dist/extensions/core/extra-editor-commands/commands/extend-word-range.d.ts +24 -0
  22. package/dist/extensions/core/extra-editor-commands/commands/extend-word-range.d.ts.map +1 -0
  23. package/dist/extensions/core/extra-editor-commands/commands/extend-word-range.js +32 -0
  24. package/dist/extensions/core/extra-editor-commands/commands/insert-markdown-content.d.ts +28 -0
  25. package/dist/extensions/core/extra-editor-commands/commands/insert-markdown-content.d.ts.map +1 -0
  26. package/dist/extensions/core/extra-editor-commands/commands/insert-markdown-content.js +25 -0
  27. package/dist/extensions/core/extra-editor-commands/extra-editor-commands.d.ts +9 -0
  28. package/dist/extensions/core/extra-editor-commands/extra-editor-commands.d.ts.map +1 -0
  29. package/dist/extensions/core/extra-editor-commands/extra-editor-commands.js +18 -0
  30. package/dist/extensions/core/view-event-handlers.d.ts +33 -0
  31. package/dist/extensions/core/view-event-handlers.d.ts.map +1 -0
  32. package/dist/extensions/core/view-event-handlers.js +35 -0
  33. package/dist/extensions/plain-text/paste-multiline-text.d.ts +10 -0
  34. package/dist/extensions/plain-text/paste-multiline-text.d.ts.map +1 -0
  35. package/dist/extensions/plain-text/paste-multiline-text.js +66 -0
  36. package/dist/extensions/plain-text/plain-text-document.d.ts +17 -0
  37. package/dist/extensions/plain-text/plain-text-document.d.ts.map +1 -0
  38. package/dist/extensions/plain-text/plain-text-document.js +17 -0
  39. package/dist/extensions/plain-text/plain-text-kit.d.ts +42 -0
  40. package/dist/extensions/plain-text/plain-text-kit.d.ts.map +1 -0
  41. package/dist/extensions/plain-text/plain-text-kit.js +47 -0
  42. package/dist/extensions/plain-text/plain-text-paragraph.d.ts +9 -0
  43. package/dist/extensions/plain-text/plain-text-paragraph.d.ts.map +1 -0
  44. package/dist/extensions/plain-text/plain-text-paragraph.js +13 -0
  45. package/dist/extensions/plain-text/smart-markdown-typing/plugins/smart-lists.d.ts +9 -0
  46. package/dist/extensions/plain-text/smart-markdown-typing/plugins/smart-lists.d.ts.map +1 -0
  47. package/dist/extensions/plain-text/smart-markdown-typing/plugins/smart-lists.js +89 -0
  48. package/dist/extensions/plain-text/smart-markdown-typing/plugins/smart-select-wrap.d.ts +9 -0
  49. package/dist/extensions/plain-text/smart-markdown-typing/plugins/smart-select-wrap.d.ts.map +1 -0
  50. package/dist/extensions/plain-text/smart-markdown-typing/plugins/smart-select-wrap.js +49 -0
  51. package/dist/extensions/plain-text/smart-markdown-typing/plugins/smart-url-pasting.d.ts +9 -0
  52. package/dist/extensions/plain-text/smart-markdown-typing/plugins/smart-url-pasting.d.ts.map +1 -0
  53. package/dist/extensions/plain-text/smart-markdown-typing/plugins/smart-url-pasting.js +43 -0
  54. package/dist/extensions/plain-text/smart-markdown-typing/smart-markdown-typing.d.ts +8 -0
  55. package/dist/extensions/plain-text/smart-markdown-typing/smart-markdown-typing.d.ts.map +1 -0
  56. package/dist/extensions/plain-text/smart-markdown-typing/smart-markdown-typing.js +17 -0
  57. package/dist/extensions/rich-text/bold-and-italics.d.ts +8 -0
  58. package/dist/extensions/rich-text/bold-and-italics.d.ts.map +1 -0
  59. package/dist/extensions/rich-text/bold-and-italics.js +40 -0
  60. package/dist/extensions/rich-text/curvenote-codemark.d.ts +11 -0
  61. package/dist/extensions/rich-text/curvenote-codemark.d.ts.map +1 -0
  62. package/dist/extensions/rich-text/curvenote-codemark.js +18 -0
  63. package/dist/extensions/rich-text/paste-emojis.d.ts +9 -0
  64. package/dist/extensions/rich-text/paste-emojis.d.ts.map +1 -0
  65. package/dist/extensions/rich-text/paste-emojis.js +28 -0
  66. package/dist/extensions/rich-text/paste-markdown.d.ts +11 -0
  67. package/dist/extensions/rich-text/paste-markdown.d.ts.map +1 -0
  68. package/dist/extensions/rich-text/paste-markdown.js +68 -0
  69. package/dist/extensions/rich-text/rich-text-document.d.ts +17 -0
  70. package/dist/extensions/rich-text/rich-text-document.d.ts.map +1 -0
  71. package/dist/extensions/rich-text/rich-text-document.js +17 -0
  72. package/dist/extensions/rich-text/rich-text-image.d.ts +80 -0
  73. package/dist/extensions/rich-text/rich-text-image.d.ts.map +1 -0
  74. package/dist/extensions/rich-text/rich-text-image.js +109 -0
  75. package/dist/extensions/rich-text/rich-text-kit.d.ts +129 -0
  76. package/dist/extensions/rich-text/rich-text-kit.d.ts.map +1 -0
  77. package/dist/extensions/rich-text/rich-text-kit.js +130 -0
  78. package/dist/extensions/rich-text/rich-text-link.d.ts +10 -0
  79. package/dist/extensions/rich-text/rich-text-link.d.ts.map +1 -0
  80. package/dist/extensions/rich-text/rich-text-link.js +102 -0
  81. package/dist/extensions/shared/copy-markdown-source.d.ts +20 -0
  82. package/dist/extensions/shared/copy-markdown-source.d.ts.map +1 -0
  83. package/dist/extensions/shared/copy-markdown-source.js +35 -0
  84. package/dist/extensions/shared/paste-singleline-text.d.ts +10 -0
  85. package/dist/extensions/shared/paste-singleline-text.d.ts.map +1 -0
  86. package/dist/extensions/shared/paste-singleline-text.js +43 -0
  87. package/dist/factories/create-suggestion-extension.d.ts +121 -0
  88. package/dist/factories/create-suggestion-extension.d.ts.map +1 -0
  89. package/dist/factories/create-suggestion-extension.js +149 -0
  90. package/dist/helpers/dom.d.ts +8 -0
  91. package/dist/helpers/dom.d.ts.map +1 -0
  92. package/dist/helpers/dom.js +9 -0
  93. package/dist/helpers/schema.d.ts +19 -0
  94. package/dist/helpers/schema.d.ts.map +1 -0
  95. package/dist/helpers/schema.js +21 -0
  96. package/dist/helpers/serializer.d.ts +11 -0
  97. package/dist/helpers/serializer.d.ts.map +1 -0
  98. package/dist/helpers/serializer.js +16 -0
  99. package/dist/hooks/use-editor.d.ts +19 -0
  100. package/dist/hooks/use-editor.d.ts.map +1 -0
  101. package/dist/hooks/use-editor.js +46 -0
  102. package/dist/index.d.ts +26 -0
  103. package/dist/index.d.ts.map +1 -0
  104. package/dist/index.js +17 -0
  105. package/dist/serializers/html/extensions/checkbox.d.ts +8 -0
  106. package/dist/serializers/html/extensions/checkbox.d.ts.map +1 -0
  107. package/dist/serializers/html/extensions/checkbox.js +12 -0
  108. package/dist/serializers/html/extensions/code.d.ts +9 -0
  109. package/dist/serializers/html/extensions/code.d.ts.map +1 -0
  110. package/dist/serializers/html/extensions/code.js +20 -0
  111. package/dist/serializers/html/extensions/html.d.ts +10 -0
  112. package/dist/serializers/html/extensions/html.d.ts.map +1 -0
  113. package/dist/serializers/html/extensions/html.js +15 -0
  114. package/dist/serializers/html/extensions/link.d.ts +11 -0
  115. package/dist/serializers/html/extensions/link.d.ts.map +1 -0
  116. package/dist/serializers/html/extensions/link.js +28 -0
  117. package/dist/serializers/html/extensions/paragraph.d.ts +12 -0
  118. package/dist/serializers/html/extensions/paragraph.d.ts.map +1 -0
  119. package/dist/serializers/html/extensions/paragraph.js +51 -0
  120. package/dist/serializers/html/extensions/task-list.d.ts +9 -0
  121. package/dist/serializers/html/extensions/task-list.d.ts.map +1 -0
  122. package/dist/serializers/html/extensions/task-list.js +31 -0
  123. package/dist/serializers/html/html.d.ts +27 -0
  124. package/dist/serializers/html/html.d.ts.map +1 -0
  125. package/dist/serializers/html/html.js +129 -0
  126. package/dist/serializers/markdown/markdown.d.ts +40 -0
  127. package/dist/serializers/markdown/markdown.d.ts.map +1 -0
  128. package/dist/serializers/markdown/markdown.js +126 -0
  129. package/dist/serializers/markdown/plugins/image.d.ts +12 -0
  130. package/dist/serializers/markdown/plugins/image.d.ts.map +1 -0
  131. package/dist/serializers/markdown/plugins/image.js +31 -0
  132. package/dist/serializers/markdown/plugins/list-item.d.ts +14 -0
  133. package/dist/serializers/markdown/plugins/list-item.d.ts.map +1 -0
  134. package/dist/serializers/markdown/plugins/list-item.js +41 -0
  135. package/dist/serializers/markdown/plugins/paragraph.d.ts +12 -0
  136. package/dist/serializers/markdown/plugins/paragraph.d.ts.map +1 -0
  137. package/dist/serializers/markdown/plugins/paragraph.js +18 -0
  138. package/dist/serializers/markdown/plugins/strikethrough.d.ts +13 -0
  139. package/dist/serializers/markdown/plugins/strikethrough.d.ts.map +1 -0
  140. package/dist/serializers/markdown/plugins/strikethrough.js +23 -0
  141. package/dist/serializers/markdown/plugins/suggestion.d.ts +11 -0
  142. package/dist/serializers/markdown/plugins/suggestion.d.ts.map +1 -0
  143. package/dist/serializers/markdown/plugins/suggestion.js +21 -0
  144. package/dist/serializers/markdown/plugins/task-item.d.ts +14 -0
  145. package/dist/serializers/markdown/plugins/task-item.d.ts.map +1 -0
  146. package/dist/serializers/markdown/plugins/task-item.js +39 -0
  147. package/package.json +146 -0
@@ -0,0 +1,15 @@
1
+ import { escape } from 'lodash-es';
2
+ /**
3
+ * A Marked extension which tweaks the `html` renderer to escape special characters (i.e. `&`, `<`,
4
+ * `>`, `"`, and `'`) for unsupported tokens. This custom rule is required because the escaping must
5
+ * be performed at the token level to avoid escaping the full input, which would result in unwanted
6
+ * escaped output (i.e. valid HTML would be escaped).
7
+ */
8
+ const html = {
9
+ renderer: {
10
+ html(html) {
11
+ return escape(html);
12
+ },
13
+ },
14
+ };
15
+ export { html };
@@ -0,0 +1,11 @@
1
+ import { marked } from 'marked';
2
+ import type { NodeType } from 'prosemirror-model';
3
+ /**
4
+ * A Marked extension which tweaks the `link` renderer to add support for suggestion nodes, while
5
+ * preserving the original renderer for standard links.
6
+ *
7
+ * @param suggestionNodes An array of the suggestion nodes to serialize.
8
+ */
9
+ declare function link(suggestionNodes: NodeType[]): marked.MarkedExtension;
10
+ export { link };
11
+ //# sourceMappingURL=link.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"link.d.ts","sourceRoot":"","sources":["../../../../src/serializers/html/extensions/link.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAE/B,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAA;AAIjD;;;;;GAKG;AACH,iBAAS,IAAI,CAAC,eAAe,EAAE,QAAQ,EAAE,GAAG,MAAM,CAAC,eAAe,CAsBjE;AAED,OAAO,EAAE,IAAI,EAAE,CAAA"}
@@ -0,0 +1,28 @@
1
+ import { kebabCase } from 'lodash-es';
2
+ import { marked } from 'marked';
3
+ const markedRenderer = new marked.Renderer();
4
+ /**
5
+ * A Marked extension which tweaks the `link` renderer to add support for suggestion nodes, while
6
+ * preserving the original renderer for standard links.
7
+ *
8
+ * @param suggestionNodes An array of the suggestion nodes to serialize.
9
+ */
10
+ function link(suggestionNodes) {
11
+ const linkSchemaRegex = new RegExp(`^(?:${suggestionNodes
12
+ .map((suggestionNode) => kebabCase(suggestionNode.name.replace(/Suggestion$/, '')))
13
+ .join('|')})://`);
14
+ return {
15
+ renderer: {
16
+ link(href, title, text) {
17
+ if (href && linkSchemaRegex.test(href)) {
18
+ const [, schema, id] = /^([a-z-]+):\/\/(\d+)$/i.exec(href) || [];
19
+ if (schema && id && text) {
20
+ return `<span data-${schema} data-id="${id}" data-label="${text}"></span>`;
21
+ }
22
+ }
23
+ return markedRenderer.link.apply(this, [href, title, text]);
24
+ },
25
+ },
26
+ };
27
+ }
28
+ export { link };
@@ -0,0 +1,12 @@
1
+ import { marked } from 'marked';
2
+ import type { NodeType } from 'prosemirror-model';
3
+ /**
4
+ * A Marked extension which tweaks the `paragraph` renderer to remove the surrounding `<p></p>` tag
5
+ * when it's wrapping an image element as a single child, or to remove all inline images if the
6
+ * editor was configured without inline image support (default).
7
+ *
8
+ * @param imageNode The node object for the schema image node.
9
+ */
10
+ declare function paragraph(imageNode?: NodeType): marked.MarkedExtension;
11
+ export { paragraph };
12
+ //# sourceMappingURL=paragraph.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paragraph.d.ts","sourceRoot":"","sources":["../../../../src/serializers/html/extensions/paragraph.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAI/B,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAA;AAOjD;;;;;;GAMG;AACH,iBAAS,SAAS,CAAC,SAAS,CAAC,EAAE,QAAQ,GAAG,MAAM,CAAC,eAAe,CA6C/D;AAED,OAAO,EAAE,SAAS,EAAE,CAAA"}
@@ -0,0 +1,51 @@
1
+ import { marked } from 'marked';
2
+ import { parseHtmlToElement } from '../../../helpers/dom';
3
+ /**
4
+ * Initialize a new instance of the original renderer to be used by the extension.
5
+ */
6
+ const markedRenderer = new marked.Renderer();
7
+ /**
8
+ * A Marked extension which tweaks the `paragraph` renderer to remove the surrounding `<p></p>` tag
9
+ * when it's wrapping an image element as a single child, or to remove all inline images if the
10
+ * editor was configured without inline image support (default).
11
+ *
12
+ * @param imageNode The node object for the schema image node.
13
+ */
14
+ function paragraph(imageNode) {
15
+ const isInlineImageSupported = imageNode ? imageNode.spec.inline : false;
16
+ return {
17
+ renderer: {
18
+ paragraph(text) {
19
+ const renderedHTML = markedRenderer.paragraph.apply(this, [text]);
20
+ const { firstElementChild: renderedElement } = parseHtmlToElement(renderedHTML);
21
+ // Return the rendered HTML as a safeguard in the event that the rendered element
22
+ // does not exist (just in case, but highly unlikely to happen)
23
+ if (!renderedElement) {
24
+ return renderedHTML;
25
+ }
26
+ // Return the rendered element HTML if inline images are supported
27
+ if (isInlineImageSupported) {
28
+ return renderedElement.outerHTML;
29
+ }
30
+ const imageChildNodes = Array.from(renderedElement.childNodes).filter((childNode) => childNode.nodeName === 'IMG');
31
+ // Return the HTML contained in the rendered element if it only contains images
32
+ // (i.e. removes the surrounding `<p></p>` tag from image child elements)
33
+ if (renderedElement.childNodes.length === imageChildNodes.length) {
34
+ return renderedElement.innerHTML;
35
+ }
36
+ // Remove all image elements contained in the rendered element since at this point
37
+ // we know that the editor does not support inline images
38
+ if (renderedElement.childNodes.length > 1) {
39
+ renderedElement.childNodes.forEach((childNode) => {
40
+ if (childNode.nodeName === 'IMG') {
41
+ renderedElement.removeChild(childNode);
42
+ }
43
+ });
44
+ }
45
+ // Return the rendered element HTML (stripped of inline images)
46
+ return renderedElement.outerHTML;
47
+ },
48
+ },
49
+ };
50
+ }
51
+ export { paragraph };
@@ -0,0 +1,9 @@
1
+ import { marked } from 'marked';
2
+ /**
3
+ * A Marked extension which tweaks the `checkbox`, `list`, and `listitem` renderers to add support
4
+ * for task lists (i.e., `* [ ] Task`), while preserving the original renderers for standard bullet
5
+ * and ordered lists.
6
+ */
7
+ declare const taskList: marked.MarkedExtension;
8
+ export { taskList };
9
+ //# sourceMappingURL=task-list.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"task-list.d.ts","sourceRoot":"","sources":["../../../../src/serializers/html/extensions/task-list.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAO/B;;;;GAIG;AACH,QAAA,MAAM,QAAQ,EAAE,MAAM,CAAC,eAwBtB,CAAA;AAED,OAAO,EAAE,QAAQ,EAAE,CAAA"}
@@ -0,0 +1,31 @@
1
+ import { marked } from 'marked';
2
+ /**
3
+ * Initialize a new instance of the original renderer to be used by the extension.
4
+ */
5
+ const markedRenderer = new marked.Renderer();
6
+ /**
7
+ * A Marked extension which tweaks the `checkbox`, `list`, and `listitem` renderers to add support
8
+ * for task lists (i.e., `* [ ] Task`), while preserving the original renderers for standard bullet
9
+ * and ordered lists.
10
+ */
11
+ const taskList = {
12
+ renderer: {
13
+ checkbox(checked) {
14
+ return `[${checked ? 'x' : ' '}] `;
15
+ },
16
+ list(body, ordered, start) {
17
+ if (body.includes('data-type="taskItem"')) {
18
+ return `<ul data-type="taskList">\n${body}</ul>\n`;
19
+ }
20
+ return markedRenderer.list.apply(this, [body, ordered, start]);
21
+ },
22
+ listitem(text) {
23
+ const taskItem = /^(\[[ x]\])( [\s\S]*)?/i.exec(text);
24
+ if (taskItem) {
25
+ return `<li data-type="taskItem" data-checked="${String(taskItem[1] !== '[ ]')}">${taskItem[2].trim() || ''}</li>\n`;
26
+ }
27
+ return markedRenderer.listitem.apply(this, [text, false, false]);
28
+ },
29
+ },
30
+ };
31
+ export { taskList };
@@ -0,0 +1,27 @@
1
+ import type { Schema } from 'prosemirror-model';
2
+ /**
3
+ * The return type for the `createHTMLSerializer` function.
4
+ */
5
+ declare type HTMLSerializerReturnType = {
6
+ /**
7
+ * Serializes an input Markdown string to an output HTML string.
8
+ *
9
+ * @param markdown The Markdown string to serialize.
10
+ *
11
+ * @returns The serialized HTML.
12
+ */
13
+ serialize: (markdown: string) => string;
14
+ };
15
+ /**
16
+ * Create a Markdown to HTML serializer with the Marked library for a rich-text editor, or use a
17
+ * custom serializer for a plain-text editor. The editor schema is used to detect which nodes and
18
+ * marks are available in the editor, and only parses the input with the minimal required rules.
19
+ *
20
+ * @param schema The editor schema to be used for nodes and marks detection.
21
+ *
22
+ * @returns A normalized `serialize` function.
23
+ */
24
+ declare function createHTMLSerializer(schema: Schema): HTMLSerializerReturnType;
25
+ export { createHTMLSerializer };
26
+ export type { HTMLSerializerReturnType };
27
+ //# sourceMappingURL=html.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"html.d.ts","sourceRoot":"","sources":["../../../src/serializers/html/html.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAA;AAE/C;;GAEG;AACH,aAAK,wBAAwB,GAAG;IAC5B;;;;;;OAMG;IACH,SAAS,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,MAAM,CAAA;CAC1C,CAAA;AA0HD;;;;;;;;GAQG;AACH,iBAAS,oBAAoB,CAAC,MAAM,EAAE,MAAM,GAAG,wBAAwB,CAQtE;AAED,OAAO,EAAE,oBAAoB,EAAE,CAAA;AAE/B,YAAY,EAAE,wBAAwB,EAAE,CAAA"}
@@ -0,0 +1,129 @@
1
+ import { escape, kebabCase } from 'lodash-es';
2
+ import { marked } from 'marked';
3
+ import { REGEX_LINE_BREAKS } from '../../constants/regular-expressions';
4
+ import { isPlainTextDocument } from '../../helpers/schema';
5
+ import { checkbox } from './extensions/checkbox';
6
+ import { code } from './extensions/code';
7
+ import { html } from './extensions/html';
8
+ import { link } from './extensions/link';
9
+ import { paragraph } from './extensions/paragraph';
10
+ import { taskList } from './extensions/task-list';
11
+ /**
12
+ * Sensible default options to initialize the Marked parser with.
13
+ *
14
+ * @see https://marked.js.org/using_advanced#options
15
+ */
16
+ const INITIAL_MARKED_OPTIONS = {
17
+ breaks: true,
18
+ gfm: true,
19
+ headerIds: false,
20
+ silent: true,
21
+ };
22
+ /**
23
+ * Serialize the Markdown input to HTML with a custom serializer, ready for a plain-text editor.
24
+ *
25
+ * @param schema The editor schema to be used for nodes and marks detection.
26
+ *
27
+ * @returns The serialized HTML output.
28
+ */
29
+ function serializeForPlainTextEditor(markdown, schema) {
30
+ // Converts special characters (i.e. `&`, `<`, `>`, `"`, and `'`) to their corresponding HTML
31
+ // entities. Unlike the `sanitize` option that is used by the rich-text serializer, it's safe
32
+ // for the plain-text serializer to escape the full input considering we need to output the full
33
+ // content as valid HTML (i.e. the editor should not drop invalid HTML).
34
+ let htmlResult = escape(markdown);
35
+ // Serialize all suggestion links if any suggestion node exists in the schema
36
+ Object.values(schema.nodes)
37
+ .filter((node) => node.name.endsWith('Suggestion'))
38
+ .forEach((suggestionNode) => {
39
+ const linkSchema = kebabCase(suggestionNode.name.replace(/Suggestion$/, ''));
40
+ htmlResult = htmlResult.replace(new RegExp(`\\[([^\\[]+)\\]\\((?:${linkSchema}):\\/\\/(\\d+)\\)`, 'gm'), `<span data-${linkSchema} data-id="$2" data-label="$1"></span>`);
41
+ });
42
+ // Return the serialized HTML with every line wrapped in a paragraph element
43
+ return htmlResult.replace(/^([^\n]+)\n?|\n+/gm, `<p>$1</p>`);
44
+ }
45
+ /**
46
+ * Serialize the Markdown input to HTML with Marked, ready for a rich-text editor.
47
+ *
48
+ * @param markdown The input Markdown to be serialized to HTML.
49
+ * @param schema The editor schema to be used for nodes and marks detection.
50
+ *
51
+ * @returns The serialized HTML output.
52
+ */
53
+ function serializeForRichTextEditor(markdown, schema) {
54
+ // Reset Marked to the defaults and set custom options
55
+ marked.setOptions({
56
+ ...marked.getDefaults(),
57
+ ...INITIAL_MARKED_OPTIONS,
58
+ });
59
+ // Disable built-in rules that the editor does not yet support
60
+ marked.use({
61
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
62
+ // @ts-ignore: Returning `undefined` is acceptable to disable tokens
63
+ tokenizer: {
64
+ ...(!schema.marks.strike
65
+ ? {
66
+ del() {
67
+ /* noop: disables tokenizer */
68
+ },
69
+ }
70
+ : {}),
71
+ ...(!schema.nodes.heading
72
+ ? {
73
+ heading() {
74
+ /* noop: disables tokenizer */
75
+ },
76
+ }
77
+ : {}),
78
+ ...(!schema.nodes.table
79
+ ? {
80
+ table() {
81
+ /* noop: disables tokenizer */
82
+ },
83
+ }
84
+ : {}),
85
+ },
86
+ });
87
+ // Overwrite some built-in rules for handling of special behaviours
88
+ // (see documentation for each extension for more details)
89
+ marked.use(checkbox, html, paragraph(schema.nodes.image));
90
+ // Overwrite the built-in `code` rule if the corresponding node exists in the schema
91
+ if (schema.nodes.codeBlock) {
92
+ marked.use(code);
93
+ }
94
+ // Add a rule for a task list if the corresponding nodes exists in the schema
95
+ if (schema.nodes.taskList && schema.nodes.taskItem) {
96
+ marked.use(taskList);
97
+ }
98
+ // Get all the available suggestion nodes from the schema
99
+ const suggestionNodes = Object.values(schema.nodes).filter((node) => node.name.endsWith('Suggestion'));
100
+ // Overwrite the built-in link rule if any suggestion node exists in the schema
101
+ if (suggestionNodes.length > 0) {
102
+ marked.use(link(suggestionNodes));
103
+ }
104
+ // Return the serialized HTML parsed with Marked
105
+ return (marked
106
+ .parse(markdown)
107
+ // Removes newlines after tags from the HTML output with a specially crafted RegExp
108
+ // (needed to prevent the editor from converting newlines to blank paragraphs)
109
+ .replace(new RegExp(`>${REGEX_LINE_BREAKS.source}`, REGEX_LINE_BREAKS.flags), '>'));
110
+ }
111
+ /**
112
+ * Create a Markdown to HTML serializer with the Marked library for a rich-text editor, or use a
113
+ * custom serializer for a plain-text editor. The editor schema is used to detect which nodes and
114
+ * marks are available in the editor, and only parses the input with the minimal required rules.
115
+ *
116
+ * @param schema The editor schema to be used for nodes and marks detection.
117
+ *
118
+ * @returns A normalized `serialize` function.
119
+ */
120
+ function createHTMLSerializer(schema) {
121
+ return {
122
+ serialize(markdown) {
123
+ return isPlainTextDocument(schema)
124
+ ? serializeForPlainTextEditor(markdown, schema)
125
+ : serializeForRichTextEditor(markdown, schema);
126
+ },
127
+ };
128
+ }
129
+ export { createHTMLSerializer };
@@ -0,0 +1,40 @@
1
+ import type { Schema } from 'prosemirror-model';
2
+ /**
3
+ * The options that the `createMarkdownSerializer` function accepts.
4
+ */
5
+ declare type MarkdownSerializerOptions = {
6
+ /**
7
+ * Disables markdown escaping.
8
+ */
9
+ escape?: false;
10
+ };
11
+ /**
12
+ * The return type for the `createMarkdownSerializer` function.
13
+ */
14
+ declare type MarkdownSerializerReturnType = {
15
+ /**
16
+ * Serializes an input HTML string to an output Markdown string.
17
+ *
18
+ * @param html The HTML string to serialize.
19
+ *
20
+ * @returns The serialized Markdown.
21
+ */
22
+ serialize: (html: string) => string;
23
+ };
24
+ /**
25
+ * The bullet list marker for both standard and task list items.
26
+ */
27
+ declare const BULLET_LIST_MARKER = "-";
28
+ /**
29
+ * Create an HTML to Markdown serializer with the Turndown library for both a rich-text editor, and
30
+ * a plain-text editor. The editor schema is used to detect which nodes and marks are available in
31
+ * the editor, and only parses the input with the minimal required rules.
32
+ *
33
+ * @param schema The editor schema to be used for nodes and marks detection.
34
+ *
35
+ * @returns A normalized `serialize` function.
36
+ */
37
+ declare function createMarkdownSerializer(schema: Schema, options?: MarkdownSerializerOptions): MarkdownSerializerReturnType;
38
+ export { BULLET_LIST_MARKER, createMarkdownSerializer };
39
+ export type { MarkdownSerializerReturnType };
40
+ //# sourceMappingURL=markdown.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"markdown.d.ts","sourceRoot":"","sources":["../../../src/serializers/markdown/markdown.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAA;AAE/C;;GAEG;AACH,aAAK,yBAAyB,GAAG;IAC7B;;OAEG;IACH,MAAM,CAAC,EAAE,KAAK,CAAA;CACjB,CAAA;AAED;;GAEG;AACH,aAAK,4BAA4B,GAAG;IAChC;;;;;;OAMG;IACH,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAA;CACtC,CAAA;AAED;;GAEG;AACH,QAAA,MAAM,kBAAkB,MAAM,CAAA;AA8C9B;;;;;;;;GAQG;AACH,iBAAS,wBAAwB,CAC7B,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE,yBAAyB,GACpC,4BAA4B,CA6E9B;AAED,OAAO,EAAE,kBAAkB,EAAE,wBAAwB,EAAE,CAAA;AAEvD,YAAY,EAAE,4BAA4B,EAAE,CAAA"}
@@ -0,0 +1,126 @@
1
+ import Turndown from 'turndown';
2
+ import { isPlainTextDocument } from '../../helpers/schema';
3
+ import { image } from './plugins/image';
4
+ import { listItem } from './plugins/list-item';
5
+ import { paragraph } from './plugins/paragraph';
6
+ import { strikethrough } from './plugins/strikethrough';
7
+ import { suggestion } from './plugins/suggestion';
8
+ import { taskItem } from './plugins/task-item';
9
+ /**
10
+ * The bullet list marker for both standard and task list items.
11
+ */
12
+ const BULLET_LIST_MARKER = '-';
13
+ /**
14
+ * Sensible default options to initialize the Turndown with.
15
+ *
16
+ * @see https://github.com/mixmark-io/turndown#options
17
+ */
18
+ const INITIAL_TURNDOWN_OPTIONS = {
19
+ headingStyle: 'atx',
20
+ hr: '---',
21
+ bulletListMarker: BULLET_LIST_MARKER,
22
+ codeBlockStyle: 'fenced',
23
+ fence: '```',
24
+ emDelimiter: '_',
25
+ strongDelimiter: '**',
26
+ linkStyle: 'inlined',
27
+ /**
28
+ * Special rule to handle blank elements (overrides EVERY rule).
29
+ *
30
+ * @see https://github.com/mixmark-io/turndown#special-rules
31
+ */
32
+ blankReplacement(_, node) {
33
+ const parentNode = node.parentNode;
34
+ // Return the list marker for empty bullet list items
35
+ if (node.nodeName === 'UL' || (parentNode?.nodeName === 'UL' && node.nodeName === 'LI')) {
36
+ return `${BULLET_LIST_MARKER} `;
37
+ }
38
+ // Return the list marker for empty ordered list items
39
+ if (node.nodeName === 'OL' || (parentNode?.nodeName === 'OL' && node.nodeName === 'LI')) {
40
+ const start = node.nodeName === 'LI'
41
+ ? parentNode.getAttribute('start')
42
+ : node.getAttribute('start');
43
+ const index = Array.prototype.indexOf.call(parentNode.children, node);
44
+ return `${start ? Number(start) + index : index + 1}. `;
45
+ }
46
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
47
+ // @ts-ignore: The `Turndown.Node` type does not include `isBlock`
48
+ return node.isBlock ? '\n\n' : '';
49
+ },
50
+ };
51
+ /**
52
+ * Create an HTML to Markdown serializer with the Turndown library for both a rich-text editor, and
53
+ * a plain-text editor. The editor schema is used to detect which nodes and marks are available in
54
+ * the editor, and only parses the input with the minimal required rules.
55
+ *
56
+ * @param schema The editor schema to be used for nodes and marks detection.
57
+ *
58
+ * @returns A normalized `serialize` function.
59
+ */
60
+ function createMarkdownSerializer(schema, options) {
61
+ // Initialize Turndown with custom options
62
+ const turndown = new Turndown(INITIAL_TURNDOWN_OPTIONS);
63
+ // Turndown ensures Markdown characters are escaped (i.e. `\`) by default, so they are not
64
+ // interpreted as Markdown when the output is compiled back to HTML. For example, the contents
65
+ // of `<h1>1. Hello world</h1>` need to be escaped to `1\. Hello world`, otherwise it will be
66
+ // interpreted as a list item rather than a heading. However, if the schema represents a
67
+ // plain-text document, we need to override the escape function to return the input as-is, so
68
+ // that Markdown characters are interpreted as Markdown.
69
+ // ref: https://github.com/mixmark-io/turndown#escaping-markdown-characters
70
+ if (isPlainTextDocument(schema) || options?.escape === false) {
71
+ turndown.escape = (str) => str;
72
+ }
73
+ // Overwrite the built-in `image` rule if the corresponding node exists in the schema
74
+ if (schema.nodes.image) {
75
+ turndown.use(image(schema.nodes.image));
76
+ }
77
+ // Overwrite the built-in `listItem` rule if the corresponding node exists in the schema
78
+ if (schema.nodes.listItem) {
79
+ turndown.use(listItem(schema.nodes.listItem));
80
+ }
81
+ // Overwrite the built-in `paragraph` rule if the corresponding node exists in the schema
82
+ if (schema.nodes.paragraph) {
83
+ turndown.use(paragraph(schema.nodes.paragraph, isPlainTextDocument(schema)));
84
+ }
85
+ // Add a rule for `strikethrough` if the corresponding node exists in the schema
86
+ if (schema.marks.strike) {
87
+ turndown.use(strikethrough(schema.marks.strike));
88
+ }
89
+ // Add a rule for `taskItem` if the corresponding nodes exists in the schema
90
+ if (schema.nodes.taskList && schema.nodes.taskItem) {
91
+ turndown.use(taskItem(schema.nodes.taskItem));
92
+ }
93
+ // Add a custom rule for all suggestion nodes available in the schema
94
+ Object.values(schema.nodes)
95
+ .filter((node) => node.name.endsWith('Suggestion'))
96
+ .forEach((suggestionNode) => {
97
+ turndown.use(suggestion(suggestionNode));
98
+ });
99
+ // Return a normalized `serialize` function
100
+ return {
101
+ serialize(html) {
102
+ let markdownResult = html;
103
+ // Turndown was built to convert HTML into Markdown, expecting the input to be
104
+ // standard-compliant HTML. As such, it collapses all whitespace by default, and there's
105
+ // currently no way to opt-out of this behavior. However, for plain-text editors, we
106
+ // need to preserve Markdown whitespace (otherwise we lose syntax like nested lists) by
107
+ // replacing all instances of the space character (but only if it's preceded by another
108
+ // space character) by the non-breaking space character, and after processing the input
109
+ // with Turndown, we restore the original space character.
110
+ if (isPlainTextDocument(schema)) {
111
+ markdownResult = markdownResult.replace(/ {2,}/g, (m) => m.replace(/ /g, '\u00a0'));
112
+ }
113
+ // Get the serialized Markdown parsed with Turndown
114
+ markdownResult = turndown.turndown(markdownResult);
115
+ // Restore the original space character for plain-text editors (as mentioned above),
116
+ // after Markdown serialization has been performed
117
+ if (isPlainTextDocument(schema)) {
118
+ markdownResult = markdownResult.replace(/\u00a0/g, ' ');
119
+ }
120
+ // Return the serialized Markdown parsed with Turndown, and with trailing space
121
+ // characters removed
122
+ return markdownResult.replace(/ +$/gm, '');
123
+ },
124
+ };
125
+ }
126
+ export { BULLET_LIST_MARKER, createMarkdownSerializer };
@@ -0,0 +1,12 @@
1
+ import type { NodeType } from 'prosemirror-model';
2
+ import type Turndown from 'turndown';
3
+ /**
4
+ * A Turndown plugin which adds a custom rule for images. This custom rule is required to disable
5
+ * support for Data URLs (URLs prefixed with the `data: scheme`), while displaying an explicit
6
+ * message in the Markdown output (for debugging and testing).
7
+ *
8
+ * @param nodeType The node object that matches this rule.
9
+ */
10
+ declare function image(nodeType: NodeType): Turndown.Plugin;
11
+ export { image };
12
+ //# sourceMappingURL=image.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"image.d.ts","sourceRoot":"","sources":["../../../../src/serializers/markdown/plugins/image.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAA;AACjD,OAAO,KAAK,QAAQ,MAAM,UAAU,CAAA;AAWpC;;;;;;GAMG;AACH,iBAAS,KAAK,CAAC,QAAQ,EAAE,QAAQ,GAAG,QAAQ,CAAC,MAAM,CAiBlD;AAED,OAAO,EAAE,KAAK,EAAE,CAAA"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Cleans an attribute value by replacing multiple newlines with a single one.
3
+ *
4
+ * @param attribute The attribute value to clean.
5
+ */
6
+ function cleanAttribute(attribute) {
7
+ return attribute ? attribute.replace(/(\n+\s*)+/g, '\n') : '';
8
+ }
9
+ /**
10
+ * A Turndown plugin which adds a custom rule for images. This custom rule is required to disable
11
+ * support for Data URLs (URLs prefixed with the `data: scheme`), while displaying an explicit
12
+ * message in the Markdown output (for debugging and testing).
13
+ *
14
+ * @param nodeType The node object that matches this rule.
15
+ */
16
+ function image(nodeType) {
17
+ return (turndown) => {
18
+ turndown.addRule(nodeType.name, {
19
+ filter: 'img',
20
+ replacement: function (_, node) {
21
+ const src = String(node.getAttribute('src'));
22
+ // Preserve Data URL image prefix with message about base64 being unsupported
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
+ export { image };
@@ -0,0 +1,14 @@
1
+ import type { NodeType } from 'prosemirror-model';
2
+ import type Turndown from 'turndown';
3
+ /**
4
+ * A Turndown plugin which adds a custom rule for standard list items (i.e., not task list items),
5
+ * based on the original list item rule. This custom rule is required to avoid conflicts with task
6
+ * list items, and to normalize the Markdown output.
7
+ *
8
+ * @see https://github.com/mixmark-io/turndown/blob/v7.1.1/src/commonmark-rules.js#L61
9
+ *
10
+ * @param nodeType The node object that matches this rule.
11
+ */
12
+ declare function listItem(nodeType: NodeType): Turndown.Plugin;
13
+ export { listItem };
14
+ //# sourceMappingURL=list-item.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"list-item.d.ts","sourceRoot":"","sources":["../../../../src/serializers/markdown/plugins/list-item.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAA;AACjD,OAAO,KAAK,QAAQ,MAAM,UAAU,CAAA;AAEpC;;;;;;;;GAQG;AACH,iBAAS,QAAQ,CAAC,QAAQ,EAAE,QAAQ,GAAG,QAAQ,CAAC,MAAM,CAqCrD;AAED,OAAO,EAAE,QAAQ,EAAE,CAAA"}
@@ -0,0 +1,41 @@
1
+ import { extractTagsFromParseRules } from '../../../helpers/serializer';
2
+ import { BULLET_LIST_MARKER } from '../markdown';
3
+ /**
4
+ * A Turndown plugin which adds a custom rule for standard list items (i.e., not task list items),
5
+ * based on the original list item rule. This custom rule is required to avoid conflicts with task
6
+ * list items, and to normalize the Markdown output.
7
+ *
8
+ * @see https://github.com/mixmark-io/turndown/blob/v7.1.1/src/commonmark-rules.js#L61
9
+ *
10
+ * @param nodeType The node object that matches this rule.
11
+ */
12
+ function listItem(nodeType) {
13
+ const tags = extractTagsFromParseRules(nodeType.spec.parseDOM);
14
+ return (turndown) => {
15
+ turndown.addRule(nodeType.name, {
16
+ filter: (node) => {
17
+ return (tags.some((tag) => tag.toUpperCase() === node.nodeName) &&
18
+ node.getAttribute('data-type') !== 'taskItem');
19
+ },
20
+ replacement: function (content, node) {
21
+ const parentNode = node.parentNode;
22
+ let listItemMarker = `${BULLET_LIST_MARKER} `;
23
+ // Use a sequence of 1–9 digits for the ordered list marker (CommonMark specification)
24
+ if (parentNode?.nodeName === 'OL') {
25
+ const start = parentNode.getAttribute('start');
26
+ const index = Array.prototype.indexOf.call(parentNode.children, node);
27
+ listItemMarker = `${start ? Number(start) + index : index + 1}. `;
28
+ }
29
+ const newContent = content
30
+ // Remove leading newlines
31
+ .replace(/^\n+/, '')
32
+ // Replace trailing newlines with a single one
33
+ .replace(/\n+$/, '\n')
34
+ // Indent list items with 4 spaces
35
+ .replace(/\n/gm, '\n ');
36
+ return `${listItemMarker}${newContent.trim()}${node.nextSibling && !newContent.endsWith('\n') ? '\n' : ''}`;
37
+ },
38
+ });
39
+ };
40
+ }
41
+ export { listItem };
@@ -0,0 +1,12 @@
1
+ import type { NodeType } from 'prosemirror-model';
2
+ import type Turndown from 'turndown';
3
+ /**
4
+ * A Turndown plugin which adds a custom rule for paragraphs. This custom rule is required to avoid
5
+ * adding unnecessary blank lines between paragraphs to plain-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
+ declare function paragraph(nodeType: NodeType, isPlainText: boolean): Turndown.Plugin;
11
+ export { paragraph };
12
+ //# sourceMappingURL=paragraph.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paragraph.d.ts","sourceRoot":"","sources":["../../../../src/serializers/markdown/plugins/paragraph.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAA;AACjD,OAAO,KAAK,QAAQ,MAAM,UAAU,CAAA;AAEpC;;;;;;GAMG;AACH,iBAAS,SAAS,CAAC,QAAQ,EAAE,QAAQ,EAAE,WAAW,EAAE,OAAO,GAAG,QAAQ,CAAC,MAAM,CAS5E;AAED,OAAO,EAAE,SAAS,EAAE,CAAA"}