@blue-patterns/richtext 0.0.0-stage → 0.7.18

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 (201) hide show
  1. package/CHANGELOG.md +275 -0
  2. package/LICENSE +22 -0
  3. package/README.md +88 -2
  4. package/astryx.integration.mjs +11 -0
  5. package/blocks/RichTextEditorShowcase.template.mjs +13 -0
  6. package/blocks/RichTextEditorShowcase.tsx +19 -0
  7. package/dist/RichTextEditor.d.ts +282 -0
  8. package/dist/RichTextEditor.d.ts.map +1 -0
  9. package/dist/RichTextEditor.js +657 -0
  10. package/dist/RichTextEditorAutoLinkPlugin.d.ts +83 -0
  11. package/dist/RichTextEditorAutoLinkPlugin.d.ts.map +1 -0
  12. package/dist/RichTextEditorAutoLinkPlugin.js +99 -0
  13. package/dist/RichTextEditorToolbar.d.ts +186 -0
  14. package/dist/RichTextEditorToolbar.d.ts.map +1 -0
  15. package/dist/RichTextEditorToolbar.js +688 -0
  16. package/dist/RichTextView.d.ts +92 -0
  17. package/dist/RichTextView.d.ts.map +1 -0
  18. package/dist/RichTextView.js +267 -0
  19. package/dist/codeLanguages.d.ts +14 -0
  20. package/dist/codeLanguages.d.ts.map +1 -0
  21. package/dist/codeLanguages.js +34 -0
  22. package/dist/codeLineTokens.d.ts +53 -0
  23. package/dist/codeLineTokens.d.ts.map +1 -0
  24. package/dist/codeLineTokens.js +188 -0
  25. package/dist/editorNodes.d.ts +8 -0
  26. package/dist/editorNodes.d.ts.map +1 -0
  27. package/dist/editorNodes.js +30 -0
  28. package/dist/editorTheme.d.ts +7 -0
  29. package/dist/editorTheme.d.ts.map +1 -0
  30. package/dist/editorTheme.js +328 -0
  31. package/dist/index.d.ts +31 -0
  32. package/dist/index.d.ts.map +1 -0
  33. package/dist/index.js +28 -0
  34. package/dist/internal/CodeBackspacePlugin.d.ts +2 -0
  35. package/dist/internal/CodeBackspacePlugin.d.ts.map +1 -0
  36. package/dist/internal/CodeBackspacePlugin.js +70 -0
  37. package/dist/internal/CodeBlockHeaderPlugin.d.ts +11 -0
  38. package/dist/internal/CodeBlockHeaderPlugin.d.ts.map +1 -0
  39. package/dist/internal/CodeBlockHeaderPlugin.js +204 -0
  40. package/dist/internal/CodePastePlugin.d.ts +5 -0
  41. package/dist/internal/CodePastePlugin.d.ts.map +1 -0
  42. package/dist/internal/CodePastePlugin.js +104 -0
  43. package/dist/internal/CodeSyntaxPlugin.d.ts +8 -0
  44. package/dist/internal/CodeSyntaxPlugin.d.ts.map +1 -0
  45. package/dist/internal/CodeSyntaxPlugin.js +397 -0
  46. package/dist/internal/CodeTabPlugin.d.ts +2 -0
  47. package/dist/internal/CodeTabPlugin.d.ts.map +1 -0
  48. package/dist/internal/CodeTabPlugin.js +256 -0
  49. package/dist/internal/DisabledLinkGuardPlugin.d.ts +4 -0
  50. package/dist/internal/DisabledLinkGuardPlugin.d.ts.map +1 -0
  51. package/dist/internal/DisabledLinkGuardPlugin.js +93 -0
  52. package/dist/internal/ImageSourceCaretPlugin.d.ts +3 -0
  53. package/dist/internal/ImageSourceCaretPlugin.d.ts.map +1 -0
  54. package/dist/internal/ImageSourceCaretPlugin.js +105 -0
  55. package/dist/internal/ListItemShortcutsPlugin.d.ts +6 -0
  56. package/dist/internal/ListItemShortcutsPlugin.d.ts.map +1 -0
  57. package/dist/internal/ListItemShortcutsPlugin.js +314 -0
  58. package/dist/internal/MarkdownExtensionsPlugin.d.ts +17 -0
  59. package/dist/internal/MarkdownExtensionsPlugin.d.ts.map +1 -0
  60. package/dist/internal/MarkdownExtensionsPlugin.js +185 -0
  61. package/dist/internal/SelectAllScopePlugin.d.ts +2 -0
  62. package/dist/internal/SelectAllScopePlugin.d.ts.map +1 -0
  63. package/dist/internal/SelectAllScopePlugin.js +67 -0
  64. package/dist/internal/TableColumnFloorPlugin.d.ts +12 -0
  65. package/dist/internal/TableColumnFloorPlugin.d.ts.map +1 -0
  66. package/dist/internal/TableColumnFloorPlugin.js +84 -0
  67. package/dist/internal/TablePastePlugin.d.ts +9 -0
  68. package/dist/internal/TablePastePlugin.d.ts.map +1 -0
  69. package/dist/internal/TablePastePlugin.js +183 -0
  70. package/dist/internal/TableScrollRegionPlugin.d.ts +9 -0
  71. package/dist/internal/TableScrollRegionPlugin.d.ts.map +1 -0
  72. package/dist/internal/TableScrollRegionPlugin.js +95 -0
  73. package/dist/internal/TaskCheckboxPlugin.d.ts +7 -0
  74. package/dist/internal/TaskCheckboxPlugin.d.ts.map +1 -0
  75. package/dist/internal/TaskCheckboxPlugin.js +193 -0
  76. package/dist/internal/ToolbarLinkPopover.d.ts +74 -0
  77. package/dist/internal/ToolbarLinkPopover.d.ts.map +1 -0
  78. package/dist/internal/ToolbarLinkPopover.js +292 -0
  79. package/dist/linkEditing.d.ts +6 -0
  80. package/dist/linkEditing.d.ts.map +1 -0
  81. package/dist/linkEditing.js +20 -0
  82. package/dist/linkForm.d.ts +28 -0
  83. package/dist/linkForm.d.ts.map +1 -0
  84. package/dist/linkForm.js +137 -0
  85. package/dist/linkUtils.d.ts +27 -0
  86. package/dist/linkUtils.d.ts.map +1 -0
  87. package/dist/linkUtils.js +101 -0
  88. package/dist/markdown.d.ts +17 -0
  89. package/dist/markdown.d.ts.map +1 -0
  90. package/dist/markdown.js +17 -0
  91. package/dist/markdownCharacterReferences.d.ts +135 -0
  92. package/dist/markdownCharacterReferences.d.ts.map +1 -0
  93. package/dist/markdownCharacterReferences.js +1127 -0
  94. package/dist/markdownCodeFence.d.ts +4 -0
  95. package/dist/markdownCodeFence.d.ts.map +1 -0
  96. package/dist/markdownCodeFence.js +167 -0
  97. package/dist/markdownExtensionNode.d.ts +88 -0
  98. package/dist/markdownExtensionNode.d.ts.map +1 -0
  99. package/dist/markdownExtensionNode.js +248 -0
  100. package/dist/markdownExtensions.d.ts +115 -0
  101. package/dist/markdownExtensions.d.ts.map +1 -0
  102. package/dist/markdownExtensions.js +392 -0
  103. package/dist/markdownHardLineBreak.d.ts +25 -0
  104. package/dist/markdownHardLineBreak.d.ts.map +1 -0
  105. package/dist/markdownHardLineBreak.js +59 -0
  106. package/dist/markdownHeading.d.ts +20 -0
  107. package/dist/markdownHeading.d.ts.map +1 -0
  108. package/dist/markdownHeading.js +30 -0
  109. package/dist/markdownLink.d.ts +5 -0
  110. package/dist/markdownLink.d.ts.map +1 -0
  111. package/dist/markdownLink.js +80 -0
  112. package/dist/markdownListExport.d.ts +3 -0
  113. package/dist/markdownListExport.d.ts.map +1 -0
  114. package/dist/markdownListExport.js +68 -0
  115. package/dist/markdownListIndentation.d.ts +8 -0
  116. package/dist/markdownListIndentation.d.ts.map +1 -0
  117. package/dist/markdownListIndentation.js +101 -0
  118. package/dist/markdownOrderedList.d.ts +3 -0
  119. package/dist/markdownOrderedList.d.ts.map +1 -0
  120. package/dist/markdownOrderedList.js +51 -0
  121. package/dist/markdownQuote.d.ts +22 -0
  122. package/dist/markdownQuote.d.ts.map +1 -0
  123. package/dist/markdownQuote.js +32 -0
  124. package/dist/markdownSerializers.d.ts +49 -0
  125. package/dist/markdownSerializers.d.ts.map +1 -0
  126. package/dist/markdownSerializers.js +83 -0
  127. package/dist/markdownSource.d.ts +71 -0
  128. package/dist/markdownSource.d.ts.map +1 -0
  129. package/dist/markdownSource.js +804 -0
  130. package/dist/markdownSpaceRuns.d.ts +24 -0
  131. package/dist/markdownSpaceRuns.d.ts.map +1 -0
  132. package/dist/markdownSpaceRuns.js +147 -0
  133. package/dist/markdownTable.d.ts +35 -0
  134. package/dist/markdownTable.d.ts.map +1 -0
  135. package/dist/markdownTable.js +227 -0
  136. package/dist/markdownTaskList.d.ts +48 -0
  137. package/dist/markdownTaskList.d.ts.map +1 -0
  138. package/dist/markdownTaskList.js +166 -0
  139. package/dist/markdownThematicBreak.d.ts +3 -0
  140. package/dist/markdownThematicBreak.d.ts.map +1 -0
  141. package/dist/markdownThematicBreak.js +55 -0
  142. package/dist/privateAttributes/layerPortalBoundary.stylex.d.ts +12 -0
  143. package/dist/privateAttributes/layerPortalBoundary.stylex.d.ts.map +1 -0
  144. package/dist/privateAttributes/layerPortalBoundary.stylex.js +16 -0
  145. package/dist/richtext.css +213 -0
  146. package/dist/textSemantics.d.ts +3 -0
  147. package/dist/textSemantics.d.ts.map +1 -0
  148. package/dist/textSemantics.js +87 -0
  149. package/package.json +158 -3
  150. package/src/RichTextEditor.doc.mjs +308 -0
  151. package/src/RichTextEditor.tsx +1109 -0
  152. package/src/RichTextEditorAutoLinkPlugin.doc.mjs +43 -0
  153. package/src/RichTextEditorAutoLinkPlugin.tsx +126 -0
  154. package/src/RichTextEditorToolbar.doc.mjs +92 -0
  155. package/src/RichTextEditorToolbar.tsx +871 -0
  156. package/src/RichTextView.doc.mjs +101 -0
  157. package/src/RichTextView.tsx +351 -0
  158. package/src/codeLanguages.ts +42 -0
  159. package/src/codeLineTokens.ts +241 -0
  160. package/src/doc-mjs.d.ts +23 -0
  161. package/src/editorNodes.ts +44 -0
  162. package/src/editorTheme.ts +550 -0
  163. package/src/index.ts +59 -0
  164. package/src/internal/CodeBackspacePlugin.tsx +96 -0
  165. package/src/internal/CodeBlockHeaderPlugin.tsx +282 -0
  166. package/src/internal/CodePastePlugin.tsx +144 -0
  167. package/src/internal/CodeSyntaxPlugin.tsx +493 -0
  168. package/src/internal/CodeTabPlugin.tsx +298 -0
  169. package/src/internal/DisabledLinkGuardPlugin.tsx +93 -0
  170. package/src/internal/ImageSourceCaretPlugin.tsx +159 -0
  171. package/src/internal/ListItemShortcutsPlugin.tsx +404 -0
  172. package/src/internal/MarkdownExtensionsPlugin.tsx +289 -0
  173. package/src/internal/SelectAllScopePlugin.tsx +86 -0
  174. package/src/internal/TableColumnFloorPlugin.tsx +99 -0
  175. package/src/internal/TablePastePlugin.tsx +223 -0
  176. package/src/internal/TableScrollRegionPlugin.tsx +107 -0
  177. package/src/internal/TaskCheckboxPlugin.tsx +246 -0
  178. package/src/internal/ToolbarLinkPopover.tsx +392 -0
  179. package/src/linkEditing.ts +20 -0
  180. package/src/linkForm.tsx +156 -0
  181. package/src/linkUtils.ts +106 -0
  182. package/src/markdown.ts +25 -0
  183. package/src/markdownCharacterReferences.ts +1137 -0
  184. package/src/markdownCodeFence.ts +216 -0
  185. package/src/markdownExtensionNode.ts +339 -0
  186. package/src/markdownExtensions.ts +496 -0
  187. package/src/markdownHardLineBreak.ts +73 -0
  188. package/src/markdownHeading.ts +33 -0
  189. package/src/markdownLink.ts +86 -0
  190. package/src/markdownListExport.ts +85 -0
  191. package/src/markdownListIndentation.ts +105 -0
  192. package/src/markdownOrderedList.ts +60 -0
  193. package/src/markdownQuote.ts +35 -0
  194. package/src/markdownSerializers.ts +108 -0
  195. package/src/markdownSource.ts +1045 -0
  196. package/src/markdownSpaceRuns.ts +164 -0
  197. package/src/markdownTable.ts +309 -0
  198. package/src/markdownTaskList.ts +222 -0
  199. package/src/markdownThematicBreak.ts +66 -0
  200. package/src/privateAttributes/layerPortalBoundary.stylex.ts +18 -0
  201. package/src/textSemantics.ts +104 -0
