@cyberyozh/text-editor 0.1.5 → 0.1.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 (34) hide show
  1. package/dist/editor.css +46 -20
  2. package/dist/editor.css.map +1 -1
  3. package/dist/editor.js +505 -430
  4. package/dist/editor.js.map +1 -1
  5. package/dist/editor.mjs +506 -431
  6. package/dist/editor.mjs.map +1 -1
  7. package/dist/render.js +62 -29
  8. package/dist/render.js.map +1 -1
  9. package/dist/render.mjs +62 -29
  10. package/dist/render.mjs.map +1 -1
  11. package/dist/styles/index.css +46 -20
  12. package/package.json +5 -1
  13. package/src/editor/commands/items.ts +1 -1
  14. package/src/editor/components/common-modal/common-modal.css +2 -1
  15. package/src/editor/context/dialog-command.tsx +0 -2
  16. package/src/editor/controls/bubble-menu-content.tsx +6 -2
  17. package/src/editor/controls/commands-list.tsx +7 -0
  18. package/src/editor/dialogs/add-video-dialog/add-video-dialog.tsx +2 -5
  19. package/src/editor/editor.tsx +69 -44
  20. package/src/editor/handlers/dialog-handlers.ts +118 -36
  21. package/src/extensions/views/components/index.ts +1 -0
  22. package/src/extensions/views/components/table.tsx +29 -0
  23. package/src/render/static-renderer-config.tsx +32 -2
  24. package/src/styles/editor/base.css +7 -5
  25. package/src/styles/editor/states.css +4 -3
  26. package/src/styles/editor/typography.css +10 -2
  27. package/src/styles/editor/variables.css +1 -0
  28. package/src/styles/extensions/callout.css +5 -2
  29. package/src/styles/extensions/code-block.css +2 -1
  30. package/src/styles/extensions/commands-list.css +5 -1
  31. package/src/styles/extensions/custom-quote.css +5 -0
  32. package/src/styles/extensions/details.css +4 -4
  33. package/src/styles/extensions/myth-reality.css +3 -1
  34. package/src/styles/extensions/prompt.css +4 -1
@@ -2,7 +2,11 @@ import type { Editor } from "@tiptap/react";
2
2
 
3
3
  import type { FigureAttrs, TiptapImageAttrs } from "../../types";
4
4
 
5
- export const createHandleImageAdd = (editor: Editor | null, closeDialogCommand: () => void) => {
5
+ export const createHandleImageAdd = (
6
+ editor: Editor | null,
7
+ closeDialogCommand: () => void,
8
+ savedPos?: number
9
+ ) => {
6
10
  return (attrs: TiptapImageAttrs) => {
7
11
  if (!editor) return;
8
12
 
@@ -19,9 +23,27 @@ export const createHandleImageAdd = (editor: Editor | null, closeDialogCommand:
19
23
  tr.replaceWith($from.before(), $from.after(), node);
20
24
  return true;
21
25
  })
26
+ .scrollIntoView()
22
27
  .run();
23
28
  } else {
24
- editor?.chain().focus().setFigure(attrs).run();
29
+ const insertPos = savedPos ?? state.selection.from;
30
+
31
+ editor
32
+ .chain()
33
+ .focus()
34
+ .command(({ tr, state }) => {
35
+ const $pos = state.doc.resolve(insertPos);
36
+ const figureNode = state.schema.nodes.figure.create({ ...attrs });
37
+
38
+ if ($pos.parent.type.name === "paragraph" && $pos.parent.textContent === "") {
39
+ tr.replaceWith($pos.before(), $pos.after(), figureNode);
40
+ } else {
41
+ tr.insert(insertPos, figureNode);
42
+ }
43
+ return true;
44
+ })
45
+ .scrollIntoView()
46
+ .run();
25
47
  }
26
48
  closeDialogCommand();
27
49
  };
@@ -40,30 +62,61 @@ export const createHandleImageEdit = (
40
62
  };
41
63
  };
42
64
 
