@cyberyozh/text-editor 0.1.1

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 (209) hide show
  1. package/README.md +102 -0
  2. package/dist/editor.css +2069 -0
  3. package/dist/editor.css.map +1 -0
  4. package/dist/editor.d.mts +45 -0
  5. package/dist/editor.d.ts +45 -0
  6. package/dist/editor.js +6155 -0
  7. package/dist/editor.js.map +1 -0
  8. package/dist/editor.mjs +6118 -0
  9. package/dist/editor.mjs.map +1 -0
  10. package/dist/render/client.d.mts +24 -0
  11. package/dist/render/client.d.ts +24 -0
  12. package/dist/render/client.js +98 -0
  13. package/dist/render/client.js.map +1 -0
  14. package/dist/render/client.mjs +74 -0
  15. package/dist/render/client.mjs.map +1 -0
  16. package/dist/render.d.mts +58 -0
  17. package/dist/render.d.ts +58 -0
  18. package/dist/render.js +2521 -0
  19. package/dist/render.js.map +1 -0
  20. package/dist/render.mjs +2484 -0
  21. package/dist/render.mjs.map +1 -0
  22. package/dist/styles/index.css +2068 -0
  23. package/package.json +92 -0
  24. package/src/editor/commands/items.ts +374 -0
  25. package/src/editor/commands/suggestion.tsx +146 -0
  26. package/src/editor/components/button/button.css +56 -0
  27. package/src/editor/components/button/button.tsx +35 -0
  28. package/src/editor/components/button/index.ts +1 -0
  29. package/src/editor/components/common-modal/common-modal.css +85 -0
  30. package/src/editor/components/common-modal/common-modal.tsx +58 -0
  31. package/src/editor/components/common-modal/index.ts +1 -0
  32. package/src/editor/components/index.ts +3 -0
  33. package/src/editor/components/input/index.ts +1 -0
  34. package/src/editor/components/input/input.css +43 -0
  35. package/src/editor/components/input/input.tsx +22 -0
  36. package/src/editor/context/dialog-command.tsx +47 -0
  37. package/src/editor/context/index.ts +2 -0
  38. package/src/editor/context/translations-context.tsx +28 -0
  39. package/src/editor/controls/bubble-menu-content.tsx +394 -0
  40. package/src/editor/controls/callout-menu.tsx +169 -0
  41. package/src/editor/controls/character-count.tsx +36 -0
  42. package/src/editor/controls/commands-list.tsx +117 -0
  43. package/src/editor/controls/drag-handle.tsx +98 -0
  44. package/src/editor/controls/emoji-picker.tsx +62 -0
  45. package/src/editor/controls/index.ts +5 -0
  46. package/src/editor/controls/table-menu.tsx +322 -0
  47. package/src/editor/dialogs/add-katex-dialog/add-katex-dialog.css +115 -0
  48. package/src/editor/dialogs/add-katex-dialog/add-katex-dialog.tsx +116 -0
  49. package/src/editor/dialogs/add-katex-dialog/index.ts +1 -0
  50. package/src/editor/dialogs/add-link-dialog/add-link-dialog.css +105 -0
  51. package/src/editor/dialogs/add-link-dialog/add-link-dialog.tsx +177 -0
  52. package/src/editor/dialogs/add-link-dialog/index.ts +1 -0
  53. package/src/editor/dialogs/add-video-dialog/add-video-dialog.css +146 -0
  54. package/src/editor/dialogs/add-video-dialog/add-video-dialog.tsx +157 -0
  55. package/src/editor/dialogs/add-video-dialog/index.ts +1 -0
  56. package/src/editor/dialogs/image-dialog/image-dialog.css +101 -0
  57. package/src/editor/dialogs/image-dialog/image-dialog.tsx +276 -0
  58. package/src/editor/dialogs/image-dialog/index.ts +1 -0
  59. package/src/editor/dialogs/index.ts +4 -0
  60. package/src/editor/editor.tsx +247 -0
  61. package/src/editor/handlers/create-image-upload-handler.ts +24 -0
  62. package/src/editor/handlers/dialog-handlers.ts +143 -0
  63. package/src/editor/handlers/index.ts +8 -0
  64. package/src/editor/index.ts +3 -0
  65. package/src/extensions/base/callout.ts +138 -0
  66. package/src/extensions/base/code-block.ts +24 -0
  67. package/src/extensions/base/compatibility.ts +493 -0
  68. package/src/extensions/base/copyable-text.ts +56 -0
  69. package/src/extensions/base/custom-quote.ts +174 -0
  70. package/src/extensions/base/figure.ts +141 -0
  71. package/src/extensions/base/index.ts +19 -0
  72. package/src/extensions/base/link.ts +18 -0
  73. package/src/extensions/base/myth-reality.ts +45 -0
  74. package/src/extensions/base/prompt.ts +109 -0
  75. package/src/extensions/base/video.ts +29 -0
  76. package/src/extensions/editor/callout.ts +43 -0
  77. package/src/extensions/editor/code-block.ts +9 -0
  78. package/src/extensions/editor/commands.ts +43 -0
  79. package/src/extensions/editor/copyable-text.ts +16 -0
  80. package/src/extensions/editor/custom-quote.ts +54 -0
  81. package/src/extensions/editor/figure.ts +20 -0
  82. package/src/extensions/editor/get-editor-extensions.ts +113 -0
  83. package/src/extensions/editor/index.ts +25 -0
  84. package/src/extensions/editor/prompt.ts +43 -0
  85. package/src/extensions/editor/video.ts +23 -0
  86. package/src/extensions/render/get-render-extensions.ts +74 -0
  87. package/src/extensions/render/index.ts +6 -0
  88. package/src/extensions/views/components/callout.tsx +70 -0
  89. package/src/extensions/views/components/code-block.tsx +73 -0
  90. package/src/extensions/views/components/copy-button.tsx +52 -0
  91. package/src/extensions/views/components/copyable-text.tsx +11 -0
  92. package/src/extensions/views/components/custom-quote.tsx +64 -0
  93. package/src/extensions/views/components/figure.tsx +63 -0
  94. package/src/extensions/views/components/index.ts +7 -0
  95. package/src/extensions/views/components/prompt.tsx +43 -0
  96. package/src/extensions/views/components/video.tsx +40 -0
  97. package/src/extensions/views/node-views/callout.tsx +51 -0
  98. package/src/extensions/views/node-views/code-block.tsx +30 -0
  99. package/src/extensions/views/node-views/copyable-text.tsx +10 -0
  100. package/src/extensions/views/node-views/custom-quote.tsx +66 -0
  101. package/src/extensions/views/node-views/figure.tsx +38 -0
  102. package/src/extensions/views/node-views/index.ts +7 -0
  103. package/src/extensions/views/node-views/prompt.tsx +35 -0
  104. package/src/extensions/views/node-views/video.tsx +19 -0
  105. package/src/icons/Check.tsx +12 -0
  106. package/src/icons/Copy.tsx +15 -0
  107. package/src/icons/DragIcon.tsx +10 -0
  108. package/src/icons/Edit.tsx +12 -0
  109. package/src/icons/Quote.tsx +10 -0
  110. package/src/icons/X.tsx +13 -0
  111. package/src/icons/color/TiptapCalloutError.tsx +25 -0
  112. package/src/icons/color/TiptapCalloutInfo.tsx +18 -0
  113. package/src/icons/color/TiptapCalloutSuccess.tsx +49 -0
  114. package/src/icons/color/TiptapCalloutWarning.tsx +28 -0
  115. package/src/icons/color/index.ts +4 -0
  116. package/src/icons/index.ts +60 -0
  117. package/src/icons/menu/AddImage.tsx +10 -0
  118. package/src/icons/menu/AlignCenter.tsx +10 -0
  119. package/src/icons/menu/AlignLeft.tsx +10 -0
  120. package/src/icons/menu/AlignRight.tsx +10 -0
  121. package/src/icons/menu/ArrowDown.tsx +13 -0
  122. package/src/icons/menu/ArrowLeft.tsx +13 -0
  123. package/src/icons/menu/ArrowRight.tsx +13 -0
  124. package/src/icons/menu/ArrowUp.tsx +13 -0
  125. package/src/icons/menu/Blockquote.tsx +7 -0
  126. package/src/icons/menu/Bold.tsx +10 -0
  127. package/src/icons/menu/BulletList.tsx +10 -0
  128. package/src/icons/menu/CalloutError.tsx +10 -0
  129. package/src/icons/menu/CalloutInfo.tsx +12 -0
  130. package/src/icons/menu/CalloutSuccess.tsx +10 -0
  131. package/src/icons/menu/CalloutWarning.tsx +12 -0
  132. package/src/icons/menu/ClearFormatting.tsx +10 -0
  133. package/src/icons/menu/Code.tsx +10 -0
  134. package/src/icons/menu/CodeBlock.tsx +10 -0
  135. package/src/icons/menu/Copyable.tsx +10 -0
  136. package/src/icons/menu/CustomQuote.tsx +17 -0
  137. package/src/icons/menu/DeleteColumn.tsx +17 -0
  138. package/src/icons/menu/DeleteRow.tsx +17 -0
  139. package/src/icons/menu/Details.tsx +10 -0
  140. package/src/icons/menu/H2.tsx +10 -0
  141. package/src/icons/menu/H3.tsx +10 -0
  142. package/src/icons/menu/H4.tsx +10 -0
  143. package/src/icons/menu/H5.tsx +10 -0
  144. package/src/icons/menu/H6.tsx +10 -0
  145. package/src/icons/menu/Highlight.tsx +13 -0
  146. package/src/icons/menu/HorizontalRule.tsx +7 -0
  147. package/src/icons/menu/Italic.tsx +10 -0
  148. package/src/icons/menu/Link.tsx +10 -0
  149. package/src/icons/menu/LinkOff.tsx +10 -0
  150. package/src/icons/menu/Math.tsx +10 -0
  151. package/src/icons/menu/MergeCells.tsx +10 -0
  152. package/src/icons/menu/MythReality.tsx +10 -0
  153. package/src/icons/menu/OrderedList.tsx +10 -0
  154. package/src/icons/menu/Prompt.tsx +18 -0
  155. package/src/icons/menu/Redo.tsx +14 -0
  156. package/src/icons/menu/SplitCells.tsx +7 -0
  157. package/src/icons/menu/Strikethrough.tsx +10 -0
  158. package/src/icons/menu/Subscript.tsx +10 -0
  159. package/src/icons/menu/Superscript.tsx +10 -0
  160. package/src/icons/menu/Table.tsx +14 -0
  161. package/src/icons/menu/TableColumnHeading.tsx +7 -0
  162. package/src/icons/menu/TableRowHeading.tsx +10 -0
  163. package/src/icons/menu/TextColor.tsx +13 -0
  164. package/src/icons/menu/Underline.tsx +10 -0
  165. package/src/icons/menu/Undo.tsx +14 -0
  166. package/src/icons/menu/Video.tsx +14 -0
  167. package/src/icons/menu/index.ts +49 -0
  168. package/src/render/index.ts +22 -0
  169. package/src/render/static-renderer-config.tsx +206 -0
  170. package/src/render/tiptap-interactivity-provider.tsx +78 -0
  171. package/src/render/tiptap-render.tsx +102 -0
  172. package/src/styles/components.css +10 -0
  173. package/src/styles/editor/base.css +39 -0
  174. package/src/styles/editor/callout-menu.css +81 -0
  175. package/src/styles/editor/controls.css +57 -0
  176. package/src/styles/editor/emoji-picker.css +113 -0
  177. package/src/styles/editor/index.css +9 -0
  178. package/src/styles/editor/menus.css +16 -0
  179. package/src/styles/editor/states.css +43 -0
  180. package/src/styles/editor/typography.css +165 -0
  181. package/src/styles/editor/utilities.css +71 -0
  182. package/src/styles/editor/variables.css +70 -0
  183. package/src/styles/extensions/callout.css +155 -0
  184. package/src/styles/extensions/code-block.css +109 -0
  185. package/src/styles/extensions/commands-list.css +83 -0
  186. package/src/styles/extensions/custom-quote.css +103 -0
  187. package/src/styles/extensions/details.css +87 -0
  188. package/src/styles/extensions/figure.css +66 -0
  189. package/src/styles/extensions/index.css +11 -0
  190. package/src/styles/extensions/math.css +32 -0
  191. package/src/styles/extensions/myth-reality.css +59 -0
  192. package/src/styles/extensions/prompt.css +75 -0
  193. package/src/styles/extensions/table.css +253 -0
  194. package/src/styles/extensions/video.css +53 -0
  195. package/src/styles/styles.css +3 -0
  196. package/src/translations/en.json +158 -0
  197. package/src/translations/ru.json +158 -0
  198. package/src/types/image-handler.ts +33 -0
  199. package/src/types/index.ts +1 -0
  200. package/src/utils/copy-to-clipboard.ts +23 -0
  201. package/src/utils/custom-emoji.ts +174 -0
  202. package/src/utils/detect-video-hosting.ts +106 -0
  203. package/src/utils/get-shared-extensions.ts +45 -0
  204. package/src/utils/get-translations.ts +25 -0
  205. package/src/utils/handle-exit-node.ts +61 -0
  206. package/src/utils/image-src-resolver.ts +74 -0
  207. package/src/utils/index.ts +2 -0
  208. package/src/utils/migrate-legacy-callouts.ts +59 -0
  209. package/src/utils/pluralize.ts +38 -0
