@eventuras/scribo 0.8.3 → 0.10.7

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 (164) hide show
  1. package/dist/App.d.ts +11 -0
  2. package/dist/App.d.ts.map +1 -0
  3. package/dist/MarkdownEditor.d.ts +18 -0
  4. package/dist/MarkdownEditor.d.ts.map +1 -0
  5. package/dist/MarkdownEditor2.js +111 -0
  6. package/dist/MarkdownEditor2.js.map +1 -0
  7. package/dist/MarkdownInput.d.ts +27 -0
  8. package/dist/MarkdownInput.d.ts.map +1 -0
  9. package/dist/MarkdownInput.js +57 -0
  10. package/dist/MarkdownInput.js.map +1 -0
  11. package/dist/callout.js +5 -0
  12. package/dist/context/SharedHistoryContext.d.ts +11 -0
  13. package/dist/context/SharedHistoryContext.d.ts.map +1 -0
  14. package/dist/context/SharedHistoryContext.js +12 -0
  15. package/dist/context/SharedHistoryContext.js.map +1 -0
  16. package/dist/index.d.ts +1 -0
  17. package/dist/index.d.ts.map +1 -0
  18. package/dist/main.d.ts +5 -0
  19. package/dist/main.d.ts.map +1 -0
  20. package/dist/main.js +4 -0
  21. package/dist/nodes/EditorNodes.d.ts +4 -0
  22. package/dist/nodes/EditorNodes.d.ts.map +1 -0
  23. package/dist/nodes/EditorNodes.js +39 -0
  24. package/dist/nodes/EditorNodes.js.map +1 -0
  25. package/dist/nodes/callout/CalloutComponent.d.ts +6 -0
  26. package/dist/nodes/callout/CalloutComponent.d.ts.map +1 -0
  27. package/dist/nodes/callout/CalloutComponent.js +53 -0
  28. package/dist/nodes/callout/CalloutComponent.js.map +1 -0
  29. package/dist/nodes/callout/CalloutNode.d.ts +27 -0
  30. package/dist/nodes/callout/CalloutNode.d.ts.map +1 -0
  31. package/dist/nodes/callout/CalloutNode.js +153 -0
  32. package/dist/nodes/callout/CalloutNode.js.map +1 -0
  33. package/dist/nodes/callout/CalloutTransformer.d.ts +3 -0
  34. package/dist/nodes/callout/CalloutTransformer.d.ts.map +1 -0
  35. package/dist/nodes/callout/CalloutTransformer.js +48 -0
  36. package/dist/nodes/callout/CalloutTransformer.js.map +1 -0
  37. package/dist/nodes/callout/calloutPlugin.d.ts +3 -0
  38. package/dist/nodes/callout/calloutPlugin.d.ts.map +1 -0
  39. package/dist/nodes/callout/calloutPlugin.js +34 -0
  40. package/dist/nodes/callout/calloutPlugin.js.map +1 -0
  41. package/dist/nodes/callout/index.d.ts +6 -0
  42. package/dist/nodes/callout/index.d.ts.map +1 -0
  43. package/dist/nodes/schedule/ScheduleItemComponent.d.ts +9 -0
  44. package/dist/nodes/schedule/ScheduleItemComponent.d.ts.map +1 -0
  45. package/dist/nodes/schedule/ScheduleItemComponent.js +167 -0
  46. package/dist/nodes/schedule/ScheduleItemComponent.js.map +1 -0
  47. package/dist/nodes/schedule/ScheduleItemNode.d.ts +31 -0
  48. package/dist/nodes/schedule/ScheduleItemNode.d.ts.map +1 -0
  49. package/dist/nodes/schedule/ScheduleItemNode.js +94 -0
  50. package/dist/nodes/schedule/ScheduleItemNode.js.map +1 -0
  51. package/dist/nodes/schedule/ScheduleTransformer.d.ts +22 -0
  52. package/dist/nodes/schedule/ScheduleTransformer.d.ts.map +1 -0
  53. package/dist/nodes/schedule/ScheduleTransformer.js +60 -0
  54. package/dist/nodes/schedule/ScheduleTransformer.js.map +1 -0
  55. package/dist/nodes/schedule/index.d.ts +5 -0
  56. package/dist/nodes/schedule/index.d.ts.map +1 -0
  57. package/dist/nodes/schedule/schedulePlugin.d.ts +3 -0
  58. package/dist/nodes/schedule/schedulePlugin.d.ts.map +1 -0
  59. package/dist/nodes/schedule/schedulePlugin.js +33 -0
  60. package/dist/nodes/schedule/schedulePlugin.js.map +1 -0
  61. package/dist/plugins/AutoLinkPlugin.d.ts +3 -0
  62. package/dist/plugins/AutoLinkPlugin.d.ts.map +1 -0
  63. package/dist/plugins/AutoLinkPlugin.js +22 -0
  64. package/dist/plugins/AutoLinkPlugin.js.map +1 -0
  65. package/dist/plugins/FloatingLinkEditorPlugin.d.ts +7 -0
  66. package/dist/plugins/FloatingLinkEditorPlugin.d.ts.map +1 -0
  67. package/dist/plugins/FloatingLinkEditorPlugin2.js +302 -0
  68. package/dist/plugins/FloatingLinkEditorPlugin2.js.map +1 -0
  69. package/dist/plugins/HistoryPlugin.d.ts +26 -0
  70. package/dist/plugins/HistoryPlugin.d.ts.map +1 -0
  71. package/dist/plugins/LinkPlugin.d.ts +3 -0
  72. package/dist/plugins/LinkPlugin.d.ts.map +1 -0
  73. package/dist/plugins/LinkPlugin.js +18 -0
  74. package/dist/plugins/LinkPlugin.js.map +1 -0
  75. package/dist/plugins/ToolbarPlugin.d.ts +7 -0
  76. package/dist/plugins/ToolbarPlugin.d.ts.map +1 -0
  77. package/dist/plugins/ToolbarPlugin.js +372 -0
  78. package/dist/plugins/ToolbarPlugin.js.map +1 -0
  79. package/dist/schedule.js +4 -0
  80. package/dist/scribo.css +1977 -0
  81. package/dist/themes/EditorTheme.d.ts +4 -0
  82. package/dist/themes/EditorTheme.d.ts.map +1 -0
  83. package/dist/themes/EditorTheme2.js +107 -0
  84. package/dist/themes/EditorTheme2.js.map +1 -0
  85. package/dist/types.d.ts +26 -0
  86. package/dist/types.d.ts.map +1 -0
  87. package/dist/ui/ContentEditable.d.ts +6 -0
  88. package/dist/ui/ContentEditable.d.ts.map +1 -0
  89. package/dist/ui/ContentEditable2.js +14 -0
  90. package/dist/ui/ContentEditable2.js.map +1 -0
  91. package/dist/ui/DropDown.d.ts +18 -0
  92. package/dist/ui/DropDown.d.ts.map +1 -0
  93. package/dist/ui/DropDown.js +162 -0
  94. package/dist/ui/DropDown.js.map +1 -0
  95. package/dist/ui/Placeholder.d.ts +6 -0
  96. package/dist/ui/Placeholder.d.ts.map +1 -0
  97. package/dist/ui/Placeholder2.js +13 -0
  98. package/dist/ui/Placeholder2.js.map +1 -0
  99. package/dist/utils/environment.d.ts +24 -0
  100. package/dist/utils/environment.d.ts.map +1 -0
  101. package/dist/utils/environment.js +20 -0
  102. package/dist/utils/environment.js.map +1 -0
  103. package/dist/utils/getSelectedNode.d.ts +3 -0
  104. package/dist/utils/getSelectedNode.d.ts.map +1 -0
  105. package/dist/utils/getSelectedNode.js +23 -0
  106. package/dist/utils/getSelectedNode.js.map +1 -0
  107. package/dist/utils/setFloatingElemPosition.d.ts +2 -0
  108. package/dist/utils/setFloatingElemPosition.d.ts.map +1 -0
  109. package/dist/utils/setFloatingElemPositionForLinkEditor.d.ts +2 -0
  110. package/dist/utils/setFloatingElemPositionForLinkEditor.d.ts.map +1 -0
  111. package/dist/utils/setFloatingElemPositionForLinkEditor.js +33 -0
  112. package/dist/utils/setFloatingElemPositionForLinkEditor.js.map +1 -0
  113. package/dist/utils/url.d.ts +11 -0
  114. package/dist/utils/url.d.ts.map +1 -0
  115. package/dist/utils/url.js +33 -0
  116. package/dist/utils/url.js.map +1 -0
  117. package/dist/vite.svg +1 -0
  118. package/package.json +55 -38
  119. package/src/MarkdownEditor.css +61 -47
  120. package/src/MarkdownEditor.tsx +76 -29
  121. package/src/MarkdownInput.tsx +9 -10
  122. package/src/context/SharedHistoryContext.tsx +8 -16
  123. package/src/main.tsx +3 -3
  124. package/src/nodes/EditorNodes.ts +10 -10
  125. package/src/nodes/callout/Callout.css +94 -0
  126. package/src/nodes/callout/CalloutComponent.tsx +61 -0
  127. package/src/nodes/callout/CalloutNode.ts +197 -0
  128. package/src/nodes/callout/CalloutTransformer.ts +87 -0
  129. package/src/nodes/callout/calloutPlugin.ts +37 -0
  130. package/src/nodes/callout/index.ts +5 -0
  131. package/src/nodes/schedule/ScheduleItem.css +140 -0
  132. package/src/nodes/schedule/ScheduleItemComponent.tsx +195 -0
  133. package/src/nodes/schedule/ScheduleItemNode.ts +135 -0
  134. package/src/nodes/schedule/ScheduleTransformer.ts +80 -0
  135. package/src/nodes/schedule/index.ts +4 -0
  136. package/src/nodes/schedule/schedulePlugin.ts +33 -0
  137. package/src/plugins/AutoLinkPlugin.tsx +5 -10
  138. package/src/plugins/FloatingLinkEditorPlugin.tsx +42 -66
  139. package/src/plugins/HistoryPlugin.tsx +20 -30
  140. package/src/plugins/LinkPlugin.tsx +2 -3
  141. package/src/plugins/ToolbarPlugin.tsx +129 -141
  142. package/src/themes/EditorTheme.css +9 -5
  143. package/src/themes/EditorTheme.ts +89 -90
  144. package/src/types.ts +27 -0
  145. package/src/ui/ContentEditable.tsx +5 -5
  146. package/src/ui/DropDown.tsx +23 -41
  147. package/src/ui/Placeholder.tsx +21 -21
  148. package/src/utils/environment.ts +6 -12
  149. package/src/utils/getSelectedNode.ts +3 -5
  150. package/src/utils/setFloatingElemPosition.ts +2 -5
  151. package/src/utils/setFloatingElemPositionForLinkEditor.ts +1 -1
  152. package/src/utils/url.ts +4 -10
  153. package/tsconfig.json +8 -0
  154. package/CHANGELOG.md +0 -65
  155. package/README.md +0 -126
  156. package/RELEASE.md +0 -102
  157. package/eslint.config.js +0 -4
  158. package/src/App.css +0 -35
  159. package/src/App.tsx +0 -42
  160. package/src/index.tsx +0 -13
  161. package/tsconfig.node.json +0 -11
  162. package/vite.config.site.ts +0 -19
  163. package/vite.config.ts +0 -33
  164. /package/{src → dist}/vite-env.d.ts +0 -0
