@cyberyozh/text-editor 0.1.6 → 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 +495 -432
  4. package/dist/editor.js.map +1 -1
  5. package/dist/editor.mjs +495 -432
  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 +56 -43
  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
@@ -81,7 +81,7 @@
81
81
  /* src/editor/components/common-modal/common-modal.css */
82
82
  .modal-overlay {
83
83
  display: flex;
84
- z-index: 10000;
84
+ z-index: 10001;
85
85
  position: fixed;
86
86
  top: 0;
87
87
  right: 0;
@@ -102,6 +102,7 @@
102
102
  }
103
103
  }
104
104
  .modal-content {
105
+ z-index: 10002;
105
106
  position: relative;
106
107
  width: 100%;
107
108
  max-width: 500px;
@@ -879,6 +880,10 @@
879
880
  border: none;
880
881
  outline: none;
881
882
  background: transparent;
883
+ font-weight: 500;
884
+ font-size: 18px;
885
+ line-height: 140%;
886
+ letter-spacing: -0.36px;
882
887
  list-style: none;
883
888
  cursor: pointer;
884
889
  transition: all 150ms ease-in-out;
@@ -948,11 +953,13 @@
948
953
  }
949
954
  .myth-reality-block-content p:first-child {
950
955
  margin-bottom: 8px;
951
- font-weight: 600;
956
+ font-weight: 600 !important;
952
957
  }
958
+ .myth-reality-block-content p,
953
959
  .myth-reality-block-content ul p,
954
960
  .myth-reality-block-content li p {
955
961
  margin-bottom: 0;
962
+ font-weight: 400;
956
963
  font-size: var(--tiptap-font-size);
957
964
  }
958
965
 
@@ -985,6 +992,11 @@
985
992
  flex-direction: column;
986
993
  max-width: 700px;
987
994
  margin: 0 auto 16px;
995
+ font-style: normal;
996
+ font-weight: 500;
997
+ font-size: 18px;
998
+ line-height: 140%;
999
+ letter-spacing: -0.36px;
988
1000
  }
989
1001
  [data-custom-quote-text] .is-empty::before {
990
1002
  position: absolute;
@@ -1070,18 +1082,12 @@
1070
1082
  }
1071
1083
  .callout-node {
1072
1084
  display: flex;
1085
+ align-items: baseline;
1073
1086
  margin: 32px 0;
1074
1087
  padding: 16px;
1075
1088
  gap: 10px;
1076
1089
  border-radius: 8px;
1077
1090
  }