package/package.json ADDED
@@ -0,0 +1,92 @@
1
+ {
2
+ "name": "@cyberyozh/text-editor",
3
+ "version": "0.1.1",
4
+ "description": "Rich text editor library based on Tiptap for CyberYozh",
5
+ "author": "CyberYozh",
6
+ "license": "MIT",
7
+ "main": "dist/render.js",
8
+ "types": "dist/render.d.ts",
9
+ "exports": {
10
+ "./render": {
11
+ "types": "./dist/render.d.ts",
12
+ "import": "./dist/render.mjs",
13
+ "require": "./dist/render.js"
14
+ },
15
+ "./render/client": {
16
+ "types": "./dist/render/client.d.ts",
17
+ "import": "./dist/render/client.mjs",
18
+ "require": "./dist/render/client.js"
19
+ },
20
+ "./editor": {
21
+ "types": "./dist/editor.d.ts",
22
+ "import": "./dist/editor.mjs",
23
+ "require": "./dist/editor.js"
24
+ },
25
+ "./styles": "./dist/styles/index.css"
26
+ },
27
+ "files": [
28
+ "dist",
29
+ "src"
30
+ ],
31
+ "publishConfig": {
32
+ "access": "public"
33
+ },
34
+ "scripts": {
35
+ "build": "rm -rf dist && tsup",
36
+ "dev": "tsup --watch",
37
+ "dev:link": "npm run build && npm link && npm run dev",
38
+ "type-check": "tsc --noEmit",
39
+ "lint": "eslint src --ext .ts,.tsx",
40
+ "changeset": "changeset",
41
+ "version": "changeset version",
42
+ "release": "npm run build && changeset publish",
43
+ "release:complete": "git add . && git commit -m \"chore: release v$(node -p \"require('./package.json').version\")\" && npm run release && git push --follow-tags"
44
+ },
45
+ "peerDependencies": {
46
+ "react": "^18.0.0 || ^19.0.0",
47
+ "react-dom": "^18.0.0 || ^19.0.0"
48
+ },
49
+ "dependencies": {
50
+ "@floating-ui/dom": "^1.7.4",
51
+ "@tiptap/core": "^3.10.6",
52
+ "@tiptap/extension-blockquote": "^3.10.6",
53
+ "@tiptap/extension-bubble-menu": "^3.10.6",
54
+ "@tiptap/extension-code": "^3.10.6",
55
+ "@tiptap/extension-code-block-lowlight": "^3.10.6",
56
+ "@tiptap/extension-details": "^3.10.6",
57
+ "@tiptap/extension-drag-handle-react": "^3.10.6",
58
+ "@tiptap/extension-floating-menu": "^3.10.6",
59
+ "@tiptap/extension-hard-break": "^3.10.6",
60
+ "@tiptap/extension-highlight": "^3.10.6",
61
+ "@tiptap/extension-horizontal-rule": "^3.10.6",
62
+ "@tiptap/extension-image": "^3.10.6",
63
+ "@tiptap/extension-link": "^3.10.6",
64
+ "@tiptap/extension-list": "^3.10.6",
65
+ "@tiptap/extension-mathematics": "^3.10.6",
66
+ "@tiptap/extension-node-range": "^3.10.6",
67
+ "@tiptap/extension-subscript": "^3.10.6",
68
+ "@tiptap/extension-superscript": "^3.10.6",
69
+ "@tiptap/extension-table": "^3.10.6",
70
+ "@tiptap/extension-text-align": "^3.10.6",
71
+ "@tiptap/extensions": "^3.10.6",
72
+ "@tiptap/html": "^3.10.6",
73
+ "@tiptap/pm": "^3.10.6",
74
+ "@tiptap/react": "^3.10.6",
75
+ "@tiptap/starter-kit": "^3.10.6",
76
+ "@tiptap/static-renderer": "^3.10.6",
77
+ "@tiptap/suggestion": "^3.10.6",
78
+ "emoji-picker-react": "^4.15.1",
79
+ "highlight.js": "^11.11.0",
80
+ "katex": "^0.16.25",
81
+ "lowlight": "^3.3.0"
82
+ },
83
+ "devDependencies": {
84
+ "@changesets/cli": "^2.29.7",
85
+ "@types/node": "^20",
86
+ "@types/react": "^19",
87
+ "@types/react-dom": "^19",
88
+ "eslint": "^8",
89
+ "tsup": "^8.0.0",
90
+ "typescript": "^5"
91
+ }
92
+ }
@@ -0,0 +1,374 @@
1
+ import type { Editor } from "@tiptap/react";
2
+
3
+ import {
4
+ TiptapMenuLink,
5
+ TiptapMenuAddImage,
6
+ TiptapMenuBlockquote,
7
+ TiptapMenuBulletList,
8
+ TiptapMenuCalloutError,
9
+ TiptapMenuCalloutInfo,
10
+ TiptapMenuCalloutSuccess,
11
+ TiptapMenuCalloutWarning,
12
+ TiptapMenuCodeBlock,
13
+ TiptapMenuCustomQuote,
14
+ TiptapMenuDetails,
15
+ TiptapMenuH2,
16
+ TiptapMenuH3,
17
+ TiptapMenuH4,
18
+ TiptapMenuH5,
19
+ TiptapMenuH6,
20
+ TiptapMenuHorizontalRule,
21
+ TiptapMenuMath,
22
+ TiptapMenuMythReality,
23
+ TiptapMenuOrderedList,
24
+ TiptapMenuPrompt,
25
+ TiptapMenuRedo,
26
+ TiptapMenuSubscript,
27
+ TiptapMenuSuperscript,
28
+ TiptapMenuTable,
29
+ TiptapMenuUndo,
30
+ TiptapMenuVideo,
31
+ } from "../../icons";
32
+
33
+ import type { TiptapTranslations } from "../../utils/get-translations";
34
+
35
+ export interface CommandMenuItem {
36
+ id: string;
37
+ title: string;
38
+ icon: React.ComponentType;
39
+ onClick: (editor: Editor) => void;
40
+ isActive?: (editor: Editor) => boolean;
41
+ isDisabled?: (editor: Editor) => boolean;
42
+ group?: string;
43
+ dialogCommand?: "image" | "video" | "link" | "katex";
44
+ }
45
+
46
+ export const getCommandItems = (
47
+ translations: TiptapTranslations,
48
+ hasImageHandlers: boolean
49
+ ): CommandMenuItem[] => [
50
+ // Undo/Redo group
51
+ // {
52
+ // id: "undo",
53
+ // title: translations.history.undo,
54
+ // icon: TiptapMenuUndo,
55
+ // onClick: (editor) => editor.chain().focus().undo().run(),
56
+ // isDisabled: (editor) => !editor.can().undo(),
57
+ // group: "history",
58
+ // },
59
+ // {
60
+ // id: "redo",
61
+ // title: translations.history.redo,
62
+ // icon: TiptapMenuRedo,
63
+ // onClick: (editor) => editor.chain().focus().redo().run(),
64
+ // isDisabled: (editor) => !editor.can().redo(),
65
+ // group: "history",
66
+ // },
67
+ // Headings group
68
+ {
69
+ id: "heading-2",
70
+ title: translations.headings.h2,
71
+ icon: TiptapMenuH2,
72
+ onClick: (editor) => editor.chain().focus().toggleHeading({ level: 2 }).run(),
73
+ isActive: (editor) => editor.isActive("heading", { level: 2 }),
74
+ group: "headings",
75
+ },
76
+ {
77
+ id: "heading-3",
78
+ title: translations.headings.h3,
79
+ icon: TiptapMenuH3,
80
+ onClick: (editor) => editor.chain().focus().toggleHeading({ level: 3 }).run(),
81
+ isActive: (editor) => editor.isActive("heading", { level: 3 }),
82
+ group: "headings",
83
+ },
84
+ {
85
+ id: "heading-4",
86
+ title: translations.headings.h4,
87
+ icon: TiptapMenuH4,
88
+ onClick: (editor) => editor.chain().focus().toggleHeading({ level: 4 }).run(),
89
+ isActive: (editor) => editor.isActive("heading", { level: 4 }),
90
+ group: "headings",
91
+ },
92
+ {
93
+ id: "heading-5",
94
+ title: translations.headings.h5,
95
+ icon: TiptapMenuH5,
96
+ onClick: (editor) => editor.chain().focus().toggleHeading({ level: 5 }).run(),
97
+ isActive: (editor) => editor.isActive("heading", { level: 5 }),
98
+ group: "headings",
99
+ },
100
+ {
101
+ id: "heading-6",
102
+ title: translations.headings.h6,
103
+ icon: TiptapMenuH6,
104
+ onClick: (editor) => editor.chain().focus().toggleHeading({ level: 6 }).run(),
105
+ isActive: (editor) => editor.isActive("heading", { level: 6 }),
106
+ group: "headings",
107
+ },
108
+ // Image group (conditionally include based on hasImageHandlers)
109
+ ...(hasImageHandlers
110
+ ? [
111
+ {
112
+ id: "image",
113
+ title: translations.media.addImage,
114
+ icon: TiptapMenuAddImage,
115
+ onClick: () => {
116
+ // Handled via dialog in CommandsList
117
+ },
118
+ dialogCommand: "image" as const,
119
+ group: "media",
120
+ },
121
+ ]
122
+ : []),
123
+ {
124
+ id: "video",
125
+ title: translations.media.addVideo,
126
+ icon: TiptapMenuVideo,
127
+ onClick: () => {
128
+ // Handled via dialog in CommandsList
129
+ },
130
+ dialogCommand: "video",
131
+ group: "media",
132
+ },
133
+ {
134
+ id: "link",
135
+ title: translations.media.addLink,
136
+ icon: TiptapMenuLink,
137
+ onClick: () => {
138
+ // Handled via dialog in CommandsList
139
+ },
140
+ dialogCommand: "link",
141
+ group: "media",
142
+ },
143
+ {
144
+ id: "katex",
145
+ title: translations.media.addKatex,
146
+ icon: TiptapMenuMath,
147
+ onClick: () => {
148
+ // Handled via dialog in CommandsList
149
+ },
150
+ dialogCommand: "katex",
151
+ group: "media",
152
+ },
153
+ // Lists group
154
+ {
155
+ id: "bullet-list",
156
+ title: translations.lists.bulletList,
157
+ icon: TiptapMenuBulletList,
158
+ onClick: (editor) => editor.chain().focus().toggleBulletList().run(),
159
+ isActive: (editor) => editor.isActive("bulletList"),
160
+ group: "lists",
161
+ },
162
+ {
163
+ id: "ordered-list",
164
+ title: translations.lists.orderedList,
165
+ icon: TiptapMenuOrderedList,
166
+ onClick: (editor) => editor.chain().focus().toggleOrderedList().run(),
167
+ isActive: (editor) => editor.isActive("orderedList"),
168
+ group: "lists",
169
+ },
170
+ {
171
+ id: "details",
172
+ title: translations.lists.details,
173
+ icon: TiptapMenuDetails,
174
+ onClick: (editor) => editor.chain().focus().setDetails().run(),
175
+ isActive: (editor) => editor.isActive("details"),
176
+ isDisabled: (editor) => !editor.can().setDetails(),
177
+ group: "lists",
178
+ },
179
+ // Callouts group
180
+ {
181
+ id: "callout-success",
182
+ title: translations.blocks.callout,
183
+ icon: TiptapMenuCalloutSuccess,
184
+ onClick: (editor) => {
185
+ editor
186
+ .chain()
187
+ .focus()
188
+ .insertContent({
189
+ type: "callout",
190
+ attrs: { type: "", bgColor: "#F7F7F8" },
191
+ content: [
192
+ {
193
+ type: "paragraph",
194
+ content: [
195
+ {
196
+ type: "text",
197
+ text: " ",
198
+ marks: [{ type: "textStyle", attrs: { color: "" } }],
199
+ },
200
+ ],
201
+ },
202
+ ],
203
+ })
204
+ .run();
205
+ },
206
+ group: "callouts",
207
+ },
208
+
209
+ // Blocks group
210
+ {
211
+ id: "blockquote",
212
+ title: translations.blocks.blockquote,
213
+ icon: TiptapMenuBlockquote,
214
+ onClick: (editor) => editor.chain().focus().toggleBlockquote().run(),
215
+ isActive: (editor) => editor.isActive("blockQuote"),
216
+ group: "blocks",
217
+ },
218
+ {
219
+ id: "custom-quote",
220
+ title: translations.blocks.customQuote,
221
+ icon: TiptapMenuCustomQuote,
222
+ onClick: (editor) =>
223
+ editor
224
+ .chain()
225
+ .focus()
226
+ .insertContent({
227
+ type: "customQuote",
228
+ content: [
229
+ {
230
+ type: "customQuoteText",
231
+ attrs: { "data-placeholder": translations.customQuote.textPlaceholder },
232
+ content: [],
233
+ },
234
+ {
235
+ type: "customQuoteAuthor",
236
+ attrs: { "data-placeholder": translations.customQuote.authorPlaceholder },
237
+ content: [],
238
+ },
239
+ {
240
+ type: "customQuoteSign",
241
+ attrs: { "data-placeholder": translations.customQuote.signPlaceholder },
242
+ content: [],
243
+ },
244
+ ],
245
+ })
246
+ .run(),
247
+ isActive: (editor) => editor.isActive("customQuote"),
248
+ group: "blocks",
249
+ },
250
+ {
251
+ id: "code-block",
252
+ title: translations.blocks.codeBlock,
253
+ icon: TiptapMenuCodeBlock,
254
+ onClick: (editor) => editor.chain().focus().toggleCodeBlock().run(),
255
+ isActive: (editor) => editor.isActive("codeBlock"),
256
+ group: "blocks",
257
+ },
258
+ {
259
+ id: "horizontal-rule",
260
+ title: translations.blocks.horizontalRule,
261
+ icon: TiptapMenuHorizontalRule,
262
+ onClick: (editor) => editor.chain().focus().setHorizontalRule().run(),
263
+ isActive: (editor) => editor.isActive("horizontalRule"),
264
+ group: "blocks",
265
+ },
266
+ {
267
+ id: "myth-reality",
268
+ title: translations.blocks.mythReality,
269
+ icon: TiptapMenuMythReality,
270
+ onClick: (editor) =>
271
+ editor
272
+ .chain()
273
+ .focus()
274
+ .insertContent({
275
+ type: "mythReality",
276
+ content: [
277
+ {
278
+ type: "mythRealityBlock",
279
+ content: [
280
+ {
281
+ type: "paragraph",
282
+ attrs: { class: "myth-reality-block-title" },
283
+ content: [{ type: "text", text: translations.mythReality.myth }],
284
+ },
285
+ {
286
+ type: "paragraph",
287
+ attrs: { class: "myth-reality-block--myth" },
288
+ content: [],
289
+ },
290
+ ],
291
+ },
292
+ {
293
+ type: "mythRealityBlock",
294
+ content: [
295
+ {
296
+ type: "paragraph",
297
+ attrs: { class: "myth-reality-block-title" },
298
+ content: [{ type: "text", text: translations.mythReality.reality }],
299
+ },
300
+ {
301
+ type: "paragraph",
302
+ attrs: { class: "myth-reality-block--reality" },
303
+ content: [],
304
+ },
305
+ ],
306
+ },
307
+ ],
308
+ })
309
+ .run(),
310
+ group: "blocks",
311
+ },
312
+ {
313
+ id: "prompt",
314
+ title: translations.blocks.prompt,
315
+ icon: TiptapMenuPrompt,
316
+ onClick: (editor) =>
317
+ editor
318
+ .chain()
319
+ .focus()
320
+ .insertContent({
321
+ type: "prompt",
322
+ content: [
323
+ {
324
+ type: "promptHeader",
325
+ content: [],
326
+ },
327
+ {
328
+ type: "promptContent",
329
+ content: [],
330
+ },
331
+ ],
332
+ })
333
+ .run(),
334
+ group: "blocks",
335
+ },
336
+ // Text formatting group
337
+ {
338
+ id: "superscript",
339
+ title: translations.textFormatting.superscript,
340
+ icon: TiptapMenuSuperscript,
341
+ onClick: (editor) => editor.chain().focus().toggleSuperscript().run(),
342
+ isActive: (editor) => editor.isActive("superscript"),
343
+ group: "text-formatting",
344
+ },
345
+ {
346
+ id: "subscript",
347
+ title: translations.textFormatting.subscript,
348
+ icon: TiptapMenuSubscript,
349
+ onClick: (editor) => editor.chain().focus().toggleSubscript().run(),
350
+ isActive: (editor) => editor.isActive("subscript"),
351
+ group: "text-formatting",
352
+ },
353
+ // Table group
354
+ {
355
+ id: "table",
356
+ title: translations.table.table,
357
+ icon: TiptapMenuTable,
358
+ onClick: (editor) =>
359
+ editor.chain().focus().insertTable({ rows: 3, cols: 3, withHeaderRow: true }).run(),
360
+ isActive: (editor) => editor.isActive("table"),
361
+ group: "table",
362
+ },
363
+ ];
364
+
365
+ export const filterCommandItems = (items: CommandMenuItem[], query: string): CommandMenuItem[] => {
366
+ if (!query.trim()) return items;
367
+
368
+ const normalizedQuery = query.toLowerCase().replace(/\s+/g, "");
369
+
370
+ return items.filter((item) => {
371
+ const normalizedTitle = item.title.toLowerCase().replace(/\s+/g, "");
372
+ return normalizedTitle.includes(normalizedQuery);
373
+ });
374
+ };
@@ -0,0 +1,146 @@
1
+ import { computePosition, flip, shift } from "@floating-ui/dom";
2
+ import { posToDOMRect } from "@tiptap/core";
3
+ import { ReactRenderer } from "@tiptap/react";
4
+ import { SuggestionKeyDownProps, SuggestionProps } from "@tiptap/suggestion";
5
+
6
+ import { getCommandItems, filterCommandItems, type CommandMenuItem } from "./items";
7
+ import type { TiptapTranslations } from "../../utils/get-translations";
8
+ import { type CommandItem, CommandsList } from "../controls/commands-list";
9
+
10
+ let component: ReactRenderer | null = null;
11
+
12
+ const closeMenu = () => {
13
+ if (component) {
14
+ component.destroy();
15
+ component.element?.remove();
16
+ component = null;
17
+ document.documentElement.classList.remove("command-menu-open");
18
+ }
19
+ };
20
+
21
+ const mapCommandMenuItemToCommandItem = (item: CommandMenuItem): CommandItem => {
22
+ const IconComponent = item.icon;
23
+ return {
24
+ title: item.title,
25
+ icon: <IconComponent />,
26
+ command: ({ editor, range }) => {
27
+ editor.chain().focus().deleteRange(range).run();
28
+ item.onClick(editor);
29
+ closeMenu();
30
+ },
31
+ dialogCommand: item.dialogCommand,
32
+ };
33
+ };
34
+
35
+ export const createSuggestions = (translations: TiptapTranslations, hasImageHandlers: boolean) => ({
36
+ items: ({ query }: { query: string }): CommandItem[] => {
37
+ const allItems = getCommandItems(translations, hasImageHandlers);
38
+ const filtered = filterCommandItems(allItems, query);
39
+ return filtered.map(mapCommandMenuItemToCommandItem);
40
+ },
41
+
42
+ render: () => {
43
+ return {
44
+ onStart: async (props: SuggestionProps & { clientRect?: DOMRect }) => {
45
+ component = new ReactRenderer(CommandsList, {
46
+ props,
47
+ editor: props.editor,
48
+ });
49
+
50
+ if (!component.element || !props.clientRect) {
51
+ return;
52
+ }
53
+
54
+ component.element.style.position = "absolute";
55
+ document.body.appendChild(component.element);
56
+
57
+ const virtualElement = {
58
+ getBoundingClientRect: () =>
59
+ posToDOMRect(
60
+ props.editor.view,
61
+ props.editor.state.selection.from,
62
+ props.editor.state.selection.to
63
+ ),
64
+ };
65
+
66
+ const floatingElement = component.element;
67
+
68
+ try {
69
+ const { x, y, strategy } = await computePosition(virtualElement, floatingElement, {
70
+ strategy: "absolute",
71
+ placement: "bottom-start",
72
+ middleware: [shift({ padding: 8 }), flip()],
73
+ });
74
+
75
+ floatingElement.style.position = strategy;
76
+ floatingElement.style.left = `${x}px`;
77
+ floatingElement.style.top = `${y}px`;
78
+ } catch (error) {
79
+ console.error("Error positioning command menu:", error);
80
+ }
81
+ },
82
+
83
+ onUpdate: (props: SuggestionProps & { clientRect?: DOMRect }) => {
84
+ if (!component) return;
85
+
86
+ component.updateProps(props);
87
+
88
+ if (!props.clientRect || !component.element) {
89
+ return;
90
+ }
91
+
92
+ // Reposition on update
93
+ const virtualElement = {
94
+ getBoundingClientRect: () =>
95
+ posToDOMRect(
96
+ props.editor.view,
97
+ props.editor.state.selection.from,
98
+ props.editor.state.selection.to
99
+ ),
100
+ };
101
+
102
+ const floatingElement = component.element;
103
+
104
+ computePosition(virtualElement, floatingElement, {
105
+ strategy: "absolute",
106
+ placement: "bottom-start",
107
+ middleware: [shift({ padding: 8 }), flip()],
108
+ }).then(({ x, y, strategy }) => {
109
+ if (!floatingElement) return;
110
+ floatingElement.style.position = strategy;
111
+ floatingElement.style.left = `${x}px`;
112
+ floatingElement.style.top = `${y}px`;
113
+ });
114
+ },
115
+
116
+ onKeyDown: (props: SuggestionKeyDownProps) => {
117
+ if (props.event.key === "Escape") {
118
+ closeMenu();
119
+ return true;
120
+ }
121
+
122
+ if (
123
+ component?.ref &&
124
+ typeof (component.ref as Record<string, unknown>).onKeyDown === "function"
125
+ ) {
126
+ return (
127
+ (component.ref as Record<string, unknown>).onKeyDown as (
128
+ props: SuggestionKeyDownProps
129
+ ) => boolean
130
+ )(props);
131
+ }
132
+
133
+ return false;
134
+ },
135
+
136
+ onExit: (props: SuggestionProps) => {
137
+ // HACK to prevent closing on hovering over callout node
138
+ if (props.items.length > 0) {
139
+ return;
140
+ }
141
+
142
+ closeMenu();
143
+ },
144
+ };
145
+ },
146
+ });
@@ -0,0 +1,56 @@
1
+ /* ═══════════════════════════════════════════════════════════ */
2
+ /* BUTTON COMPONENT */
3
+ /* ═══════════════════════════════════════════════════════════ */
4
+
5
+ .button {
6
+ display: inline-flex;
7
+ align-items: center;
8
+ justify-content: center;
9
+ padding: 8px 16px;
10
+ border: none;
11
+ border-radius: 4px;
12
+ font-weight: 500;
13
+ font-size: 14px;
14
+ text-align: center;
15
+ cursor: pointer;
16
+ transition: all 0.2s ease-out;
17
+ user-select: none;
18
+ }
19
+
20
+ .button:disabled {
21
+ cursor: not-allowed;
22
+ opacity: 0.6;
23
+ }
24
+
25
+ /* Primary variant */
26
+ .button-primary {
27
+ background-color: var(--ui-color-primary);
28
+ color: var(--ui-text-white);
29
+ }
30
+
31
+ .button-primary:hover:not(:disabled) {
32
+ background-color: var(--ui-color-primary-dark);
33
+ box-shadow: var(--ui-shadow-primary);
34
+ }
35
+
36
+ /* Secondary variant */
37
+ .button-secondary {
38
+ border: 1px solid var(--ui-secondary-bg-hover);
39
+ background-color: var(--ui-secondary-bg);
40
+ color: var(--ui-text-primary);
41
+ }
42
+
43
+ .button-secondary:hover:not(:disabled) {
44
+ border-color: var(--ui-secondary-bg-hover);
45
+ background-color: var(--ui-secondary-bg-hover);
46
+ }
47
+
48
+ .button-secondary:active:not(:disabled) {
49
+ background-color: var(--ui-secondary-bg-active);
50
+ }
51
+
52
+ /* Loading state */
53
+ .button-loading {
54
+ opacity: 0.7;
55
+ pointer-events: none;
56
+ }
@@ -0,0 +1,35 @@
1
+ 'use client';
2
+
3
+ import { ButtonHTMLAttributes, ReactNode } from 'react';
4
+
5
+ interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
6
+ variant?: 'primary' | 'secondary';
7
+ isLoading?: boolean;
8
+ children: ReactNode;
9
+ }
10
+
11
+ const variantMap = {
12
+ primary: 'button-primary',
13
+ secondary: 'button-secondary',
14
+ };
15
+
16
+ export const Button = ({
17
+ variant = 'primary',
18
+ isLoading = false,
19
+ children,
20
+ disabled,
21
+ className,
22
+ ...props
23
+ }: ButtonProps) => {
24
+ const variantClass = variantMap[variant];
25
+ const loadingClass = isLoading ? 'button-loading' : '';
26
+
27
+ return (
28
+ <button
29
+ className={`button ${variantClass} ${loadingClass} ${className || ''}`}
30
+ disabled={disabled || isLoading}
31
+ {...props}>
32
+ {children}
33
+ </button>
34
+ );
35
+ };
@@ -0,0 +1 @@
1
+ export { Button } from './button';