@@ -0,0 +1,33 @@
1
+ //#region src/utils/url.ts
2
+ /**
3
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
4
+ *
5
+ * This source code is licensed under the MIT license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ *
8
+ * Source: https://github.com/facebook/lexical/blob/main/packages/lexical-playground/src/utils/url.ts
9
+ */
10
+ var SUPPORTED_URL_PROTOCOLS = /* @__PURE__ */ new Set([
11
+ "http:",
12
+ "https:",
13
+ "mailto:",
14
+ "sms:",
15
+ "tel:"
16
+ ]);
17
+ function sanitizeUrl(url) {
18
+ try {
19
+ const parsedUrl = new URL(url);
20
+ if (!SUPPORTED_URL_PROTOCOLS.has(parsedUrl.protocol)) return "about:blank";
21
+ } catch {
22
+ return url;
23
+ }
24
+ return url;
25
+ }
26
+ var urlRegExp = /* @__PURE__ */ new RegExp(/((([A-Za-z]{3,9}:(?:\/\/)?)(?:[-;:&=+$,\w]+@)?[A-Za-z0-9.-]+|(?:www.|[-;:&=+$,\w]+@)[A-Za-z0-9.-]+)((?:\/[+~%/.\w-]*)?\??(?:[-+=&;%@.\w]*)#?(?:\w*))?)/);
27
+ function validateUrl(url) {
28
+ return url === "https://" || urlRegExp.test(url);
29
+ }
30
+ //#endregion
31
+ export { sanitizeUrl, validateUrl };
32
+
33
+ //# sourceMappingURL=url.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"url.js","names":[],"sources":["../../src/utils/url.ts"],"sourcesContent":["/**\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * Source: https://github.com/facebook/lexical/blob/main/packages/lexical-playground/src/utils/url.ts\n */\n\nconst SUPPORTED_URL_PROTOCOLS = new Set(['http:', 'https:', 'mailto:', 'sms:', 'tel:']);\n\nexport function sanitizeUrl(url: string): string {\n try {\n const parsedUrl = new URL(url);\n // eslint-disable-next-line no-script-url\n if (!SUPPORTED_URL_PROTOCOLS.has(parsedUrl.protocol)) {\n return 'about:blank';\n }\n } catch {\n return url;\n }\n return url;\n}\n\n// Source: https://stackoverflow.com/a/8234912/2013580\nconst urlRegExp = new RegExp(\n /((([A-Za-z]{3,9}:(?:\\/\\/)?)(?:[-;:&=+$,\\w]+@)?[A-Za-z0-9.-]+|(?:www.|[-;:&=+$,\\w]+@)[A-Za-z0-9.-]+)((?:\\/[+~%/.\\w-]*)?\\??(?:[-+=&;%@.\\w]*)#?(?:\\w*))?)/\n);\nexport function validateUrl(url: string): boolean {\n // TODO Fix UI for link insertion; it should never default to an invalid URL such as https://.\n // Maybe show a dialog where they user can type the URL before inserting it.\n return url === 'https://' || urlRegExp.test(url);\n}\n"],"mappings":";;;;;;;;;AASA,IAAM,0CAA0B,IAAI,IAAI;CAAC;CAAS;CAAU;CAAW;CAAQ;AAAM,CAAC;AAEtF,SAAgB,YAAY,KAAqB;CAC/C,IAAI;EACF,MAAM,YAAY,IAAI,IAAI,GAAG;EAE7B,IAAI,CAAC,wBAAwB,IAAI,UAAU,QAAQ,GACjD,OAAO;CAEX,QAAQ;EACN,OAAO;CACT;CACA,OAAO;AACT;AAGA,IAAM,4BAAY,IAAI,OACpB,wJACF;AACA,SAAgB,YAAY,KAAsB;CAGhD,OAAO,QAAQ,cAAc,UAAU,KAAK,GAAG;AACjD"}
package/dist/vite.svg ADDED
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
package/package.json CHANGED
@@ -1,66 +1,83 @@
1
1
  {
2
2
  "name": "@eventuras/scribo",
3
- "version": "0.8.3",
3
+ "version": "0.10.7",
4
+ "description": "Markdown editor built on Lexical — WYSIWYG editing with markdown export",
5
+ "license": "MIT",
6
+ "homepage": "https://scribo.losol.no/",
4
7
  "repository": {
5
8
  "type": "git",
6
- "url": "https://github.com/losol/eventuras.git",
7
- "directory": "libs/scribo"
9
+ "url": "https://github.com/losol/scribo.git"
10
+ },
11
+ "bugs": {
12
+ "url": "https://github.com/losol/scribo/issues"
8
13
  },
9
14
  "type": "module",
10
15
  "exports": {
11
16
  ".": {
12
17
  "types": "./dist/main.d.ts",
13
- "import": "./dist/main.js"
18
+ "default": "./src/main.tsx"
19
+ },
20
+ "./plugins/schedule": {
21
+ "types": "./dist/nodes/schedule/index.d.ts",
22
+ "default": "./src/nodes/schedule/index.ts"
23
+ },
24
+ "./plugins/callout": {
25
+ "types": "./dist/nodes/callout/index.d.ts",
26
+ "default": "./src/nodes/callout/index.ts"
14
27
  },
15
28
  "./style.css": "./dist/scribo.css"
16
29
  },
17
30
  "main": "src/main.tsx",
18
- "scripts": {
19
- "build": "vite build",
20
- "build:site": "vite build --config vite.config.site.ts",
21
- "dev": "vite"
22
- },
23
31
  "overrides": {
24
32
  "vite": {
25
33
  "rollup": "npm:@rollup/wasm-node"
26
34
  }
27
35
  },
28
36
  "dependencies": {
29
- "@lexical/code": "0.35.0",
30
- "@lexical/hashtag": "0.35.0",
31
- "@lexical/link": "0.35.0",
32
- "@lexical/list": "0.35.0",
33
- "@lexical/mark": "0.35.0",
34
- "@lexical/markdown": "0.35.0",
35
- "@lexical/overflow": "0.35.0",
36
- "@lexical/react": "^0.35.0",
37
- "@lexical/rich-text": "0.35.0",
38
- "@lexical/selection": "0.35.0",
39
- "@lexical/table": "0.35.0",
40
- "@lexical/utils": "0.35.0",
41
- "lexical": "0.35.0",
37
+ "@lexical/code": "0.50.0",
38
+ "@lexical/hashtag": "0.50.0",
39
+ "@lexical/link": "0.50.0",
40
+ "@lexical/list": "0.50.0",
41
+ "@lexical/mark": "0.50.0",
42
+ "@lexical/markdown": "0.50.0",
43
+ "@lexical/overflow": "0.50.0",
44
+ "@lexical/react": "^0.50.0",
45
+ "@lexical/rich-text": "0.50.0",
46
+ "@lexical/selection": "0.50.0",
47
+ "@lexical/table": "0.50.0",
48
+ "@lexical/utils": "0.50.0",
49
+ "lexical": "0.50.0",
42
50
  "prismjs": "^1.30.0",
43
- "yjs": "^13.6.29"
51
+ "yjs": "^13.6.32"
44
52
  },
45
53
  "devDependencies": {
46
- "@eventuras/eslint-config": "workspace:*",
47
- "@eventuras/typescript-config": "workspace:*",
48
- "@eventuras/vite-config": "workspace:*",
54
+ "@biomejs/biome": "^2.5.14",
55
+ "@eventuras/typescript-config": "^1.0.0",
56
+ "@eventuras/vite-config": "^0.3.1",
49
57
  "@testing-library/react": "^16.3.2",
50
- "@types/react": "^19.2.9",
51
- "@types/react-dom": "^19.2.3",
52
- "@vitejs/plugin-react": "^5.1.2",
53
- "lightningcss": "^1.31.1",
54
- "typescript": "^5.9.3",
55
- "vite": "^7.3.1",
56
- "vitest": "^4.0.18"
58
+ "@types/react": "^19.3.0",
59
+ "@types/react-dom": "^19.3.0",
60
+ "@typescript/typescript6": "^6.0.2",
61
+ "@vitejs/plugin-react": "^6.0.5",
62
+ "lightningcss": "^1.33.0",
63
+ "typescript": "^7.0.2",
64
+ "vite": "^8.3.0",
65
+ "vitest": "^5.0.1"
57
66
  },
58
67
  "peerDependencies": {
59
- "react": "^19.2.3",
60
- "react-dom": "^19.2.3"
68
+ "react": "^19.0.0",
69
+ "react-dom": "^19.0.0"
61
70
  },
62
- "packageManager": "pnpm@10.28.1",
63
71
  "engines": {
64
- "node": ">=22.0.0 <25.0.0"
72
+ "node": ">=24.0.0 <25.0.0"
73
+ },
74
+ "scripts": {
75
+ "build": "vite build",
76
+ "build:site": "vite build --config vite.config.site.ts",
77
+ "dev": "vite",
78
+ "lint": "biome check .",
79
+ "test": "vitest run",
80
+ "format": "biome format --write .",
81
+ "format:check": "biome format --check ."
65
82
  }
66
- }
83
+ }
@@ -13,14 +13,14 @@
13
13
  .editor-shell {
14
14
  margin: 10px auto;
15
15
  border-radius: 2px;
16
- /*max-width: 1100px;*/
17
16
  color: #000;
18
17
  position: relative;
19
18
  line-height: 1.7;
20
19
  font-weight: 400;
21
20
  }
22
21
 
23
- [data-theme="dark"] .editor-shell {
22
+ [data-theme="dark"] .editor-shell,
23
+ [data-color-scheme="dark"] .editor-shell {
24
24
  color: #e5e5e5;
25
25
  }
26
26
 
@@ -32,7 +32,8 @@
32
32
  border-bottom-right-radius: 10px;
33
33
  }
34
34
 
35
- [data-theme="dark"] .editor-shell .editor-container {
35
+ [data-theme="dark"] .editor-shell .editor-container,
36
+ [data-color-scheme="dark"] .editor-shell .editor-container {
36
37
  background: rgba(255, 255, 255, 0.05);
37
38
  }
38
39
 
@@ -112,17 +113,18 @@
112
113
  border-left: 3px solid #ddd;
113
114
  }
114
115
 
115
- [data-theme="dark"] .editor blockquote {
116
+ [data-theme="dark"] .editor blockquote,
117
+ [data-color-scheme="dark"] .editor blockquote {
116
118
  border-left-color: #555;
117
119
  }
118
120
 
119
- ul {
121
+ .editor ul {
120
122
  list-style-type: disc;
121
123
  padding: 10px 0 !important;
122
124
  padding-left: 20px !important;
123
125
  }
124
126
 
125
- pre {
127
+ .editor-shell pre {
126
128
  line-height: 1.1;
127
129
  background: #222;
128
130
  color: #fff;
@@ -133,12 +135,12 @@ pre {
133
135
  max-height: 400px;
134
136
  }
135
137
 
136
- pre::-webkit-scrollbar {
138
+ .editor-shell pre::-webkit-scrollbar {
137
139
  background: transparent;
138
140
  width: 10px;
139
141
  }
140
142
 
141
- pre::-webkit-scrollbar-thumb {
143
+ .editor-shell pre::-webkit-scrollbar-thumb {
142
144
  background: #999;
143
145
  }
144
146
 
@@ -204,7 +206,9 @@ i.redo {
204
206
  }
205
207
 
206
208
  [data-theme="dark"] .link-editor .button.active,
207
- [data-theme="dark"] .toolbar .button.active {
209
+ [data-color-scheme="dark"] .link-editor .button.active,
210
+ [data-theme="dark"] .toolbar .button.active,
211
+ [data-color-scheme="dark"] .toolbar .button.active {
208
212
  background-color: rgba(100, 150, 255, 0.3);
209
213
  }
210
214
 
@@ -224,8 +228,9 @@ i.redo {
224
228
  font-family: inherit;
225
229
  }
226
230
 
227
- [data-theme="dark"] .link-editor .link-input {
228
- background-color: rgba(255, 255, 255, 0.1);
231
+ [data-theme="dark"] .link-editor .link-input,
232
+ [data-color-scheme="dark"] .link-editor .link-input {
233
+ background-color: #303030;
229
234
  color: #e5e5e5;
230
235
  }
231
236
 
@@ -365,7 +370,8 @@ i.redo {
365
370
  background-color: #fff;
366
371
  }
367
372
 
368
- [data-theme="dark"] .dropdown {
373
+ [data-theme="dark"] .dropdown,
374
+ [data-color-scheme="dark"] .dropdown {
369
375
  background-color: rgba(30, 30, 30, 0.95);
370
376
  box-shadow:
371
377
  0 12px 28px 0 rgba(0, 0, 0, 0.6),
@@ -392,7 +398,8 @@ i.redo {
392
398
  min-width: 100px;
393
399
  }
394
400
 
395
- [data-theme="dark"] .dropdown .item {
401
+ [data-theme="dark"] .dropdown .item,
402
+ [data-color-scheme="dark"] .dropdown .item {
396
403
  color: #e5e5e5;
397
404
  background-color: transparent;
398
405
  }
@@ -421,7 +428,8 @@ i.redo {
421
428
  background-color: #eee;
422
429
  }
423
430
 
424
- [data-theme="dark"] .dropdown .item:hover {
431
+ [data-theme="dark"] .dropdown .item:hover,
432
+ [data-color-scheme="dark"] .dropdown .item:hover {
425
433
  background-color: rgba(255, 255, 255, 0.1);
426
434
  }
427
435
 
@@ -444,7 +452,8 @@ i.redo {
444
452
  background-repeat: no-repeat;
445
453
  }
446
454
 
447
- [data-theme="dark"] .dropdown .item .icon {
455
+ [data-theme="dark"] .dropdown .item .icon,
456
+ [data-color-scheme="dark"] .dropdown .item .icon {
448
457
  filter: invert(1);
449
458
  }
450
459
 
@@ -455,7 +464,8 @@ i.redo {
455
464
  height: 1px;
456
465
  }
457
466
 
458
- [data-theme="dark"] .dropdown .divider {
467
+ [data-theme="dark"] .dropdown .divider,
468
+ [data-color-scheme="dark"] .dropdown .divider {
459
469
  background-color: rgba(255, 255, 255, 0.2);
460
470
  }
461
471
 
@@ -511,6 +521,14 @@ i.redo {
511
521
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='currentColor' class='bi bi-code'%3E%3Cpath d='M5.854 4.854a.5.5 0 1 0-.708-.708l-3.5 3.5a.5.5 0 0 0 0 .708l3.5 3.5a.5.5 0 0 0 .708-.708L2.707 8l3.147-3.146zm4.292 0a.5.5 0 0 1 .708-.708l3.5 3.5a.5.5 0 0 1 0 .708l-3.5 3.5a.5.5 0 0 1-.708-.708L13.293 8l-3.147-3.146z'/%3E%3C/svg%3E");
512
522
  }
513
523
 
524
+ .icon.schedule {
525
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='currentColor'%3E%3Cpath d='M8 3.5a.5.5 0 0 0-1 0V8a.5.5 0 0 0 .252.434l3.5 2a.5.5 0 0 0 .496-.868L8 7.71V3.5z'/%3E%3Cpath d='M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16zm7-8A7 7 0 1 1 1 8a7 7 0 0 1 14 0z'/%3E%3C/svg%3E");
526
+ }
527
+
528
+ .icon.alert {
529
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='currentColor'%3E%3Cpath d='M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z'/%3E%3Cpath d='m8.93 6.588-2.29.287-.082.38.45.083c.294.07.352.176.288.469l-.738 3.468c-.194.897.105 1.319.808 1.319.545 0 1.178-.252 1.465-.598l.088-.416c-.2.176-.492.246-.686.246-.275 0-.375-.193-.304-.533L8.93 6.588zM9 4.5a1 1 0 1 1-2 0 1 1 0 0 1 2 0z'/%3E%3C/svg%3E");
530
+ }
531
+
514
532
  .switches {
515
533
  z-index: 6;
516
534
  position: fixed;
@@ -852,15 +870,14 @@ i.redo {
852
870
  }
853
871
 
854
872
  .table-cell-action-button {
855
- background-color: none;
856
- display: flex;
873
+ background-color: transparent;
874
+ display: inline-flex;
857
875
  justify-content: center;
858
876
  align-items: center;
859
877
  border: 0;
860
878
  position: relative;
861
879
  border-radius: 15px;
862
880
  color: #222;
863
- display: inline-block;
864
881
  cursor: pointer;
865
882
  }
866
883
 
@@ -885,8 +902,9 @@ i.chevron-down {
885
902
  cursor: pointer;
886
903
  }
887
904
 
888
- [data-theme="dark"] .action-button {
889
- background-color: rgba(255, 255, 255, 0.1);
905
+ [data-theme="dark"] .action-button,
906
+ [data-color-scheme="dark"] .action-button {
907
+ background-color: #303030;
890
908
  color: #e5e5e5;
891
909
  }
892
910
 
@@ -895,8 +913,9 @@ i.chevron-down {
895
913
  color: #000;
896
914
  }
897
915
 
898
- [data-theme="dark"] .action-button:hover {
899
- background-color: rgba(255, 255, 255, 0.2);
916
+ [data-theme="dark"] .action-button:hover,
917
+ [data-color-scheme="dark"] .action-button:hover {
918
+ background-color: #474747;
900
919
  color: #fff;
901
920
  }
902
921
 
@@ -906,7 +925,8 @@ button.action-button:disabled {
906
925
  cursor: not-allowed;
907
926
  }
908
927
 
909
- [data-theme="dark"] button.action-button:disabled {
928
+ [data-theme="dark"] button.action-button:disabled,
929
+ [data-color-scheme="dark"] button.action-button:disabled {
910
930
  background: rgba(255, 255, 255, 0.05);
911
931
  }
912
932
 
@@ -933,7 +953,8 @@ button.action-button:disabled {
933
953
  z-index: 2;
934
954
  }
935
955
 
936
- [data-theme="dark"] .toolbar {
956
+ [data-theme="dark"] .toolbar,
957
+ [data-color-scheme="dark"] .toolbar {
937
958
  background: rgba(255, 255, 255, 0.1);
938
959
  }
939
960
 
@@ -960,15 +981,15 @@ button.toolbar-item.spaced {
960
981
 
961
982
  button.toolbar-item i.format {
962
983
  background-size: contain;
963
- display: inline-block;
984
+ display: flex;
964
985
  height: 18px;
965
986
  width: 18px;
966
987
  vertical-align: -0.25em;
967
- display: flex;
968
988
  opacity: 0.6;
969
989
  }
970
990
 
971
- [data-theme="dark"] button.toolbar-item i.format {
991
+ [data-theme="dark"] button.toolbar-item i.format,
992
+ [data-color-scheme="dark"] button.toolbar-item i.format {
972
993
  filter: invert(1);
973
994
  }
974
995
 
@@ -991,7 +1012,8 @@ button.toolbar-item.active i {
991
1012
  background-color: #eee;
992
1013
  }
993
1014
 
994
- [data-theme="dark"] .toolbar-item:hover:not([disabled]) {
1015
+ [data-theme="dark"] .toolbar-item:hover:not([disabled]),
1016
+ [data-color-scheme="dark"] .toolbar-item:hover:not([disabled]) {
995
1017
  background-color: rgba(255, 255, 255, 0.1);
996
1018
  }
997
1019
 
@@ -1017,7 +1039,8 @@ button.toolbar-item.active i {
1017
1039
  padding-right: 10px;
1018
1040
  }
1019
1041
 
1020
- [data-theme="dark"] .toolbar .toolbar-item .text {
1042
+ [data-theme="dark"] .toolbar .toolbar-item .text,
1043
+ [data-color-scheme="dark"] .toolbar .toolbar-item .text {
1021
1044
  color: #bbb;
1022
1045
  }
1023
1046
 
@@ -1031,7 +1054,8 @@ button.toolbar-item.active i {
1031
1054
  background-size: contain;
1032
1055
  }
1033
1056
 
1034
- [data-theme="dark"] .toolbar .toolbar-item .icon {
1057
+ [data-theme="dark"] .toolbar .toolbar-item .icon,
1058
+ [data-color-scheme="dark"] .toolbar .toolbar-item .icon {
1035
1059
  filter: invert(1);
1036
1060
  }
1037
1061
 
@@ -1045,7 +1069,9 @@ button.toolbar-item.active i {
1045
1069
  }
1046
1070
 
1047
1071
  [data-theme="dark"] .toolbar i.chevron-down,
1048
- [data-theme="dark"] .toolbar-item i.chevron-down {
1072
+ [data-color-scheme="dark"] .toolbar i.chevron-down,
1073
+ [data-theme="dark"] .toolbar-item i.chevron-down,
1074
+ [data-color-scheme="dark"] .toolbar-item i.chevron-down {
1049
1075
  filter: invert(1);
1050
1076
  }
1051
1077
 
@@ -1080,7 +1106,7 @@ button.toolbar-item.active i {
1080
1106
  padding: 20px 10px;
1081
1107
  position: relative;
1082
1108
  border: 1px solid #e8e8e8;
1083
- font-family: "Reenie Beanie";
1109
+ font-family: "Reenie Beanie", cursive;
1084
1110
  font-size: 24px;
1085
1111
  border-bottom-right-radius: 60px 5px;
1086
1112
  display: block;
@@ -1102,24 +1128,12 @@ button.toolbar-item.active i {
1102
1128
 
1103
1129
  .sticky-note.yellow {
1104
1130
  border-top: 1px solid #fdfd86;
1105
- background: linear-gradient(
1106
- 135deg,
1107
- #ffff88 81%,
1108
- #ffff88 82%,
1109
- #ffff88 82%,
1110
- #ffffc6 100%
1111
- );
1131
+ background: linear-gradient(135deg, #ffff88 81%, #ffff88 82%, #ffff88 82%, #ffffc6 100%);
1112
1132
  }
1113
1133
 
1114
1134
  .sticky-note.pink {
1115
1135
  border-top: 1px solid #e7d1e4;
1116
- background: linear-gradient(
1117
- 135deg,
1118
- #f7cbe8 81%,
1119
- #f7cbe8 82%,
1120
- #f7cbe8 82%,
1121
- #e7bfe1 100%
1122
- );
1136
+ background: linear-gradient(135deg, #f7cbe8 81%, #f7cbe8 82%, #f7cbe8 82%, #e7bfe1 100%);
1123
1137
  }
1124
1138
 
1125
1139
  .sticky-note-container.dragging {
@@ -2,26 +2,26 @@ import {
2
2
  $convertFromMarkdownString,
3
3
  $convertToMarkdownString,
4
4
  TRANSFORMERS,
5
- } from "@lexical/markdown";
6
- import { $getRoot } from 'lexical';
7
- import { LexicalComposer } from "@lexical/react/LexicalComposer";
8
- import { LexicalErrorBoundary } from "@lexical/react/LexicalErrorBoundary";
9
- import { OnChangePlugin } from "@lexical/react/LexicalOnChangePlugin";
10
- import { RichTextPlugin } from "@lexical/react/LexicalRichTextPlugin";
11
- import { EditorState } from "lexical";
12
- import React, { useState } from "react";
13
-
14
- import EditorNodes from "./nodes/EditorNodes";
15
- import ToolbarPlugin from "./plugins/ToolbarPlugin";
16
- import EditorTheme from "./themes/EditorTheme";
17
- import ContentEditable from "./ui/ContentEditable";
18
- import Placeholder from "./ui/Placeholder";
5
+ } from '@lexical/markdown';
6
+ import { LexicalComposer } from '@lexical/react/LexicalComposer';
7
+ import { LexicalErrorBoundary } from '@lexical/react/LexicalErrorBoundary';
19
8
  import { HistoryPlugin } from '@lexical/react/LexicalHistoryPlugin';
20
9
  import { MarkdownShortcutPlugin } from '@lexical/react/LexicalMarkdownShortcutPlugin';
21
- import LinkPlugin from "./plugins/LinkPlugin";
22
- import FloatingLinkEditorPlugin from "./plugins/FloatingLinkEditorPlugin";
23
- import AutoLinkPlugin from "./plugins/AutoLinkPlugin";
10
+ import { OnChangePlugin } from '@lexical/react/LexicalOnChangePlugin';
11
+ import { RichTextPlugin } from '@lexical/react/LexicalRichTextPlugin';
12
+ import { $createParagraphNode, $createTextNode, $getRoot, type EditorState } from 'lexical';
13
+ import type React from 'react';
14
+ import { useMemo, useState } from 'react';
24
15
  import { useSharedHistoryContext } from './context/SharedHistoryContext';
16
+ import EditorNodes from './nodes/EditorNodes';
17
+ import AutoLinkPlugin from './plugins/AutoLinkPlugin';
18
+ import FloatingLinkEditorPlugin from './plugins/FloatingLinkEditorPlugin';
19
+ import LinkPlugin from './plugins/LinkPlugin';
20
+ import ToolbarPlugin from './plugins/ToolbarPlugin';
21
+ import EditorTheme from './themes/EditorTheme';
22
+ import type { ScriboPlugin } from './types';
23
+ import ContentEditable from './ui/ContentEditable';
24
+ import Placeholder from './ui/Placeholder';
25
25
 
26
26
  export type onChangeMisc = {
27
27
  plainText: string;
@@ -29,6 +29,7 @@ export type onChangeMisc = {
29
29
  export interface MarkdownEditorProps {
30
30
  initialMarkdown?: string;
31
31
  className?: string;
32
+ plugins?: ScriboPlugin[];
32
33
  onChange?: (markdown: string, misc: onChangeMisc) => void;
33
34
  onBlur?: () => void;
34
35
  placeholder?: string;
@@ -40,8 +41,9 @@ const MarkdownEditor: React.FC<MarkdownEditorProps> = (props) => {
40
41
  const { historyState } = useSharedHistoryContext();
41
42
  const [isLinkEditMode, setIsLinkEditMode] = useState<boolean>(false);
42
43
 
43
- const [floatingAnchorElem, setFloatingAnchorElem] =
44
- useState<HTMLDivElement | undefined>(undefined);
44
+ const [floatingAnchorElem, setFloatingAnchorElem] = useState<HTMLDivElement | undefined>(
45
+ undefined
46
+ );
45
47
 
46
48
  const onRef = (_floatingAnchorElem: HTMLDivElement) => {
47
49
  if (_floatingAnchorElem !== null) {
@@ -49,17 +51,35 @@ const MarkdownEditor: React.FC<MarkdownEditorProps> = (props) => {
49
51
  }
50
52
  };
51
53
 
54
+ // Collect nodes and transformers from plugins
55
+ const pluginNodes = useMemo(
56
+ () => (props.plugins ?? []).flatMap((p) => p.nodes ?? []),
57
+ [props.plugins]
58
+ );
59
+ const allTransformers = useMemo(
60
+ () => [...(props.plugins ?? []).flatMap((p) => p.transformers ?? []), ...TRANSFORMERS],
61
+ [props.plugins]
62
+ );
63
+ const toolbarButtons = useMemo(
64
+ () => (props.plugins ?? []).flatMap((p) => p.toolbarButtons ?? []),
65
+ [props.plugins]
66
+ );
67
+ const editorPlugins = useMemo(
68
+ () => (props.plugins ?? []).flatMap((p) => p.editorPlugins ?? []),
69
+ [props.plugins]
70
+ );
71
+
52
72
  const internalOnChange = (editorState: EditorState) => {
53
73
  editorState.read(() => {
54
- const markdown = $convertToMarkdownString(TRANSFORMERS);
74
+ const markdown = $convertToMarkdownString(allTransformers);
55
75
  const plainText = $getRoot().getTextContent();
56
- props.onChange && props.onChange(markdown, { plainText });
76
+ props.onChange?.(markdown, { plainText });
57
77
  });
58
78
  };
59
79
 
60
80
  const initialConfig = {
61
- namespace: "ScriboMarkdown",
62
- nodes: [...EditorNodes],
81
+ namespace: 'ScriboMarkdown',
82
+ nodes: [...EditorNodes, ...pluginNodes],
63
83
  onError: (error: Error) => {
64
84
  throw error;
65
85
  },
@@ -67,8 +87,23 @@ const MarkdownEditor: React.FC<MarkdownEditorProps> = (props) => {
67
87
  editorState: props.initialMarkdown
68
88
  ? () => {
69
89
  // Unescape backslash-escaped markdown characters
70
- const unescapedMarkdown = props.initialMarkdown!.replace(/\\([*_`\[\]()#+-])/g, '$1');
71
- $convertFromMarkdownString(unescapedMarkdown, TRANSFORMERS);
90
+ const unescapedMarkdown = (props.initialMarkdown ?? '').replaceAll(
91
+ /\\([*_`[\]()#+-])/g,
92
+ '$1'
93
+ );
94
+ try {
95
+ $convertFromMarkdownString(unescapedMarkdown, allTransformers);
96
+ } catch (err) {
97
+ // Markdown parsing threw (e.g. a transformer bug). Fall back to plain
98
+ // text so the field stays editable and content isn't lost — better
99
+ // than crashing the whole tree and propagating to the page boundary.
100
+ console.error('Scribo: markdown parse failed, falling back to plain text', err);
101
+ const root = $getRoot();
102
+ root.clear();
103
+ const paragraph = $createParagraphNode();
104
+ paragraph.append($createTextNode(unescapedMarkdown));
105
+ root.append(paragraph);
106
+ }
72
107
  }
73
108
  : undefined,
74
109
  };
@@ -76,11 +111,16 @@ const MarkdownEditor: React.FC<MarkdownEditorProps> = (props) => {
76
111
  return (
77
112
  <div className={props.className}>
78
113
  <LexicalComposer initialConfig={initialConfig}>
79
- <div className="editor-shell" onBlur={props.onBlur} data-testid={props['data-testid']} id={props.id}>
80
- <ToolbarPlugin setIsLinkEditMode={setIsLinkEditMode} />
114
+ <div
115
+ className="editor-shell"
116
+ onBlur={props.onBlur}
117
+ data-testid={props['data-testid']}
118
+ id={props.id}
119
+ >
120
+ <ToolbarPlugin setIsLinkEditMode={setIsLinkEditMode} toolbarButtons={toolbarButtons} />
81
121
  <div className="editor-container rich-text">
82
122
  <HistoryPlugin externalHistoryState={historyState} />
83
- <MarkdownShortcutPlugin transformers={TRANSFORMERS} />
123
+ <MarkdownShortcutPlugin transformers={allTransformers} />
84
124
  <AutoLinkPlugin />
85
125
  <LinkPlugin />
86
126
  <FloatingLinkEditorPlugin
@@ -91,7 +131,11 @@ const MarkdownEditor: React.FC<MarkdownEditorProps> = (props) => {
91
131
  <RichTextPlugin
92
132
  contentEditable={
93
133
  <div className="editor-scroller">
94
- <div className="editor" ref={onRef} id={props.id ? `${props.id}-editable` : undefined}>
134
+ <div
135
+ className="editor"
136
+ ref={onRef}
137
+ id={props.id ? `${props.id}-editable` : undefined}
138
+ >
95
139
  <ContentEditable />
96
140
  </div>
97
141
  </div>
@@ -101,6 +145,9 @@ const MarkdownEditor: React.FC<MarkdownEditorProps> = (props) => {
101
145
  />
102
146
  </div>
103
147
  {props.onChange && <OnChangePlugin onChange={internalOnChange} />}
148
+ {editorPlugins.map((Plugin) => (
149
+ <Plugin key={Plugin.displayName ?? Plugin.name} />
150
+ ))}
104
151
  </div>
105
152
  </LexicalComposer>
106
153
  </div>