1078
- .callout-node:has(.callout-content h2),
1079
- .callout-node:has(.callout-content h3),
1080
- .callout-node:has(.callout-content h4),
1081
- .callout-node:has(.callout-content h5),
1082
- .callout-node:has(.callout-content h6) {
1083
- align-items: baseline;
1084
- }
1085
1091
  .callout-node p {
1086
1092
  font-size: 16px;
1087
1093
  line-height: 22px;
@@ -1134,6 +1140,8 @@
1134
1140
  .callout-content-wrapper {
1135
1141
  flex: 1;
1136
1142
  min-width: 0;
1143
+ font-size: var(--tiptap-font-size);
1144
+ line-height: 1;
1137
1145
  }
1138
1146
  .callout-content {
1139
1147
  color: inherit;
@@ -1226,6 +1234,7 @@
1226
1234
  flex: 1;
1227
1235
  max-width: 50%;
1228
1236
  color: var(--tiptap-code-block-text);
1237
+ font-size: 14px;
1229
1238
  font-family: var(--tiptap-code-block-font-family);
1230
1239
  }
1231
1240
  .tiptap-code-block-copy-button {
@@ -1305,8 +1314,11 @@
1305
1314
  justify-content: space-between;
1306
1315
  margin-bottom: 16px;
1307
1316
  gap: 8px;
1317
+ font-style: normal;
1308
1318
  font-weight: 600;
1309
- font-size: var(--tiptap-prompt-header-font-size);
1319
+ font-size: var(--tiptap-font-size);
1320
+ line-height: 140%;
1321
+ letter-spacing: -0.36px;
1310
1322
  }
1311
1323
  .prompt-content-wrapper {
1312
1324
  display: flex;
@@ -1416,9 +1428,12 @@
1416
1428
  }
1417
1429
 
1418
1430
  /* src/styles/extensions/commands-list.css */
1431
+ .react-renderer:has(.commands-list-container) {
1432
+ z-index: 10000;
1433
+ }
1419
1434
  .commands-list-container {
1420
1435
  display: flex;
1421
- z-index: 50;
1436
+ position: relative;
1422
1437
  flex-direction: column;
1423
1438
  width: 280px;
1424
1439
  max-height: 300px;
@@ -1500,6 +1515,7 @@
1500
1515
  --tiptap-color-link: #0059df;
1501
1516
  --tiptap-color-primary-error: #f04438;
1502
1517
  --tiptap-color-border: #f2f4f7;
1518
+ --tiptap-color-bg-disabled: #f7f7f8;
1503
1519
  --tiptap-color-divider: #aaabb7;
1504
1520
  --tiptap-color-text-disabled: #aaabb7;
1505
1521
  --tiptap-color-text-placeholder: #aaabb7;
@@ -1546,19 +1562,21 @@
1546
1562
  .tiptap-content-wrapper {
1547
1563
  position: relative;
1548
1564
  margin: 0 !important;
1549
- }
1550
- .tiptap {
1551
1565
  padding: var(--tiptap-spacing);
1552
1566
  border: 1px solid var(--tiptap-color-border);
1553
1567
  border-radius: var(--tiptap-radius);
1554
1568
  font-size: var(--tiptap-font-size);
1555
1569
  font-family: var(--tiptap-font-family);
1556
1570
  }
1557
- .tiptap:focus {
1571
+ .tiptap-content-wrapper:focus {
1558
1572
  border-color: var(--tiptap-color-primary-light);
1559
1573
  outline: none;
1560
1574
  }
1561
- .tiptap-content-wrapper.has-error .tiptap {
1575
+ .tiptap-content-wrapper .ProseMirror {
1576
+ border: none;
1577
+ outline: none;
1578
+ }
1579
+ .tiptap-content-wrapper.has-error {
1562
1580
  border-color: var(--tiptap-color-primary-error);
1563
1581
  }
1564
1582
  .tiptap-error-message {
@@ -1583,10 +1601,10 @@
1583
1601
  font-weight: 400;
1584
1602
  font-size: var(--tiptap-font-size);
1585
1603
  line-height: 140%;
1586
- letter-spacing: 0;
1604
+ letter-spacing: normal;
1587
1605
  }
1588
1606
  .tiptap > * {
1589
- letter-spacing: 0;
1607
+ letter-spacing: normal;
1590
1608
  }
1591
1609
  .tiptap p:not(:last-child),
1592
1610
  .tiptap ul:not(:last-child),
@@ -1594,6 +1612,12 @@
1594
1612
  .tiptap blockquote:not(:last-child) {
1595
1613
  margin-bottom: var(--tiptap-spacing);
1596
1614
  }
1615
+ .tiptap h1 {
1616
+ font-weight: 600;
1617
+ font-size: 40px;
1618
+ line-height: 110%;
1619
+ letter-spacing: 0;
1620
+ }
1597
1621
  .tiptap h2 {
1598
1622
  font-weight: 600;
1599
1623
  font-size: 36px;
@@ -1645,6 +1669,7 @@
1645
1669
  .tiptap ul,
1646
1670
  .tiptap ol {
1647
1671
  padding-left: 32px;
1672
+ font-size: var(--tiptap-font-size);
1648
1673
  }
1649
1674
  .tiptap ul:not(:last-child),
1650
1675
  .tiptap ol:not(:last-child),
@@ -1890,12 +1915,13 @@
1890
1915
  background-color: transparent;
1891
1916
  }
1892
1917
  .tiptap-content-wrapper.is-disabled {
1893
- opacity: 0.6;
1918
+ padding: var(--tiptap-spacing);
1894
1919
  cursor: not-allowed;
1920
+ opacity: 0.6;
1895
1921
  pointer-events: none;
1896
1922
  }
1897
- .tiptap-content-wrapper.is-disabled .ProseMirror {
1898
- background-color: var(--tiptap-color-grey-1, #f5f5f5);
1923
+ .tiptap-content-wrapper.is-disabled {
1924
+ background-color: var(--tiptap-color-bg-disabled);
1899
1925
  }
1900
1926
 
1901
1927
  /* src/styles/editor/callout-menu.css */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cyberyozh/text-editor",
3
- "version": "0.1.6",
3
+ "version": "0.1.7",
4
4
  "description": "Rich text editor library based on Tiptap for CyberYozh",
5
5
  "author": "CyberYozh",
6
6
  "license": "MIT",
@@ -42,6 +42,10 @@
42
42
  "release": "npm run build && changeset publish",
43
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
44
  },
45
+ "peerDependencies": {
46
+ "react": "^18.0.0 || ^19.0.0",
47
+ "react-dom": "^18.0.0 || ^19.0.0"
48
+ },
45
49
  "dependencies": {
46
50
  "@floating-ui/dom": "^1.7.4",
47
51
  "@tiptap/core": "^3.10.6",
@@ -187,7 +187,7 @@ export const getCommandItems = (
187
187
  .focus()
188
188
  .insertContent({
189
189
  type: "callout",
190
- attrs: { type: "", bgColor: "#F7F7F8" },
190
+ attrs: { type: "", bgColor: "#F7F7F8", emoji: "callout-info" },
191
191
  content: [
192
192
  {
193
193
  type: "paragraph",
@@ -1,6 +1,6 @@
1
1
  .modal-overlay {
2
2
  display: flex;
3
- z-index: 10000;
3
+ z-index: 10001;
4
4
  position: fixed;
5
5
  top: 0;
6
6
  right: 0;
@@ -23,6 +23,7 @@
23
23
  }
24
24
 
25
25
  .modal-content {
26
+ z-index: 10002;
26
27
  position: relative;
27
28
  width: 100%;
28
29
  max-width: 500px;
@@ -2,8 +2,6 @@
2
2
 
3
3
  import { createContext, useContext, useState, ReactNode } from "react";
4
4
 
5
- import type { Editor } from "@tiptap/react";
6
-
7
5
  export type DialogCommandType = "image" | "video" | "link" | "katex";
8
6
 
9
7
  interface DialogCommandContextType {
@@ -68,7 +68,6 @@ export const BubbleMenuContent = ({ editor, onLinkClick }: Props) => {
68
68
  } = useEditorState({
69
69
  editor,
70
70
  selector: (ctx) => {
71
- if (!editor) return {};
72
71
  return {
73
72
  canUndo: ctx.editor.can().undo(),
74
73
  canRedo: ctx.editor.can().redo(),
@@ -113,7 +112,12 @@ export const BubbleMenuContent = ({ editor, onLinkClick }: Props) => {
113
112
  };
114
113
 
115
114
  return (
116
- <div className={`bubble-menu ${textColor || highlightColor ? "hide-text-selection" : ""}`}>
115
+ <div
116
+ className={`bubble-menu ${textColor || highlightColor ? "hide-text-selection" : ""}`}
117
+ onMouseDown={(e) => {
118
+ e.preventDefault();
119
+ }}
120
+ >
117
121
  <div className="tiptap-controls-group">
118
122
  <button
119
123
  className="tiptap-control-button"
@@ -78,7 +78,10 @@ export const CommandsList = forwardRef<
78
78
  if (item) {
79
79
  if (item.dialogCommand) {
80
80
  openDialogCommand(item.dialogCommand);
81
+ // Also call command to ensure menu closes
82
+ command(item);
81
83
  } else {
84
+ console.log("open video");
82
85
  command(item);
83
86
  }
84
87
  }
@@ -98,6 +101,10 @@ export const CommandsList = forwardRef<
98
101
  <div
99
102
  ref={listRef}
100
103
  className="commands-list-container"
104
+ onMouseDown={(e) => {
105
+ e.preventDefault();
106
+ e.stopPropagation();
107
+ }}
101
108
  >
102
109
  {items.map((item, index) => (
103
110
  <button
@@ -19,11 +19,7 @@ const SUPPORTED_PLATFORMS = {
19
19
  [VideoHosting.VK]: "VK",
20
20
  };
21
21
 
22
- export const AddVideoDialog = ({
23
- open,
24
- onClose,
25
- onVideoAdd,
26
- }: AddVideoDialogProps) => {
22
+ export const AddVideoDialog = ({ open, onClose, onVideoAdd }: AddVideoDialogProps) => {
27
23
  const translations = useTiptapTranslations();
28
24
  const [url, setUrl] = useState("");
29
25
  const [error, setError] = useState<string>("");
@@ -112,6 +108,7 @@ export const AddVideoDialog = ({
112
108
  <input
113
109
  ref={urlInputRef}
114
110
  id="video-url"
111
+ autoComplete="off"
115
112
  type="text"
116
113
  className="video-dialog-input"
117
114
  placeholder={translations.videoUpload.urlPlaceholder}
@@ -87,6 +87,7 @@ function TiptapContent({
87
87
  attrs: FigureAttrs;
88
88
  pos?: number;
89
89
  } | null>(null);
90
+ const [savedDialogCursorPos, setSavedDialogCursorPos] = useState<number | undefined>(undefined);
90
91
  const { activeDialog, closeDialogCommand, openDialogCommand } = useDialogCommand();
91
92
  const translations = useTiptapTranslations();
92
93
 
@@ -141,19 +142,27 @@ function TiptapContent({
141
142
  [lang]
142
143
  );
143
144
 
145
+ useEffect(() => {
146
+ if (activeDialog && editor && !editor.isDestroyed) {
147
+ const pos = editor.state.selection.from;
148
+ setSavedDialogCursorPos(pos);
149
+ }
150
+ }, [activeDialog, editor]);
151
+
144
152
  const handleCloseDialog = () => {
145
153
  setLinkDialogContext(null);
146
154
  setKatexDialogContext(null);
147
155
  setEditFigureContext(null);
156
+ setSavedDialogCursorPos(undefined);
148
157
  closeDialogCommand();
149
158
  };
150
159
 
151
- const handleImageAdd = createHandleImageAdd(editor, handleCloseDialog);
160
+ const handleImageAdd = createHandleImageAdd(editor, handleCloseDialog, savedDialogCursorPos);
152
161
  const handleImageEdit = createHandleImageEdit(editor, handleCloseDialog, editFigureContext?.pos);
153
- const handleVideoAdd = createHandleVideoAdd(editor, handleCloseDialog);
154
- const handleLinkAdd = createHandleLinkAdd(editor, handleCloseDialog);
162
+ const handleVideoAdd = createHandleVideoAdd(editor, handleCloseDialog, savedDialogCursorPos);
163
+ const handleLinkAdd = createHandleLinkAdd(editor, handleCloseDialog, savedDialogCursorPos);
155
164
  const handleKatexAdd = (latex: string) => {
156
- const katexHandler = createHandleKatexAdd(editor, handleCloseDialog);
165
+ const katexHandler = createHandleKatexAdd(editor, handleCloseDialog, savedDialogCursorPos);
157
166
  katexHandler(latex, katexDialogContext?.pos);
158
167
  };
159
168
 
@@ -165,8 +174,6 @@ function TiptapContent({
165
174
  });
166
175
  }, [content, editor]);
167
176
 
168
- if (!editor) return null;
169
-
170
177
  const shouldBubbleMenuShow = ({ state }: { state: EditorState }) => {
171
178
  if (state.selection instanceof NodeSelection) {
172
179
  return false;
@@ -183,11 +190,13 @@ function TiptapContent({
183
190
  return true;
184
191
  };
185
192
 
193
+ if (!editor) return null;
194
+
186
195
  return (
187
196
  <>
188
- {!isDisabled && <DragHandle editor={editor} />}
197
+ {<DragHandle editor={editor} />}
189
198
  {isEditable && !isDisabled && <TableMenu editor={editor} />}
190
- {!isDisabled && (
199
+ {isEditable && !isDisabled && (
191
200
  <BubbleMenu
192
201
  shouldShow={shouldBubbleMenuShow}
193
202
  editor={editor}
@@ -210,44 +219,48 @@ function TiptapContent({
210
219
  </div>
211
220
  {!!error && <p className="tiptap-error-message">{error}</p>}
212
221
 
213
- {activeDialog === "image" && isEditable && (
214
- <ImageDialog
215
- open={true}
216
- onClose={handleCloseDialog}
217
- onImageAdd={handleImageAdd}
218
- onImageEdit={handleImageEdit}
219
- uploadHandler={imageHandlers?.upload}
220
- getImageById={imageHandlers?.getImage}
221
- initialAttrs={editFigureContext?.attrs || undefined}
222
- mode={editFigureContext ? "edit" : "add"}
223
- />
224
- )}
222
+ {isEditable && (
223
+ <>
224
+ {activeDialog === "image" && (
225
+ <ImageDialog
226
+ open={true}
227
+ onClose={handleCloseDialog}
228
+ onImageAdd={handleImageAdd}
229
+ onImageEdit={handleImageEdit}
230
+ uploadHandler={imageHandlers?.upload}
231
+ getImageById={imageHandlers?.getImage}
232
+ initialAttrs={editFigureContext?.attrs || undefined}
233
+ mode={editFigureContext ? "edit" : "add"}
234
+ />
235
+ )}
225
236
 
226
- {activeDialog === "link" && (
227
- <AddLinkDialog
228
- open={true}
229
- onClose={handleCloseDialog}
230
- onLinkAdd={handleLinkAdd}
231
- initialTitle={linkDialogContext?.selectedText || ""}
232
- initialUrl={linkDialogContext?.url || ""}
233
- />
234
- )}
237
+ {activeDialog === "link" && (
238
+ <AddLinkDialog
239
+ open={true}
240
+ onClose={handleCloseDialog}
241
+ onLinkAdd={handleLinkAdd}
242
+ initialTitle={linkDialogContext?.selectedText || ""}
243
+ initialUrl={linkDialogContext?.url || ""}
244
+ />
245
+ )}
235
246
 
236
- {activeDialog === "video" && (
237
- <AddVideoDialog
238
- open={true}
239
- onClose={handleCloseDialog}
240
- onVideoAdd={handleVideoAdd}
241
- />
242
- )}
247
+ {activeDialog === "video" && (
248
+ <AddVideoDialog
249
+ open={true}
250
+ onClose={handleCloseDialog}
251
+ onVideoAdd={handleVideoAdd}
252
+ />
253
+ )}
243
254
 
244
- {activeDialog === "katex" && (
245
- <AddKatexDialog
246
- open={true}
247
- onClose={handleCloseDialog}
248
- onKatexAdd={handleKatexAdd}
249
- initialContent={katexDialogContext?.latex || ""}
250
- />
255
+ {activeDialog === "katex" && (
256
+ <AddKatexDialog
257
+ open={true}
258
+ onClose={handleCloseDialog}
259
+ onKatexAdd={handleKatexAdd}
260
+ initialContent={katexDialogContext?.latex || ""}
261
+ />
262
+ )}
263
+ </>
251
264
  )}
252
265
  </>
253
266
  );
@@ -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";