43
- export const createHandleVideoAdd = (editor: Editor | null, closeDialogCommand: () => void) => {
65
+ export const createHandleVideoAdd = (
66
+ editor: Editor | null,
67
+ closeDialogCommand: () => void,
68
+ savedPos?: number
69
+ ) => {
44
70
  return (url: string) => {
45
71
  if (!editor) return;
46
72
 
47
73
  const { state } = editor;
48
74
  const { $from } = state.selection;
75
+ const text = $from.parent.textContent;
49
76
 
50
- editor
51
- .chain()
52
- .focus()
53
- .command(({ tr }) => {
54
- tr.replaceWith(
55
- $from.before(),
56
- $from.after(),
57
- editor.schema.nodes.video.create({ src: url })
58
- );
59
- return true;
60
- })
61
- .run();
77
+ if (text.lastIndexOf("/") !== -1) {
78
+ editor
79
+ .chain()
80
+ .focus()
81
+ .command(({ tr }) => {
82
+ tr.replaceWith(
83
+ $from.before(),
84
+ $from.after(),
85
+ editor.schema.nodes.video.create({ src: url })
86
+ );
87
+ return true;
88
+ })
89
+ .scrollIntoView()
90
+ .run();
91
+ } else {
92
+ const insertPos = savedPos ?? state.selection.from;
93
+
94
+ editor
95
+ .chain()
96
+ .focus()
97
+ .command(({ tr, state }) => {
98
+ const $pos = state.doc.resolve(insertPos);
99
+ const videoNode = state.schema.nodes.video.create({ src: url });
100
+
101
+ if ($pos.parent.type.name === "paragraph" && $pos.parent.textContent === "") {
102
+ tr.replaceWith($pos.before(), $pos.after(), videoNode);
103
+ } else {
104
+ tr.insert(insertPos, videoNode);
105
+ }
106
+ return true;
107
+ })
108
+ .scrollIntoView()
109
+ .run();
110
+ }
62
111
  closeDialogCommand();
63
112
  };
64
113
  };
65
114
 
66
- export const createHandleLinkAdd = (editor: Editor | null, closeDialogCommand: () => void) => {
115
+ export const createHandleLinkAdd = (
116
+ editor: Editor | null,
117
+ closeDialogCommand: () => void,
118
+ savedPos?: number
119
+ ) => {
67
120
  return (title: string, url: string) => {
68
121
  if (!editor) return;
69
122
 
@@ -81,32 +134,50 @@ export const createHandleLinkAdd = (editor: Editor | null, closeDialogCommand: (
81
134
  })
82
135
  .run();
83
136
  } else {
84
- editor
85
- .chain()
86
- .focus()
87
- .command(({ tr, state }) => {
88
- const { $from } = state.selection;
89
- const text = $from.parent.textContent;
90
- const nodeStart = $from.start();
91
- const beforeCursor = text.substring(0, $from.pos - nodeStart);
92
- const slashIndex = beforeCursor.lastIndexOf("/");
137
+ const { state } = editor;
138
+ const { $from } = state.selection;
139
+ const text = $from.parent.textContent;
140
+ const nodeStart = $from.start();
141
+ const beforeCursor = text.substring(0, $from.pos - nodeStart);
142
+ const slashIndex = beforeCursor.lastIndexOf("/");
93
143
 
94
- if (slashIndex !== -1) {
144
+ if (slashIndex !== -1) {
145
+ editor
146
+ .chain()
147
+ .focus()
148
+ .command(({ tr }) => {
95
149
  tr.delete(nodeStart + slashIndex, $from.pos);
96
- }
97
-
98
- return true;
99
- })
100
- .extendMarkRange("link")
101
- .setLink({ href: url })
102
- .insertContent(title)
103
- .run();
150
+ return true;
151
+ })
152
+ .extendMarkRange("link")
153
+ .setLink({ href: url })
154
+ .insertContent(title)
155
+ .scrollIntoView()
156
+ .run();
157
+ } else {
158
+ const insertPos = savedPos ?? state.selection.from;
159
+ editor
160
+ .chain()
161
+ .focus()
162
+ .command(({ tr }) => {
163
+ const linkMark = editor.schema.marks.link.create({ href: url });
164
+ const textNode = editor.schema.text(title, [linkMark]);
165
+ tr.insert(insertPos, textNode);
166
+ return true;
167
+ })
168
+ .scrollIntoView()
169
+ .run();
170
+ }
104
171
  }
105
172
  closeDialogCommand();
106
173
  };
107
174
  };
108
175
 
109
- export const createHandleKatexAdd = (editor: Editor | null, closeDialogCommand: () => void) => {
176
+ export const createHandleKatexAdd = (
177
+ editor: Editor | null,
178
+ closeDialogCommand: () => void,
179
+ savedPos?: number
180
+ ) => {
110
181
  return (latex: string, pos?: number) => {
111
182
  if (!editor) return;
112
183
 
@@ -132,9 +203,20 @@ export const createHandleKatexAdd = (editor: Editor | null, closeDialogCommand:
132
203
  tr.replaceRangeWith(deleteFrom, deleteTo, inlineMathNode);
133
204
  return true;
134
205
  })
206
+ .scrollIntoView()
135
207
  .run();
136
208
  } else {
137
- editor.chain().insertInlineMath({ latex }).focus().run();
209
+ const insertPos = savedPos ?? state.selection.from;
210
+ editor
211
+ .chain()
212
+ .focus()
213
+ .command(({ tr, state }) => {
214
+ const inlineMathNode = state.schema.nodes.inlineMath.create({ latex });
215
+ tr.insert(insertPos, inlineMathNode);
216
+ return true;
217
+ })
218
+ .scrollIntoView()
219
+ .run();
138
220
  }
139
221
  }
140
222
 
@@ -4,4 +4,5 @@ export { CopyableText } from "./copyable-text";
4
4
  export { CustomQuote, CustomQuoteText, CustomQuoteAuthor, CustomQuoteSign } from "./custom-quote";
5
5
  export { Figure } from "./figure";
6
6
  export { PromptHeader, PromptContent } from "./prompt";
7
+ export { Table } from "./table";
7
8
  export { Video } from "./video";
@@ -0,0 +1,29 @@
1
+ import type { ReactNode } from "react";
2
+
3
+ interface TableProps {
4
+ children: ReactNode;
5
+ colWidths?: number[];
6
+ }
7
+
8
+ /**
9
+ * Table component for static rendering
10
+ * Applies column widths from Tiptap table cells
11
+ */
12
+ export function Table({ children, colWidths = [] }: TableProps) {
13
+ const tableWidth = colWidths.length > 0 ? colWidths.reduce((total, width) => total + width, 0) : null;
14
+
15
+ return (
16
+ <div className="tableWrapper">
17
+ <table style={tableWidth ? { width: `${tableWidth}px` } : undefined}>
18
+ {colWidths.length > 0 && (
19
+ <colgroup>
20
+ {colWidths.map((width, index) => (
21
+ <col key={index} style={{ width: `${width}px` }} />
22
+ ))}
23
+ </colgroup>
24
+ )}
25
+ <tbody>{children}</tbody>
26
+ </table>
27
+ </div>
28
+ );
29
+ }
@@ -24,6 +24,7 @@ import {
24
24
  CustomQuoteSign,
25
25
  PromptHeader,
26
26
  PromptContent,
27
+ Table,
27
28
  Video,
28
29
  Figure,
29
30
  } from "@/extensions/views/components";
@@ -94,11 +95,40 @@ export const getNodeMapping = (getImageById?: (fileId: string) => string | null)
94
95
  video({ node }: any) {
95
96
  return <Video src={node.attrs.src} />;
96
97
  },
98
+ table({ children }: any) {
99
+ let colWidths: number[] = [];
100
+
101
+ if (Array.isArray(children) && children.length > 0) {
102
+ const firstRow = children[0];
103
+
104
+ if (firstRow?.props?.children) {
105
+ const cells = Array.isArray(firstRow.props.children)
106
+ ? firstRow.props.children
107
+ : [firstRow.props.children];
108
+
109
+ colWidths = cells
110
+ .map((cell: any) => cell?.props?.node?.attrs?.colwidth?.[0])
111
+ .filter((width: number | undefined) => width !== undefined);
112
+ }
113
+ }
114
+
115
+ return <Table colWidths={colWidths}>{children}</Table>;
116
+ },
97
117
  figure({ node }: any) {
98
- return <Figure {...node.attrs} getImageById={getImageById} />;
118
+ return (
119
+ <Figure
120
+ {...node.attrs}
121
+ getImageById={getImageById}
122
+ />
123
+ );
99
124
  },
100
125
  image({ node }: any) {
101
- return <Figure {...node.attrs} getImageById={getImageById} />;
126
+ return (
127
+ <Figure
128
+ {...node.attrs}
129
+ getImageById={getImageById}
130
+ />
131
+ );
102
132
  },
103
133
  inlineMath({ node }: any) {
104
134
  const latex = node.attrs.latex || "";
@@ -1,9 +1,6 @@
1
1
  .tiptap-content-wrapper {
2
2
  position: relative;
3
3
  margin: 0 !important;
4
- }
5
-
6
- .tiptap {
7
4
  padding: var(--tiptap-spacing);
8
5
  border: 1px solid var(--tiptap-color-border);
9
6
  border-radius: var(--tiptap-radius);
@@ -11,12 +8,17 @@
11
8
  font-family: var(--tiptap-font-family);
12
9
  }
13
10
 
14
- .tiptap:focus {
11
+ .tiptap-content-wrapper:focus {
15
12
  border-color: var(--tiptap-color-primary-light);
16
13
  outline: none;
17
14
  }
18
15
 
19
- .tiptap-content-wrapper.has-error .tiptap {
16
+ .tiptap-content-wrapper .ProseMirror {
17
+ border: none;
18
+ outline: none;
19
+ }
20
+
21
+ .tiptap-content-wrapper.has-error {
20
22
  border-color: var(--tiptap-color-primary-error);
21
23
  }
22
24
 
@@ -44,11 +44,12 @@
44
44
 
45
45
  /* Disabled state styles */
46
46
  .tiptap-content-wrapper.is-disabled {
47
- opacity: 0.6;
47
+ padding: var(--tiptap-spacing);
48
48
  cursor: not-allowed;
49
+ opacity: 0.6;
49
50
  pointer-events: none;
50
51
  }
51
52
 
52
- .tiptap-content-wrapper.is-disabled .ProseMirror {
53
- background-color: var(--tiptap-color-grey-1, #f5f5f5);
53
+ .tiptap-content-wrapper.is-disabled {
54
+ background-color: var(--tiptap-color-bg-disabled);
54
55
  }
@@ -3,11 +3,11 @@
3
3
  font-weight: 400;
4
4
  font-size: var(--tiptap-font-size);
5
5
  line-height: 140%;
6
- letter-spacing: 0;
6
+ letter-spacing: normal;
7
7
  }
8
8
 
9
9
  .tiptap > * {
10
- letter-spacing: 0;
10
+ letter-spacing: normal;
11
11
  }
12
12
 
13
13
  .tiptap p:not(:last-child),
@@ -17,6 +17,13 @@
17
17
  margin-bottom: var(--tiptap-spacing);
18
18
  }
19
19
 
20
+ .tiptap h1 {
21
+ font-weight: 600;
22
+ font-size: 40px;
23
+ line-height: 110%;
24
+ letter-spacing: 0;
25
+ }
26
+
20
27
  .tiptap h2 {
21
28
  font-weight: 600;
22
29
  font-size: 36px;
@@ -76,6 +83,7 @@
76
83
  .tiptap ul,
77
84
  .tiptap ol {
78
85
  padding-left: 32px;
86
+ font-size: var(--tiptap-font-size);
79
87
  }
80
88
 
81
89
  .tiptap ul:not(:last-child),
@@ -8,6 +8,7 @@
8
8
  --tiptap-color-link: #0059df;
9
9
  --tiptap-color-primary-error: #f04438;
10
10
  --tiptap-color-border: #f2f4f7;
11
+ --tiptap-color-bg-disabled: #f7f7f8;
11
12
  --tiptap-color-divider: #aaabb7;
12
13
  --tiptap-color-text-disabled: #aaabb7;
13
14
  --tiptap-color-text-placeholder: #aaabb7;
@@ -22,19 +22,20 @@
22
22
 
23
23
  .callout-node {
24
24
  display: flex;
25
+ align-items: baseline;
25
26
  margin: 32px 0;
26
27
  padding: 16px;
27
28
  gap: 10px;
28
29
  border-radius: 8px;
29
30
  }
30
-
31
+ /*
31
32
  .callout-node:has(.callout-content h2),
32
33
  .callout-node:has(.callout-content h3),
33
34
  .callout-node:has(.callout-content h4),
34
35
  .callout-node:has(.callout-content h5),
35
36
  .callout-node:has(.callout-content h6) {
36
37
  align-items: baseline;
37
- }
38
+ } */
38
39
 
39
40
  .callout-node p {
40
41
  font-size: 16px;
@@ -103,6 +104,8 @@
103
104
  .callout-content-wrapper {
104
105
  flex: 1;
105
106
  min-width: 0;
107
+ font-size: var(--tiptap-font-size);
108
+ line-height: 1;
106
109
  }
107
110
 
108
111
  .callout-content {
@@ -4,7 +4,7 @@
4
4
  --tiptap-code-block-text: #e0e0e0;
5
5
  --tiptap-code-block-copy-button: #eaecef;
6
6
  --tiptap-code-block-copy-button-hover: #cdc8c8;
7
- --tiptap-code-block-font-family: 'Fira Code', monospace;
7
+ --tiptap-code-block-font-family: "Fira Code", monospace;
8
8
  }
9
9
 
10
10
  .tiptap-code-block-wrapper {
@@ -55,6 +55,7 @@
55
55
  flex: 1;
56
56
  max-width: 50%;
57
57
  color: var(--tiptap-code-block-text);
58
+ font-size: 14px;
58
59
  font-family: var(--tiptap-code-block-font-family);
59
60
  }
60
61
 
@@ -1,6 +1,10 @@
1
+ .react-renderer:has(.commands-list-container) {
2
+ z-index: 10000;
3
+ }
4
+
1
5
  .commands-list-container {
2
6
  display: flex;
3
- z-index: 50;
7
+ position: relative;
4
8
  flex-direction: column;
5
9
  width: 280px;
6
10
  max-height: 300px;
@@ -30,6 +30,11 @@
30
30
  flex-direction: column;
31
31
  max-width: 700px;
32
32
  margin: 0 auto 16px;
33
+ font-style: normal;
34
+ font-weight: 500;
35
+ font-size: 18px;
36
+ line-height: 140%;
37
+ letter-spacing: -0.36px;
33
38
  }
34
39
 
35
40
  [data-custom-quote-text] .is-empty::before {
@@ -1,4 +1,3 @@
1
- /* Editor view with custom button */
2
1
  [data-type="details"] {
3
2
  display: flex;
4
3
  align-items: baseline;
@@ -35,7 +34,6 @@
35
34
  transform: rotate(90deg);
36
35
  }
37
36
 
38
- /* Native HTML details element (server-rendered) */
39
37
  .tiptap details.details {
40
38
  align-items: baseline;
41
39
  margin: 24px 0;
@@ -53,12 +51,15 @@
53
51
  border: none;
54
52
  outline: none;
55
53
  background: transparent;
54
+ font-weight: 500;
55
+ font-size: 18px;
56
+ line-height: 140%;
57
+ letter-spacing: -0.36px;
56
58
  list-style: none;
57
59
  cursor: pointer;
58
60
  transition: all 150ms ease-in-out;
59
61
  }
60
62
 
61
- /* Custom marker styling using ::before since ::marker doesn't support transform */
62
63
  .tiptap details.details summary::before {
63
64
  display: inline-block;
64
65
  flex-shrink: 0;
@@ -72,7 +73,6 @@
72
73
  transform: rotate(90deg);
73
74
  }
74
75
 
75
- /* Hide the default marker */
76
76
  .tiptap details.details summary::-webkit-details-marker {
77
77
  display: none;
78
78
  }
@@ -49,11 +49,13 @@
49
49
 
50
50
  .myth-reality-block-content p:first-child {
51
51
  margin-bottom: 8px;
52
- font-weight: 600;
52
+ font-weight: 600 !important;
53
53
  }
54
54
 
55
+ .myth-reality-block-content p,
55
56
  .myth-reality-block-content ul p,
56
57
  .myth-reality-block-content li p {
57
58
  margin-bottom: 0;
59
+ font-weight: 400;
58
60
  font-size: var(--tiptap-font-size);
59
61
  }
@@ -37,8 +37,11 @@
37
37
  justify-content: space-between;
38
38
  margin-bottom: 16px;
39
39
  gap: 8px;
40
+ font-style: normal;
40
41
  font-weight: 600;
41
- font-size: var(--tiptap-prompt-header-font-size);
42
+ font-size: var(--tiptap-font-size);
43
+ line-height: 140%;
44
+ letter-spacing: -0.36px;
42
45
  }
43
46
 
44
47
  .prompt-content-wrapper {