@@ -0,0 +1,137 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ 'use client';
4
+
5
+ /**
6
+ * @file linkForm.tsx
7
+ * @input The address being entered, a refused-address message, and the
8
+ * caller's apply, remove, and cancel handlers
9
+ * @output Exports LinkForm, the link URL form, and REMOVE_LINK_GLYPH
10
+ * @position Internal to RichText. RichTextEditorToolbar's link popover
11
+ * renders this form, so adding and editing a link share one form.
12
+ */
13
+ import { useRef } from 'react';
14
+ import { IconButton } from '@blue-patterns/core/IconButton';
15
+ import { TextInput } from '@blue-patterns/core/TextInput';
16
+ import { HStack, StackItem } from '@blue-patterns/core/Layout';
17
+ import { Icon } from '@blue-patterns/core/Icon';
18
+ import { isImeKeyEvent } from '@blue-patterns/core/utils';
19
+ import { useTranslator } from '@blue-patterns/core/i18n';
20
+
21
+ /**
22
+ * Remove's glyph. No shared icon name means "unlink", so it is fixed
23
+ * component artwork until component icon slots exist.
24
+ */
25
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
26
+ export const REMOVE_LINK_GLYPH = /*#__PURE__*/_jsx("svg", {
27
+ width: "16",
28
+ height: "16",
29
+ viewBox: "0 0 24 24",
30
+ fill: "none",
31
+ "aria-hidden": "true",
32
+ children: /*#__PURE__*/_jsx("path", {
33
+ d: "M18 13l2-2a5 5 0 0 0-7-7l-2 2M6 11l-2 2a5 5 0 0 0 7 7l2-2M8 2v3M2 8h3M16 22v-3M22 16h-3",
34
+ stroke: "currentColor",
35
+ strokeWidth: "2",
36
+ strokeLinecap: "round",
37
+ strokeLinejoin: "round"
38
+ })
39
+ });
40
+ /**
41
+ * The link form: one URL field, then Apply, Remove (only when editing a
42
+ * link), and Cancel as icon buttons. Enter in the field
43
+ * applies, and Escape anywhere in the form cancels. A refused address shows
44
+ * the field's error status and keeps focus in the field.
45
+ */
46
+ export function LinkForm({
47
+ url,
48
+ error,
49
+ isEditing,
50
+ size,
51
+ onUrlChange,
52
+ onApply,
53
+ onRemove,
54
+ onCancel
55
+ }) {
56
+ const t = useTranslator();
57
+ const inputRef = useRef(null);
58
+ const apply = () => {
59
+ if (!onApply()) {
60
+ inputRef.current?.focus();
61
+ }
62
+ };
63
+ const applyLabel = isEditing ? t('@astryx.richTextEditor.link.update') : t('@astryx.richTextEditor.link.add');
64
+ const removeLabel = t('@astryx.richTextEditor.link.remove');
65
+ const cancelLabel = t('@astryx.richTextEditor.link.cancel');
66
+ return /*#__PURE__*/_jsxs(HStack, {
67
+ gap: 1,
68
+ vAlign: "start",
69
+ onKeyDown: event => {
70
+ // Escape that ends an IME composition is the input method's.
71
+ if (event.key === 'Escape' && !isImeKeyEvent(event.nativeEvent)) {
72
+ // Claimed here, so a surrounding layer's Escape handling leaves it
73
+ // to Cancel, which also gives the editor back its selection.
74
+ event.preventDefault();
75
+ onCancel();
76
+ }
77
+ },
78
+ children: [/*#__PURE__*/_jsx(StackItem, {
79
+ size: "fill",
80
+ children: /*#__PURE__*/_jsx(TextInput, {
81
+ ref: inputRef,
82
+ label: t('@astryx.richTextEditor.link.url'),
83
+ isLabelHidden: true,
84
+ value: url,
85
+ size: size,
86
+ width: "100%",
87
+ autoComplete: "url"
88
+ // A URL keyboard on touch screens and no spelling marks under an
89
+ // address. The field stays plain text, so a bare domain reaches
90
+ // the link rules (sanitizeUrl), which make it an https link.
91
+ ,
92
+ inputMode: "url",
93
+ hasSpellCheck: false,
94
+ placeholder: t('@astryx.richTextEditor.link.urlPlaceholder'),
95
+ status: error ? {
96
+ type: 'error',
97
+ message: error
98
+ } : undefined,
99
+ statusVariant: "detached",
100
+ onChange: onUrlChange,
101
+ onEnter: apply,
102
+ onKeyDown: event => {
103
+ // Applying hands focus to the editor, which must not receive the
104
+ // rest of this Enter as a new line over the linked words.
105
+ if (event.key === 'Enter' && !isImeKeyEvent(event.nativeEvent)) {
106
+ event.preventDefault();
107
+ }
108
+ }
109
+ })
110
+ }), /*#__PURE__*/_jsx(IconButton, {
111
+ label: applyLabel,
112
+ icon: /*#__PURE__*/_jsx(Icon, {
113
+ icon: "check"
114
+ }),
115
+ variant: "ghost",
116
+ size: size,
117
+ tooltip: applyLabel,
118
+ onClick: apply
119
+ }), isEditing && /*#__PURE__*/_jsx(IconButton, {
120
+ label: removeLabel,
121
+ icon: REMOVE_LINK_GLYPH,
122
+ variant: "ghost",
123
+ size: size,
124
+ tooltip: removeLabel,
125
+ onClick: onRemove
126
+ }), /*#__PURE__*/_jsx(IconButton, {
127
+ label: cancelLabel,
128
+ icon: /*#__PURE__*/_jsx(Icon, {
129
+ icon: "close"
130
+ }),
131
+ variant: "ghost",
132
+ size: size,
133
+ tooltip: cancelLabel,
134
+ onClick: onCancel
135
+ })]
136
+ });
137
+ }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Matches a bare URL for AutoLink. Adapted from the Lexical playground.
3
+ * Intentionally permissive on path/query so real-world URLs linkify.
4
+ */
5
+ export declare const URL_MATCHER: RegExp;
6
+ /**
7
+ * Matches a bare email address for AutoLink (rendered as a `mailto:` link).
8
+ * An unquoted local part starts only at the text start, after whitespace, or
9
+ * after one of `<>()[]\,;:@"`. A quoted local part ends at the next quote or
10
+ * line break. Each run is read once, so matching time stays linear.
11
+ */
12
+ export declare const EMAIL_MATCHER: RegExp;
13
+ /**
14
+ * Return a safe href for `url`, or `'about:blank'` if it can't be parsed or uses
15
+ * a disallowed scheme. A scheme-less input like `example.com` is treated as
16
+ * `https://example.com`. Use this before writing any user-supplied string into
17
+ * a link's href.
18
+ */
19
+ export declare function sanitizeUrl(url: string): string;
20
+ /**
21
+ * Whether `url` is a well-formed, safe (http/https/mailto/tel) URL. Used to
22
+ * decide whether an entered link value is acceptable before dispatching the
23
+ * toggle-link command. Shares the {@link SAFE_PROTOCOLS} allowlist with
24
+ * {@link sanitizeUrl} so the two never disagree.
25
+ */
26
+ export declare function validateUrl(url: string): boolean;
27
+ //# sourceMappingURL=linkUtils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"linkUtils.d.ts","sourceRoot":"","sources":["../src/linkUtils.ts"],"names":[],"mappings":"AAoCA;;;GAGG;AACH,eAAO,MAAM,WAAW,QAC0F,CAAC;AAEnH;;;;;GAKG;AACH,eAAO,MAAM,aAAa,QACyJ,CAAC;AAgBpL;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAc/C;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAWhD"}
@@ -0,0 +1,101 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file linkUtils.ts
5
+ * @input Uses only the DOM URL constructor (guarded via try/catch for SSR/edge).
6
+ * @output Exports `sanitizeUrl`, `validateUrl`, and the URL/email matcher regexes
7
+ * used by the AutoLink wiring.
8
+ * @position @blue-patterns/richtext helper shared by RichTextEditorToolbar (link
9
+ * button) and the AutoLink matchers in RichTextEditor. Kept dependency-free so
10
+ * both can import it without pulling in extra `@lexical/*` surface.
11
+ *
12
+ * SYNC: When modified, update:
13
+ * - /packages/richtext/src/RichTextEditorToolbar.tsx (link button)
14
+ * - /packages/richtext/src/RichTextEditor.tsx (AutoLink matchers)
15
+ * - /packages/richtext/src/RichTextEditor.test.tsx (tests)
16
+ * - /packages/richtext/src/RichTextEditor.doc.mjs (link docs)
17
+ * - /packages/richtext/src/index.ts (exports / barrel)
18
+ *
19
+ * NOTE: Mirrors the Lexical playground's url.ts (`sanitizeUrl` / `validateUrl`
20
+ * / `URL_REGEX` / `EMAIL_REGEX`). Sanitization is the security-critical part:
21
+ * it blocks `javascript:`, `data:`, and other non-http(s)/mailto/tel schemes
22
+ * from being written into a LinkNode's href, so an inserted link can never
23
+ * smuggle in an executable URL.
24
+ */
25
+
26
+ /**
27
+ * Schemes a link is allowed to carry. Anything else (notably `javascript:`,
28
+ * `data:`, `vbscript:`) is rejected by {@link validateUrl} and coerced to
29
+ * `about:blank` by {@link sanitizeUrl}, so a malicious paste can't produce a
30
+ * clickable code-execution link.
31
+ */
32
+ const SAFE_PROTOCOLS = new Set(['http:', 'https:', 'mailto:', 'tel:']);
33
+
34
+ /** Matches a scheme prefix like `https:` / `mailto:` at the start of a string. */
35
+ const HAS_SCHEME = /^[a-z][a-z0-9+.-]*:/i;
36
+
37
+ /**
38
+ * Matches a bare URL for AutoLink. Adapted from the Lexical playground.
39
+ * Intentionally permissive on path/query so real-world URLs linkify.
40
+ */
41
+ export const URL_MATCHER = /((https?:\/\/(www\.)?)|(www\.))[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&/=]*)/;
42
+
43
+ /**
44
+ * Matches a bare email address for AutoLink (rendered as a `mailto:` link).
45
+ * An unquoted local part starts only at the text start, after whitespace, or
46
+ * after one of `<>()[]\,;:@"`. A quoted local part ends at the next quote or
47
+ * line break. Each run is read once, so matching time stays linear.
48
+ */
49
+ export const EMAIL_MATCHER = /((?<![^<>()[\]\\,;:\s@"])[^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*|("[^"\r\n]+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))/;
50
+
51
+ /**
52
+ * A host with a port also looks like a scheme (`example.com:`).
53
+ * The host needs a dot or is `localhost`, so `tel:911` keeps its scheme.
54
+ */
55
+ const HOST_WITH_PORT = /^(?:localhost|[a-z0-9-]+(?:\.[a-z0-9-]+)+):\d{1,5}(?:[/?#]|$)/i;
56
+
57
+ /** Prepend a default `https://` scheme to a bare host so it parses as absolute. */
58
+ function withDefaultScheme(url) {
59
+ return HAS_SCHEME.test(url) && !HOST_WITH_PORT.test(url) ? url : `https://${url}`;
60
+ }
61
+
62
+ /**
63
+ * Return a safe href for `url`, or `'about:blank'` if it can't be parsed or uses
64
+ * a disallowed scheme. A scheme-less input like `example.com` is treated as
65
+ * `https://example.com`. Use this before writing any user-supplied string into
66
+ * a link's href.
67
+ */
68
+ export function sanitizeUrl(url) {
69
+ const trimmed = url.trim();
70
+ if (trimmed === '') {
71
+ return 'about:blank';
72
+ }
73
+ try {
74
+ const parsed = new URL(withDefaultScheme(trimmed));
75
+ if (!SAFE_PROTOCOLS.has(parsed.protocol.toLowerCase())) {
76
+ return 'about:blank';
77
+ }
78
+ return parsed.href;
79
+ } catch {
80
+ return 'about:blank';
81
+ }
82
+ }
83
+
84
+ /**
85
+ * Whether `url` is a well-formed, safe (http/https/mailto/tel) URL. Used to
86
+ * decide whether an entered link value is acceptable before dispatching the
87
+ * toggle-link command. Shares the {@link SAFE_PROTOCOLS} allowlist with
88
+ * {@link sanitizeUrl} so the two never disagree.
89
+ */
90
+ export function validateUrl(url) {
91
+ const trimmed = url.trim();
92
+ if (trimmed === '') {
93
+ return false;
94
+ }
95
+ try {
96
+ const parsed = new URL(withDefaultScheme(trimmed));
97
+ return SAFE_PROTOCOLS.has(parsed.protocol.toLowerCase());
98
+ } catch {
99
+ return false;
100
+ }
101
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * @file markdown.ts
3
+ * @output Server-safe entry `@blue-patterns/richtext/markdown`: the Markdown
4
+ * serializers, createRichTextExtension, RichTextExtensionError, and their
5
+ * types.
6
+ * @position Imports no React, DOM, or client-only module, so server and Node
7
+ * code converts Markdown and adopts plugins here (spec:AST-064 FR3, FR12).
8
+ * The package's main entry is a client module and re-exports all of this
9
+ * for client code. An extension holds its plugin's functions, so it is not
10
+ * serializable: create the extensions a page passes to the surfaces in a
11
+ * client module, and create your own for headless use.
12
+ */
13
+ export { markdownToEditorStateJSON, editorStateJSONToMarkdown, } from './markdownSerializers.js';
14
+ export type { MarkdownSerializerOptions } from './markdownSerializers.js';
15
+ export { createRichTextExtension, RichTextExtensionError, } from './markdownExtensions.js';
16
+ export type { RichTextMarkdownExtension } from './markdownExtensions.js';
17
+ //# sourceMappingURL=markdown.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"markdown.d.ts","sourceRoot":"","sources":["../src/markdown.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;GAWG;AAEH,OAAO,EACL,yBAAyB,EACzB,yBAAyB,GAC1B,MAAM,uBAAuB,CAAC;AAC/B,YAAY,EAAC,yBAAyB,EAAC,MAAM,uBAAuB,CAAC;AACrE,OAAO,EACL,uBAAuB,EACvB,sBAAsB,GACvB,MAAM,sBAAsB,CAAC;AAC9B,YAAY,EAAC,yBAAyB,EAAC,MAAM,sBAAsB,CAAC"}
@@ -0,0 +1,17 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file markdown.ts
5
+ * @output Server-safe entry `@blue-patterns/richtext/markdown`: the Markdown
6
+ * serializers, createRichTextExtension, RichTextExtensionError, and their
7
+ * types.
8
+ * @position Imports no React, DOM, or client-only module, so server and Node
9
+ * code converts Markdown and adopts plugins here (spec:AST-064 FR3, FR12).
10
+ * The package's main entry is a client module and re-exports all of this
11
+ * for client code. An extension holds its plugin's functions, so it is not
12
+ * serializable: create the extensions a page passes to the surfaces in a
13
+ * client module, and create your own for headless use.
14
+ */
15
+
16
+ export { markdownToEditorStateJSON, editorStateJSONToMarkdown } from "./markdownSerializers.js";
17
+ export { createRichTextExtension, RichTextExtensionError } from "./markdownExtensions.js";
@@ -0,0 +1,135 @@
1
+ import { type ElementNode, type LexicalNode } from 'lexical';
2
+ /**
3
+ * What a stand-in replaced: a reference as written, an escaped `&`, or a
4
+ * backslash-escaped character.
5
+ */
6
+ type StandIn = {
7
+ readonly kind: 'reference';
8
+ readonly source: string;
9
+ } | {
10
+ readonly kind: 'ampersand';
11
+ } | {
12
+ readonly kind: 'escape';
13
+ readonly character: string;
14
+ } | {
15
+ readonly kind: 'backslash';
16
+ } | {
17
+ readonly kind: 'literal';
18
+ readonly text: string;
19
+ };
20
+ export interface ProtectedMarkdown {
21
+ /** The source with stand-ins in place of references. */
22
+ readonly markdown: string;
23
+ /** What each stand-in replaced. */
24
+ readonly standIns: ReadonlyMap<string, StandIn>;
25
+ }
26
+ /** Private-use characters that do not occur in `text`, in order. */
27
+ export declare function absentCharacters(text: string): Generator<string>;
28
+ /**
29
+ * Returns `markdown` with every backslash escape outside code — a backslash
30
+ * before ASCII punctuation — replaced by a private-use stand-in for the
31
+ * escaped character, and every backslash in code by a stand-in for itself,
32
+ * which Lexical would otherwise drop from a code span — except before `|`,
33
+ * which a table reads before its code. A backslash before anything else
34
+ * outside code stays as written. One pass over the source.
35
+ */
36
+ export declare function protectBackslashEscapes(markdown: string): ProtectedMarkdown;
37
+ /**
38
+ * Whether core Markdown refuses a link to `url`, a destination already
39
+ * decoded: the probe writes it back with every special character escaped.
40
+ * A URL that cannot be written back is refused.
41
+ */
42
+ export declare function coreRefusesUrl(url: string): boolean;
43
+ /**
44
+ * Returns `markdown` with each inline link whose destination core Markdown
45
+ * refuses — `javascript:` and the like — kept from becoming a link
46
+ * (spec:AST-061 FR7). Where core shows the link as its source text, exactly
47
+ * as written, the whole source becomes one stand-in that comes back as that
48
+ * text. Where core reads no link there at all (its link text holds brackets
49
+ * of its own), only the `(` that opens the destination becomes a stand-in,
50
+ * so the text around it reads as core reads it, marks included. Brackets and
51
+ * parentheses pair by nesting, so link text holding brackets is found whole.
52
+ * Linear in the source: pairs come from one pass, and a destination inside a
53
+ * found link is not probed again. Run first, on the source as written;
54
+ * `$unwrapRefusedLinks` catches any link the editor still makes.
55
+ */
56
+ export declare function protectRefusedLinks(markdown: string): ProtectedMarkdown;
57
+ /** Whether `node` is text that holds an image's Markdown source. */
58
+ export declare function $isImageSource(node: LexicalNode): boolean;
59
+ /**
60
+ * Returns `markdown` with every image outside code replaced by a private-use
61
+ * stand-in, and the source each stand-in replaced. An image starts with a `!`
62
+ * that is not escaped and the paired brackets of its text. An inline image
63
+ * (`![a](b)`) ends with the destination and title that close it, read whole
64
+ * by core's inline parser. A reference image (`![a][b]`, `![a][]`, `![a]`)
65
+ * ends with its label; its definition can sit in another block, so it is kept
66
+ * whether or not one exists. A link around an image keeps its own brackets,
67
+ * so the image stays inside the link's text.
68
+ */
69
+ export declare function protectImages(markdown: string): {
70
+ readonly markdown: string;
71
+ readonly images: ReadonlyMap<string, string>;
72
+ };
73
+ /**
74
+ * Puts each image's source back where its stand-in sits under `node`, as
75
+ * text marked as an image's source. The text is a token: a person deletes it
76
+ * whole, and autolinking leaves its URL alone. ImageSourceCaretPlugin keeps
77
+ * the caret out of it, so typing never replaces it.
78
+ */
79
+ export declare function $restoreImages(node: ElementNode, images: ReadonlyMap<string, string>): void;
80
+ /**
81
+ * Takes every link under `node` whose URL core Markdown refuses out of its
82
+ * link, leaving its text in place, so no form of link text or destination
83
+ * the source pass did not find can store or draw an unsafe URL.
84
+ */
85
+ export declare function $unwrapRefusedLinks(node: ElementNode): void;
86
+ /**
87
+ * Returns `markdown` with every parenthesis inside an inline link
88
+ * destination — between the destination's own parentheses, balanced, as core
89
+ * Markdown and CommonMark read them — replaced by a private-use stand-in for
90
+ * itself, outside code. Run after protectBackslashEscapes, so an escaped
91
+ * parenthesis is already a stand-in and never counts toward the balance.
92
+ */
93
+ export declare function protectLinkDestinationParentheses(markdown: string): ProtectedMarkdown;
94
+ /** The code spans a stand-in pass replaced: each stand-in's code. */
95
+ export interface ProtectedCodeSpans {
96
+ readonly markdown: string;
97
+ readonly spans: ReadonlyMap<string, string>;
98
+ /** Stand-ins for backticks in link destinations and titles. */
99
+ readonly standIns: ReadonlyMap<string, StandIn>;
100
+ }
101
+ /**
102
+ * Returns `markdown` with each code span outside fenced code replaced by a
103
+ * private-use stand-in, and the code each stands for, as core Markdown reads
104
+ * it. Code spans bind tighter than emphasis and links (CommonMark 0.31
105
+ * §6.1), but Lexical's import applies a code span before an earlier mark on
106
+ * the same line, which breaks every mark around code that follows another
107
+ * (`~~a~~ ~~`c`~~`); with the code out of the way, the marks pair first and
108
+ * the code comes back inside them. Backticks in a link's destination or
109
+ * title are the link's, never a code span: they become stand-ins too, put
110
+ * back into the link's address and title after import, so Lexical cannot
111
+ * pair them into code either. In a table row, a span holding an unescaped
112
+ * `|` is left alone, so the row still splits its cells there, as GFM does.
113
+ */
114
+ export declare function protectCodeSpans(markdown: string): ProtectedCodeSpans;
115
+ /**
116
+ * Puts each code span back where its stand-in sits under `node`: the code as
117
+ * text with the code format, inside whatever marks the stand-in took. In a
118
+ * table cell an escaped `\|` in code is a `|`, as GFM reads cells.
119
+ */
120
+ export declare function $restoreCodeSpans(node: ElementNode, spans: ReadonlyMap<string, string>): void;
121
+ /**
122
+ * Returns `markdown` with every character reference outside code replaced by
123
+ * a private-use stand-in that occurs nowhere else in it, and the escaped `&`
124
+ * of an escaped reference replaced by one too. One pass over the source.
125
+ */
126
+ export declare function protectCharacterReferences(markdown: string): ProtectedMarkdown;
127
+ /**
128
+ * Replaces the stand-ins under `node` with the characters their references
129
+ * name or their escapes stand for — or, in code Lexical read as code, with
130
+ * the references and escapes as written — in text, link destinations, and
131
+ * link titles. One pass over the text.
132
+ */
133
+ export declare function $restoreCharacterReferences(node: ElementNode, standIns: ReadonlyMap<string, StandIn>): void;
134
+ export {};
135
+ //# sourceMappingURL=markdownCharacterReferences.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"markdownCharacterReferences.d.ts","sourceRoot":"","sources":["../src/markdownCharacterReferences.ts"],"names":[],"mappings":"AAiCA,OAAO,EAML,KAAK,WAAW,EAChB,KAAK,WAAW,EACjB,MAAM,SAAS,CAAC;AAMjB;;;GAGG;AACH,KAAK,OAAO,GACR;IAAC,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAC,GACrD;IAAC,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAA;CAAC,GAC5B;IAAC,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IAAC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;CAAC,GACrD;IAAC,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAA;CAAC,GAC5B;IAAC,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAC,CAAC;AAEtD,MAAM,WAAW,iBAAiB;IAChC,wDAAwD;IACxD,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,mCAAmC;IACnC,QAAQ,CAAC,QAAQ,EAAE,WAAW,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACjD;AAYD,oEAAoE;AACpE,wBAAiB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC,MAAM,CAAC,CAUjE;AAkID;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,MAAM,GAAG,iBAAiB,CA2D3E;AAkHD;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAUnD;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,iBAAiB,CA8DvE;AAUD,oEAAoE;AACpE,wBAAgB,cAAc,CAAC,IAAI,EAAE,WAAW,GAAG,OAAO,CAEzD;AAED;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG;IAC/C,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC9C,CA+DA;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAC5B,IAAI,EAAE,WAAW,EACjB,MAAM,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,GAClC,IAAI,CA8BN;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,WAAW,GAAG,IAAI,CAS3D;AAED;;;;;;GAMG;AACH,wBAAgB,iCAAiC,CAC/C,QAAQ,EAAE,MAAM,GACf,iBAAiB,CA4EnB;AAED,qEAAqE;AACrE,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC5C,+DAA+D;IAC/D,QAAQ,CAAC,QAAQ,EAAE,WAAW,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACjD;AA2FD;;;;;;;;;;;;GAYG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,kBAAkB,CAwErE;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,WAAW,EACjB,KAAK,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,GACjC,IAAI,CAgCN;AAED;;;;GAIG;AACH,wBAAgB,0BAA0B,CACxC,QAAQ,EAAE,MAAM,GACf,iBAAiB,CA+EnB;AAmDD;;;;;GAKG;AACH,wBAAgB,2BAA2B,CACzC,IAAI,EAAE,WAAW,EACjB,QAAQ,EAAE,WAAW,CAAC,MAAM,EAAE,OAAO,CAAC,GACrC,IAAI,CAsCN"}