@teriusu/rich-editor 0.10.8 → 0.11.0

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 (147) hide show
  1. package/dist/components/BubbleToolbar.svelte +40 -36
  2. package/dist/components/BubbleToolbar.svelte.d.ts +3 -0
  3. package/dist/components/BubbleToolbar.svelte.d.ts.map +1 -1
  4. package/dist/components/EmojiSuggestionMenu.svelte +291 -0
  5. package/dist/components/EmojiSuggestionMenu.svelte.d.ts +9 -0
  6. package/dist/components/EmojiSuggestionMenu.svelte.d.ts.map +1 -0
  7. package/dist/components/FixedToolbar.svelte +53 -47
  8. package/dist/components/FixedToolbar.svelte.d.ts +3 -0
  9. package/dist/components/FixedToolbar.svelte.d.ts.map +1 -1
  10. package/dist/components/FloatingHelper.svelte +219 -0
  11. package/dist/components/FloatingHelper.svelte.d.ts +14 -0
  12. package/dist/components/FloatingHelper.svelte.d.ts.map +1 -0
  13. package/dist/components/InputModal.svelte +7 -3
  14. package/dist/components/InputModal.svelte.d.ts +3 -0
  15. package/dist/components/InputModal.svelte.d.ts.map +1 -1
  16. package/dist/components/MathModal.svelte +33 -29
  17. package/dist/components/MathModal.svelte.d.ts +3 -0
  18. package/dist/components/MathModal.svelte.d.ts.map +1 -1
  19. package/dist/components/MediaPickerModal.svelte +7 -3
  20. package/dist/components/MediaPickerModal.svelte.d.ts +3 -0
  21. package/dist/components/MediaPickerModal.svelte.d.ts.map +1 -1
  22. package/dist/components/SlashCommandMenu.svelte +88 -72
  23. package/dist/components/SlashCommandMenu.svelte.d.ts +3 -0
  24. package/dist/components/SlashCommandMenu.svelte.d.ts.map +1 -1
  25. package/dist/components/TableBubbleMenu.svelte +41 -33
  26. package/dist/components/TableBubbleMenu.svelte.d.ts +3 -0
  27. package/dist/components/TableBubbleMenu.svelte.d.ts.map +1 -1
  28. package/dist/components/TipTapEditor.svelte +239 -127
  29. package/dist/components/TipTapEditor.svelte.d.ts +7 -0
  30. package/dist/components/TipTapEditor.svelte.d.ts.map +1 -1
  31. package/dist/extensions/CardBlock.d.ts.map +1 -1
  32. package/dist/extensions/CardBlock.js +6 -4
  33. package/dist/extensions/FileAttachment.d.ts.map +1 -1
  34. package/dist/extensions/FileAttachment.js +2 -1
  35. package/dist/extensions/FixedDetails.d.ts.map +1 -1
  36. package/dist/extensions/FixedDetails.js +5 -3
  37. package/dist/extensions/LegacyBlock.d.ts.map +1 -1
  38. package/dist/extensions/LegacyBlock.js +8 -6
  39. package/dist/extensions/Math.d.ts.map +1 -1
  40. package/dist/extensions/Math.js +5 -2
  41. package/dist/extensions/MbusVideo.d.ts.map +1 -1
  42. package/dist/extensions/MbusVideo.js +73 -9
  43. package/dist/extensions/MediaToolbar.d.ts +41 -0
  44. package/dist/extensions/MediaToolbar.d.ts.map +1 -0
  45. package/dist/extensions/MediaToolbar.js +196 -0
  46. package/dist/extensions/MediaToolbar.test.d.ts +2 -0
  47. package/dist/extensions/MediaToolbar.test.d.ts.map +1 -0
  48. package/dist/extensions/MediaToolbar.test.js +231 -0
  49. package/dist/extensions/OrderedListMarker.d.ts +2 -0
  50. package/dist/extensions/OrderedListMarker.d.ts.map +1 -0
  51. package/dist/extensions/OrderedListMarker.js +114 -0
  52. package/dist/extensions/OrderedListMarker.test.d.ts +2 -0
  53. package/dist/extensions/OrderedListMarker.test.d.ts.map +1 -0
  54. package/dist/extensions/OrderedListMarker.test.js +176 -0
  55. package/dist/extensions/PdfBlock.d.ts.map +1 -1
  56. package/dist/extensions/PdfBlock.js +25 -23
  57. package/dist/extensions/ResizableImage.d.ts.map +1 -1
  58. package/dist/extensions/ResizableImage.js +20 -1
  59. package/dist/extensions/TableKeymap.d.ts +16 -0
  60. package/dist/extensions/TableKeymap.d.ts.map +1 -0
  61. package/dist/extensions/TableKeymap.js +97 -0
  62. package/dist/extensions/TableKeymap.test.d.ts +2 -0
  63. package/dist/extensions/TableKeymap.test.d.ts.map +1 -0
  64. package/dist/extensions/TableKeymap.test.js +147 -0
  65. package/dist/extensions/TabsBlock.d.ts.map +1 -1
  66. package/dist/extensions/TabsBlock.js +12 -9
  67. package/dist/extensions/TiptapMidibus.d.ts.map +1 -1
  68. package/dist/extensions/TiptapMidibus.js +60 -6
  69. package/dist/extensions/UploadSkeleton.d.ts +61 -0
  70. package/dist/extensions/UploadSkeleton.d.ts.map +1 -0
  71. package/dist/extensions/UploadSkeleton.js +233 -0
  72. package/dist/extensions/UploadSkeleton.test.d.ts +2 -0
  73. package/dist/extensions/UploadSkeleton.test.d.ts.map +1 -0
  74. package/dist/extensions/UploadSkeleton.test.js +128 -0
  75. package/dist/extensions/VideoEmbed.d.ts +8 -0
  76. package/dist/extensions/VideoEmbed.d.ts.map +1 -1
  77. package/dist/extensions/VideoEmbed.js +226 -45
  78. package/dist/extensions/VideoEmbed.test.js +131 -2
  79. package/dist/i18n/en.d.ts +3 -0
  80. package/dist/i18n/en.d.ts.map +1 -0
  81. package/dist/i18n/en.js +191 -0
  82. package/dist/i18n/es.d.ts +3 -0
  83. package/dist/i18n/es.d.ts.map +1 -0
  84. package/dist/i18n/es.js +191 -0
  85. package/dist/i18n/index.d.ts +43 -0
  86. package/dist/i18n/index.d.ts.map +1 -0
  87. package/dist/i18n/index.js +109 -0
  88. package/dist/i18n/index.test.d.ts +2 -0
  89. package/dist/i18n/index.test.d.ts.map +1 -0
  90. package/dist/i18n/index.test.js +173 -0
  91. package/dist/i18n/ja.d.ts +3 -0
  92. package/dist/i18n/ja.d.ts.map +1 -0
  93. package/dist/i18n/ja.js +191 -0
  94. package/dist/i18n/ko.d.ts +198 -0
  95. package/dist/i18n/ko.d.ts.map +1 -0
  96. package/dist/i18n/ko.js +215 -0
  97. package/dist/i18n/zh-hans.d.ts +3 -0
  98. package/dist/i18n/zh-hans.d.ts.map +1 -0
  99. package/dist/i18n/zh-hans.js +191 -0
  100. package/dist/i18n/zh-hant.d.ts +3 -0
  101. package/dist/i18n/zh-hant.d.ts.map +1 -0
  102. package/dist/i18n/zh-hant.js +192 -0
  103. package/dist/index.d.ts +7 -1
  104. package/dist/index.d.ts.map +1 -1
  105. package/dist/index.js +13 -1
  106. package/dist/static/highlight.d.ts +13 -0
  107. package/dist/static/highlight.d.ts.map +1 -0
  108. package/dist/static/highlight.js +99 -0
  109. package/dist/static/index.d.ts +33 -0
  110. package/dist/static/index.d.ts.map +1 -0
  111. package/dist/static/index.js +367 -0
  112. package/dist/static/index.test.d.ts +2 -0
  113. package/dist/static/index.test.d.ts.map +1 -0
  114. package/dist/static/index.test.js +194 -0
  115. package/dist/static/tokenizer.d.ts +52 -0
  116. package/dist/static/tokenizer.d.ts.map +1 -0
  117. package/dist/static/tokenizer.js +289 -0
  118. package/dist/styles/editor.css +1 -1
  119. package/dist/utils/emoji-data.d.ts +15 -0
  120. package/dist/utils/emoji-suggestion.d.ts +48 -0
  121. package/dist/utils/emoji-suggestion.d.ts.map +1 -0
  122. package/dist/utils/emoji-suggestion.js +51 -0
  123. package/dist/utils/emoji-suggestion.test.d.ts +2 -0
  124. package/dist/utils/emoji-suggestion.test.d.ts.map +1 -0
  125. package/dist/utils/emoji-suggestion.test.js +117 -0
  126. package/dist/utils/floating-helper.d.ts +60 -0
  127. package/dist/utils/floating-helper.d.ts.map +1 -0
  128. package/dist/utils/floating-helper.js +63 -0
  129. package/dist/utils/floating-helper.test.d.ts +2 -0
  130. package/dist/utils/floating-helper.test.d.ts.map +1 -0
  131. package/dist/utils/floating-helper.test.js +141 -0
  132. package/dist/utils/guard-vscode-paste.d.ts +19 -0
  133. package/dist/utils/guard-vscode-paste.d.ts.map +1 -0
  134. package/dist/utils/guard-vscode-paste.js +46 -0
  135. package/dist/utils/guard-vscode-paste.test.d.ts +2 -0
  136. package/dist/utils/guard-vscode-paste.test.d.ts.map +1 -0
  137. package/dist/utils/guard-vscode-paste.test.js +86 -0
  138. package/dist/utils/media-size.d.ts +29 -0
  139. package/dist/utils/media-size.d.ts.map +1 -0
  140. package/dist/utils/media-size.js +90 -0
  141. package/dist/utils/resize.d.ts +12 -0
  142. package/dist/utils/resize.d.ts.map +1 -1
  143. package/dist/utils/resize.js +5 -5
  144. package/dist/utils/sanitize.d.ts +2 -0
  145. package/dist/utils/sanitize.d.ts.map +1 -1
  146. package/dist/utils/sanitize.js +28 -3
  147. package/package.json +8 -1
@@ -0,0 +1,141 @@
1
+ import { describe, it, expect, afterEach } from "vitest";
2
+ import { readFileSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import { Editor } from "@tiptap/core";
5
+ import StarterKit from "@tiptap/starter-kit";
6
+ import { MathInline, MathDisplay } from "../extensions/Math";
7
+ import { isEmptyRootParagraph, floatingHelperVisible, helperInsertMath, helperInsertCodeBlock, FLOATING_HELPER_ON_CLASS, } from "./floating-helper";
8
+ /*
9
+ * 헬퍼의 계약 넷을 문서 상태로 확인한다 — 뜨는 자리(포커스된 최상위 빈 문단)에서만
10
+ * 뜨고, 읽기 전용·비포커스·팝업 열림(suppressed)이면 안 뜨고, 슬래시·이모지 트리거
11
+ * 텍스트가 있으면 (블록이 비지 않았으므로) 안 뜨고, 두 버튼은 슬래시 메뉴 항목과
12
+ * 같은 결과를 만든다.
13
+ */
14
+ describe("빈 줄 플로팅 헬퍼", () => {
15
+ let editor;
16
+ function createEditor(content = "<p></p>", opts = {}) {
17
+ editor = new Editor({
18
+ element: document.createElement("div"),
19
+ extensions: [
20
+ StarterKit,
21
+ opts.promptMath ? MathInline.configure({ promptMath: opts.promptMath }) : MathInline,
22
+ opts.promptMath ? MathDisplay.configure({ promptMath: opts.promptMath }) : MathDisplay,
23
+ ],
24
+ content,
25
+ editable: opts.editable ?? true,
26
+ });
27
+ return editor;
28
+ }
29
+ afterEach(() => {
30
+ editor?.destroy();
31
+ });
32
+ /** 포커스·비억제 기준의 최종 판정 — 개별 축은 아래에서 따로 끈다. */
33
+ const shown = () => floatingHelperVisible(editor, { focused: true, suppressed: false });
34
+ const flush = () => new Promise((resolve) => setTimeout(resolve, 0));
35
+ function findNode(node, type) {
36
+ if (node.type === type)
37
+ return node;
38
+ for (const child of node.content ?? []) {
39
+ const hit = findNode(child, type);
40
+ if (hit)
41
+ return hit;
42
+ }
43
+ return null;
44
+ }
45
+ const docJson = () => editor.getJSON();
46
+ describe("표시 조건", () => {
47
+ it("포커스된 최상위 빈 문단이면 뜬다", () => {
48
+ createEditor("<p></p>");
49
+ expect(shown()).toBe(true);
50
+ });
51
+ it("문서 끝의 빈 문단에서도 뜬다 (첫 줄 한정이 아니다)", () => {
52
+ createEditor("<p>내용</p><p></p>");
53
+ editor.commands.setTextSelection(editor.state.doc.content.size - 1);
54
+ expect(shown()).toBe(true);
55
+ });
56
+ it("글자가 있으면 안 뜬다", () => {
57
+ createEditor("<p>안녕</p>");
58
+ expect(shown()).toBe(false);
59
+ });
60
+ it("빈 제목에서는 안 뜬다 — '제목 N' placeholder 몫 (main 과 의도적 차이)", () => {
61
+ createEditor("<h1></h1>");
62
+ expect(shown()).toBe(false);
63
+ });
64
+ it("코드블록 안 빈 줄에서는 안 뜬다", () => {
65
+ createEditor("<pre><code></code></pre>");
66
+ expect(shown()).toBe(false);
67
+ });
68
+ it("중첩 블록(인용문) 안의 빈 문단에서는 안 뜬다 — 최상위 깊이 한정", () => {
69
+ createEditor("<blockquote><p></p></blockquote>");
70
+ expect(editor.state.selection.$anchor.depth).toBeGreaterThan(1);
71
+ expect(shown()).toBe(false);
72
+ });
73
+ it("노드 선택(커서가 접혀 있지 않음)이면 안 뜬다", () => {
74
+ createEditor("<p></p><hr>");
75
+ editor.commands.setNodeSelection(2);
76
+ expect(shown()).toBe(false);
77
+ });
78
+ it("isEmptyRootParagraph 는 텍스트만이 아니라 내용 유무를 본다", () => {
79
+ // hardBreak 는 textContent 가 "" 지만 빈 줄이 아니다 — tiptap 기본
80
+ // shouldShow(textContent 검사)와 달리 content.size 로 걸러진다.
81
+ createEditor("<p><br></p>");
82
+ expect(isEmptyRootParagraph(editor.state)).toBe(false);
83
+ });
84
+ });
85
+ describe("표시 게이트", () => {
86
+ it("읽기 전용이면 절대 안 뜬다", () => {
87
+ createEditor("<p></p>", { editable: false });
88
+ expect(floatingHelperVisible(editor, { focused: true, suppressed: false })).toBe(false);
89
+ });
90
+ it("포커스가 없으면 안 뜬다", () => {
91
+ createEditor("<p></p>");
92
+ expect(floatingHelperVisible(editor, { focused: false, suppressed: false })).toBe(false);
93
+ });
94
+ it("suppressed(슬래시·이모지 메뉴 열림)면 안 뜬다", () => {
95
+ createEditor("<p></p>");
96
+ expect(floatingHelperVisible(editor, { focused: true, suppressed: true })).toBe(false);
97
+ });
98
+ it("슬래시 트리거 텍스트(/)가 있으면 블록이 비지 않아 안 뜬다", () => {
99
+ createEditor("<p>/</p>");
100
+ editor.commands.setTextSelection(2);
101
+ expect(shown()).toBe(false);
102
+ });
103
+ it("이모지 트리거 텍스트(:sm)가 있으면 블록이 비지 않아 안 뜬다", () => {
104
+ createEditor("<p>:sm</p>");
105
+ editor.commands.setTextSelection(4);
106
+ expect(shown()).toBe(false);
107
+ });
108
+ });
109
+ describe("버튼 커맨드", () => {
110
+ it("수식 — 프롬프트가 돌려준 LaTeX 로 수식 블록을 넣는다 (슬래시 '수식' 과 동일)", async () => {
111
+ createEditor("<p></p>", { promptMath: async () => "a^2" });
112
+ expect(helperInsertMath(editor)).toBe(true);
113
+ await flush();
114
+ const math = findNode(docJson(), "math_display");
115
+ expect(math).not.toBeNull();
116
+ expect(math.content?.[0]?.text).toBe("a^2");
117
+ });
118
+ it("수식 — 프롬프트를 취소하면 아무것도 넣지 않는다", async () => {
119
+ createEditor("<p></p>", { promptMath: async () => null });
120
+ helperInsertMath(editor);
121
+ await flush();
122
+ expect(findNode(docJson(), "math_display")).toBeNull();
123
+ expect(findNode(docJson(), "math_inline")).toBeNull();
124
+ });
125
+ it("코드 — 현재 빈 문단을 코드블록으로 바꾼다 (슬래시 '코드' 와 동일)", () => {
126
+ createEditor("<p></p>");
127
+ expect(helperInsertCodeBlock(editor)).toBe(true);
128
+ expect(editor.state.doc.firstChild?.type.name).toBe("codeBlock");
129
+ });
130
+ });
131
+ describe("placeholder 겹침 계약", () => {
132
+ it("editor.css 에 숨김 규칙이 클래스 상수와 같은 이름으로 있다", () => {
133
+ // 컴포넌트는 이 클래스를 `.tiptap` 에 토글하고, CSS 가 그 동안 placeholder 를
134
+ // 숨긴다. 이름이 갈리면 문구가 겹쳐도 아무 테스트도 안 깨지므로 여기서 묶는다.
135
+ // happy-dom 에서는 `import.meta.url` 이 http 스킴이라 파일 경로로 못 쓴다 —
136
+ // vitest 는 패키지 루트에서 돌므로 cwd 기준으로 읽는다.
137
+ const css = readFileSync(join(process.cwd(), "src/styles/editor.css"), "utf8");
138
+ expect(css).toContain(`.tiptap.${FLOATING_HELPER_ON_CLASS} .is-empty::before`);
139
+ });
140
+ });
141
+ });
@@ -0,0 +1,19 @@
1
+ import { Plugin } from "@tiptap/pm/state";
2
+ /**
3
+ * 업스트림 코드블록의 VS Code 붙여넣기 처리를 **메타데이터 검증으로 감싼다**.
4
+ *
5
+ * ⚠️ 왜 필요한가 — VS Code 계열 앱은 복사할 때 클립보드에 `vscode-editor-data`(JSON)를
6
+ * 싣는데, `@tiptap/extension-code-block` 의 `codeBlockVSCodeHandler` 는 이것을 **검증 없이
7
+ * `JSON.parse`** 한다(dist/index.js `const vscodeData = vscode ? JSON.parse(vscode) : ...`).
8
+ * 다른 앱이 같은 타입에 깨진 값을 실어 보내면 붙여넣기 한 번에 handlePaste 가 던지고,
9
+ * 그 뒤로 에디터가 멈춘다.
10
+ *
11
+ * 업스트림 플러그인을 갈아 끼우지 않고 **그대로 감싼다** — 붙여넣기 동작과 플러그인
12
+ * 정체(키·나머지 props)는 유지하고, 메타데이터가 멀쩡할 때만 원래 핸들러에 넘긴다.
13
+ * 메타데이터가 깨졌으면 false 를 돌려 **일반 텍스트 붙여넣기로** 내려보낸다.
14
+ *
15
+ * handlePaste 가 없는 플러그인은 손대지 않고 그대로 돌려주므로, 코드블록의 부모 플러그인
16
+ * 전부에 일괄로 씌워도 안전하다.
17
+ */
18
+ export declare function guardVSCodePaste(plugin: Plugin): Plugin;
19
+ //# sourceMappingURL=guard-vscode-paste.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"guard-vscode-paste.d.ts","sourceRoot":"","sources":["../../src/utils/guard-vscode-paste.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAE1C;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CA8BvD"}
@@ -0,0 +1,46 @@
1
+ import { Plugin } from "@tiptap/pm/state";
2
+ /**
3
+ * 업스트림 코드블록의 VS Code 붙여넣기 처리를 **메타데이터 검증으로 감싼다**.
4
+ *
5
+ * ⚠️ 왜 필요한가 — VS Code 계열 앱은 복사할 때 클립보드에 `vscode-editor-data`(JSON)를
6
+ * 싣는데, `@tiptap/extension-code-block` 의 `codeBlockVSCodeHandler` 는 이것을 **검증 없이
7
+ * `JSON.parse`** 한다(dist/index.js `const vscodeData = vscode ? JSON.parse(vscode) : ...`).
8
+ * 다른 앱이 같은 타입에 깨진 값을 실어 보내면 붙여넣기 한 번에 handlePaste 가 던지고,
9
+ * 그 뒤로 에디터가 멈춘다.
10
+ *
11
+ * 업스트림 플러그인을 갈아 끼우지 않고 **그대로 감싼다** — 붙여넣기 동작과 플러그인
12
+ * 정체(키·나머지 props)는 유지하고, 메타데이터가 멀쩡할 때만 원래 핸들러에 넘긴다.
13
+ * 메타데이터가 깨졌으면 false 를 돌려 **일반 텍스트 붙여넣기로** 내려보낸다.
14
+ *
15
+ * handlePaste 가 없는 플러그인은 손대지 않고 그대로 돌려주므로, 코드블록의 부모 플러그인
16
+ * 전부에 일괄로 씌워도 안전하다.
17
+ */
18
+ export function guardVSCodePaste(plugin) {
19
+ const handlePaste = plugin.spec.props?.handlePaste;
20
+ if (!handlePaste)
21
+ return plugin;
22
+ return new Plugin({
23
+ ...plugin.spec,
24
+ props: {
25
+ ...plugin.spec.props,
26
+ handlePaste(view, event, slice) {
27
+ const metadata = event.clipboardData?.getData("vscode-editor-data");
28
+ if (metadata) {
29
+ try {
30
+ const data = JSON.parse(metadata);
31
+ if (!data ||
32
+ typeof data !== "object" ||
33
+ !("mode" in data) ||
34
+ typeof data.mode !== "string") {
35
+ return false;
36
+ }
37
+ }
38
+ catch {
39
+ return false;
40
+ }
41
+ }
42
+ return handlePaste.call(this, view, event, slice);
43
+ }
44
+ }
45
+ });
46
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=guard-vscode-paste.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"guard-vscode-paste.test.d.ts","sourceRoot":"","sources":["../../src/utils/guard-vscode-paste.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,86 @@
1
+ import { describe, it, expect, afterEach } from "vitest";
2
+ import { Editor } from "@tiptap/core";
3
+ import StarterKit from "@tiptap/starter-kit";
4
+ import CodeBlockLowlight from "@tiptap/extension-code-block-lowlight";
5
+ import { Plugin } from "@tiptap/pm/state";
6
+ import { Slice } from "@tiptap/pm/model";
7
+ import { lowlight } from "./lowlight";
8
+ import { guardVSCodePaste } from "./guard-vscode-paste";
9
+ /*
10
+ * 업스트림 `codeBlockVSCodeHandler` 는 클립보드의 `vscode-editor-data` 를 try/catch 없이
11
+ * `JSON.parse` 한다 — 깨진 메타데이터가 실리면 붙여넣기 한 번에 던진다. 이 가드는
12
+ * 메타데이터를 먼저 검증하고, 멀쩡할 때만 원래 핸들러에 넘긴다.
13
+ *
14
+ * `TipTapEditor.svelte` 의 `GuardedCodeBlockLowlight` 와 같은 조합으로 에디터를 세워,
15
+ * 실제 플러그인 사슬(someProp)로 붙여넣기를 흉내 낸다.
16
+ */
17
+ const GuardedCodeBlockLowlight = CodeBlockLowlight.extend({
18
+ addProseMirrorPlugins() {
19
+ return (this.parent?.() ?? []).map((plugin) => guardVSCodePaste(plugin));
20
+ }
21
+ });
22
+ describe("guardVSCodePaste", () => {
23
+ let editor;
24
+ function createEditor(content = "<p></p>") {
25
+ editor = new Editor({
26
+ element: document.createElement("div"),
27
+ extensions: [
28
+ StarterKit.configure({ codeBlock: false }),
29
+ GuardedCodeBlockLowlight.configure({ lowlight, defaultLanguage: "cpp" })
30
+ ],
31
+ content
32
+ });
33
+ return editor;
34
+ }
35
+ afterEach(() => {
36
+ editor?.destroy();
37
+ });
38
+ /** 클립보드 타입 → 값 맵으로 붙여넣기를 흉내 낸다. 핸들러는 getData 만 쓴다. */
39
+ function paste(data) {
40
+ const event = {
41
+ clipboardData: { getData: (type) => data[type] ?? "" }
42
+ };
43
+ return (editor.view.someProp("handlePaste", (f) => f(editor.view, event, Slice.empty)) ?? false);
44
+ }
45
+ function hasCodeBlock() {
46
+ return (editor.getJSON().content ?? []).some((node) => node.type === "codeBlock");
47
+ }
48
+ it("깨진 JSON 메타데이터가 실려도 던지지 않는다", () => {
49
+ createEditor();
50
+ let handled = true;
51
+ expect(() => {
52
+ handled = paste({
53
+ "vscode-editor-data": "{broken json",
54
+ "text/plain": "print(1)"
55
+ });
56
+ }).not.toThrow();
57
+ expect(handled).toBe(false);
58
+ expect(hasCodeBlock()).toBe(false);
59
+ });
60
+ it("mode 가 문자열이 아니면 코드블록을 만들지 않는다", () => {
61
+ createEditor();
62
+ expect(paste({ "vscode-editor-data": '{"mode":42}', "text/plain": "x" })).toBe(false);
63
+ expect(paste({ "vscode-editor-data": "null", "text/plain": "x" })).toBe(false);
64
+ expect(paste({ "vscode-editor-data": '"python"', "text/plain": "x" })).toBe(false);
65
+ expect(hasCodeBlock()).toBe(false);
66
+ });
67
+ it("정상 메타데이터는 그대로 코드블록이 된다 — 가드가 동작을 바꾸지 않는다", () => {
68
+ createEditor();
69
+ const handled = paste({
70
+ "vscode-editor-data": '{"mode":"python"}',
71
+ "text/plain": "print(1)"
72
+ });
73
+ expect(handled).toBe(true);
74
+ const block = (editor.getJSON().content ?? []).find((node) => node.type === "codeBlock");
75
+ expect(block?.attrs?.language).toBe("python");
76
+ });
77
+ it("메타데이터가 없으면 업스트림 판단(언어 없음 → 미처리)에 맡긴다", () => {
78
+ createEditor();
79
+ expect(paste({ "text/plain": "plain text" })).toBe(false);
80
+ expect(hasCodeBlock()).toBe(false);
81
+ });
82
+ it("handlePaste 없는 플러그인은 정체 그대로 돌려준다", () => {
83
+ const plain = new Plugin({});
84
+ expect(guardVSCodePaste(plain)).toBe(plain);
85
+ });
86
+ });
@@ -0,0 +1,29 @@
1
+ /**
2
+ * 미디어 블록(영상·이미지)의 크기·정렬 속성 정규화.
3
+ *
4
+ * main 정올 `plugin/resize/index.ts` 의 `normalize*` 무리를 필요한 만큼만 떼어 재작성했다.
5
+ * 원칙은 이미지 폭(`ResizableImage`)과 같다 — **파싱은 관대하게, 쓰기는 좁게.**
6
+ * 읽을 때는 `420`·`420px`·`16:9`·`16/9`·`1.78` 을 다 받고, 저장은 언제나 한 가지 꼴이다:
7
+ * 높이는 단위 없는 px 정수, 비율은 `W:H` 문자열, 정렬은 `left|center|right`.
8
+ *
9
+ * ⚠️ **인라인 `style` 로 저장하지 않는 이유**(사고 이력): 살균기가 `img` 의 `style` 을
10
+ * 지워서 정적 렌더와 에디터의 크기가 어긋난다. 크기·정렬은 전부 **속성**으로 간다.
11
+ */
12
+ export type MediaAlign = "left" | "center" | "right";
13
+ /** `left`·`center`·`right` 만 통과. 대소문자·여백은 눈감아 준다. */
14
+ export declare function normalizeMediaAlign(value: unknown): MediaAlign | null;
15
+ /**
16
+ * 어떤 꼴로 들어오든 단위 없는 px 정수 높이로 만든다. 못 읽으면 `null`.
17
+ * `%`·`auto` 는 버린다 — 높이를 %로 저장하면 기준이 없어 어디서나 다르게 풀린다.
18
+ */
19
+ export declare function normalizeMediaHeight(value: unknown): number | null;
20
+ /**
21
+ * 비율을 정준형 `W:H` 문자열로 만든다. `16:9`·`16/9`·`1.78` 을 받고, 숫자 하나면
22
+ * `W:1` 꼴로 세운다. 못 읽으면 `null`.
23
+ */
24
+ export declare function normalizeMediaRatio(value: unknown): string | null;
25
+ /** `W:H` → 숫자 비율(W/H). CSS `aspect-ratio` 계산·비교용. */
26
+ export declare function mediaRatioValue(value: unknown): number | null;
27
+ /** `16:9` → `16 / 9` — CSS `aspect-ratio` 값 꼴. */
28
+ export declare function mediaRatioCss(value: unknown): string | null;
29
+ //# sourceMappingURL=media-size.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"media-size.d.ts","sourceRoot":"","sources":["../../src/utils/media-size.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,QAAQ,GAAG,OAAO,CAAC;AAIrD,qDAAqD;AACrD,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,GAAG,UAAU,GAAG,IAAI,CAMrE;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAYlE;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAkBjE;AAED,qDAAqD;AACrD,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAM7D;AAED,kDAAkD;AAClD,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAI3D"}
@@ -0,0 +1,90 @@
1
+ /**
2
+ * 미디어 블록(영상·이미지)의 크기·정렬 속성 정규화.
3
+ *
4
+ * main 정올 `plugin/resize/index.ts` 의 `normalize*` 무리를 필요한 만큼만 떼어 재작성했다.
5
+ * 원칙은 이미지 폭(`ResizableImage`)과 같다 — **파싱은 관대하게, 쓰기는 좁게.**
6
+ * 읽을 때는 `420`·`420px`·`16:9`·`16/9`·`1.78` 을 다 받고, 저장은 언제나 한 가지 꼴이다:
7
+ * 높이는 단위 없는 px 정수, 비율은 `W:H` 문자열, 정렬은 `left|center|right`.
8
+ *
9
+ * ⚠️ **인라인 `style` 로 저장하지 않는 이유**(사고 이력): 살균기가 `img` 의 `style` 을
10
+ * 지워서 정적 렌더와 에디터의 크기가 어긋난다. 크기·정렬은 전부 **속성**으로 간다.
11
+ */
12
+ const ALIGN_VALUES = ["left", "center", "right"];
13
+ /** `left`·`center`·`right` 만 통과. 대소문자·여백은 눈감아 준다. */
14
+ export function normalizeMediaAlign(value) {
15
+ if (typeof value !== "string")
16
+ return null;
17
+ const normalized = value.trim().toLowerCase();
18
+ return ALIGN_VALUES.includes(normalized)
19
+ ? normalized
20
+ : null;
21
+ }
22
+ /**
23
+ * 어떤 꼴로 들어오든 단위 없는 px 정수 높이로 만든다. 못 읽으면 `null`.
24
+ * `%`·`auto` 는 버린다 — 높이를 %로 저장하면 기준이 없어 어디서나 다르게 풀린다.
25
+ */
26
+ export function normalizeMediaHeight(value) {
27
+ if (value == null)
28
+ return null;
29
+ if (typeof value === "number") {
30
+ return Number.isFinite(value) && value > 0 ? Math.round(value) : null;
31
+ }
32
+ const raw = String(value).trim();
33
+ if (!raw)
34
+ return null;
35
+ const m = /^(\d+(?:\.\d+)?)(px)?$/i.exec(raw);
36
+ if (!m)
37
+ return null;
38
+ const n = Number.parseFloat(m[1]);
39
+ if (!Number.isFinite(n) || n <= 0)
40
+ return null;
41
+ return Math.round(n);
42
+ }
43
+ /**
44
+ * 비율을 정준형 `W:H` 문자열로 만든다. `16:9`·`16/9`·`1.78` 을 받고, 숫자 하나면
45
+ * `W:1` 꼴로 세운다. 못 읽으면 `null`.
46
+ */
47
+ export function normalizeMediaRatio(value) {
48
+ if (typeof value === "number") {
49
+ if (!Number.isFinite(value) || value <= 0)
50
+ return null;
51
+ return `${trimDecimal(value)}:1`;
52
+ }
53
+ if (typeof value !== "string")
54
+ return null;
55
+ const trimmed = value.trim();
56
+ if (!trimmed)
57
+ return null;
58
+ const pair = /^([0-9]+(?:\.[0-9]+)?)\s*[:/]\s*([0-9]+(?:\.[0-9]+)?)$/.exec(trimmed);
59
+ if (pair) {
60
+ const w = Number.parseFloat(pair[1]);
61
+ const h = Number.parseFloat(pair[2]);
62
+ if (!Number.isFinite(w) || !Number.isFinite(h) || w <= 0 || h <= 0)
63
+ return null;
64
+ return `${trimDecimal(w)}:${trimDecimal(h)}`;
65
+ }
66
+ const single = Number.parseFloat(trimmed);
67
+ if (!Number.isFinite(single) || single <= 0)
68
+ return null;
69
+ return `${trimDecimal(single)}:1`;
70
+ }
71
+ /** `W:H` → 숫자 비율(W/H). CSS `aspect-ratio` 계산·비교용. */
72
+ export function mediaRatioValue(value) {
73
+ const canonical = normalizeMediaRatio(value);
74
+ if (!canonical)
75
+ return null;
76
+ const [w, h] = canonical.split(":").map(Number.parseFloat);
77
+ if (!Number.isFinite(w) || !Number.isFinite(h) || h <= 0)
78
+ return null;
79
+ return w / h;
80
+ }
81
+ /** `16:9` → `16 / 9` — CSS `aspect-ratio` 값 꼴. */
82
+ export function mediaRatioCss(value) {
83
+ const canonical = normalizeMediaRatio(value);
84
+ if (!canonical)
85
+ return null;
86
+ return canonical.replace(":", " / ");
87
+ }
88
+ function trimDecimal(value) {
89
+ return String(Number(value.toFixed(4)));
90
+ }
@@ -36,6 +36,18 @@ export interface AttachResizeOptions {
36
36
  label?: string;
37
37
  /** 저장 형식. 기본은 `'240px'` 같은 px 문자열. */
38
38
  format?: (value: number) => string;
39
+ /**
40
+ * 손잡이를 붙일 부모. 기본은 `dom` 자신. **크기를 먹는 요소와 손잡이가 사는 요소를
41
+ * 갈라야 할 때** 쓴다 — 영상의 비율 박스는 `overflow:hidden` 이라 밖 12px 을 침범하는
42
+ * 손잡이가 잘리므로, 크기는 박스에 먹이고 손잡이는 래퍼에 둔다.
43
+ */
44
+ handleParent?: HTMLElement;
45
+ /**
46
+ * 드래그 커밋 때 저장할 속성 일습. 기본은 `{ ...attrs, [attr]: format(v) }`.
47
+ * 한 번의 커밋으로 **여러 속성을 함께** 바꿔야 할 때 쓴다 — 높이 드래그가 비율
48
+ * 프리셋을 풀거나(`ratio: null`), `TiptapMidibus` 가 `rawAttrs` 사본을 맞추는 자리.
49
+ */
50
+ buildAttrs?: (node: ProseMirrorNode, value: number) => Record<string, unknown>;
39
51
  }
40
52
  export declare function attachResize(options: AttachResizeOptions): () => void;
41
53
  //# sourceMappingURL=resize.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"resize.d.ts","sourceRoot":"","sources":["../../src/utils/resize.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,KAAK,EAAE,IAAI,IAAI,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAEhE;;;;;;;;;;;;;;;;;GAiBG;AAEH,MAAM,MAAM,UAAU,GAAG,GAAG,GAAG,GAAG,CAAC;AAEnC,MAAM,WAAW,mBAAmB;IACnC,uCAAuC;IACvC,GAAG,EAAE,WAAW,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,MAAM,GAAG,SAAS,CAAC;IACjC,mDAAmD;IACnD,OAAO,EAAE,MAAM,eAAe,CAAC;IAC/B,qCAAqC;IACrC,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,sDAAsD;IACtD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,kCAAkC;IAClC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,sCAAsC;IACtC,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;CACnC;AA4BD,wBAAgB,YAAY,CAAC,OAAO,EAAE,mBAAmB,GAAG,MAAM,IAAI,CAiIrE"}
1
+ {"version":3,"file":"resize.d.ts","sourceRoot":"","sources":["../../src/utils/resize.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,KAAK,EAAE,IAAI,IAAI,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAEhE;;;;;;;;;;;;;;;;;GAiBG;AAEH,MAAM,MAAM,UAAU,GAAG,GAAG,GAAG,GAAG,CAAC;AAEnC,MAAM,WAAW,mBAAmB;IACnC,uCAAuC;IACvC,GAAG,EAAE,WAAW,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,MAAM,GAAG,SAAS,CAAC;IACjC,mDAAmD;IACnD,OAAO,EAAE,MAAM,eAAe,CAAC;IAC/B,qCAAqC;IACrC,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,sDAAsD;IACtD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,kCAAkC;IAClC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,sCAAsC;IACtC,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IACnC;;;;OAIG;IACH,YAAY,CAAC,EAAE,WAAW,CAAC;IAC3B;;;;OAIG;IACH,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,eAAe,EAAE,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC/E;AA4BD,wBAAgB,YAAY,CAAC,OAAO,EAAE,mBAAmB,GAAG,MAAM,IAAI,CA+HrE"}
@@ -94,10 +94,10 @@ export function attachResize(options) {
94
94
  // ⚠️ `getNode()` — 클로저가 잡아 둔 낡은 노드를 쓰면 리사이즈가 그 사이의 다른
95
95
  // 속성 변경을 되돌린다(복붙 시절의 버그).
96
96
  const node = getNode();
97
- editor.view.dispatch(editor.view.state.tr.setNodeMarkup(pos, undefined, {
98
- ...node.attrs,
99
- [attr]: format(parsed)
100
- }));
97
+ const nextAttrs = options.buildAttrs
98
+ ? options.buildAttrs(node, parsed)
99
+ : { ...node.attrs, [attr]: format(parsed) };
100
+ editor.view.dispatch(editor.view.state.tr.setNodeMarkup(pos, undefined, nextAttrs));
101
101
  };
102
102
  const attachWindow = () => {
103
103
  window.addEventListener("pointermove", onPointerMove);
@@ -125,7 +125,7 @@ export function attachResize(options) {
125
125
  handle.addEventListener("mouseenter", onEnter);
126
126
  handle.addEventListener("mouseleave", onLeave);
127
127
  handle.addEventListener("pointerdown", onPointerDown);
128
- dom.appendChild(handle);
128
+ (options.handleParent ?? dom).appendChild(handle);
129
129
  return () => {
130
130
  handle.removeEventListener("mouseenter", onEnter);
131
131
  handle.removeEventListener("mouseleave", onLeave);
@@ -1,4 +1,6 @@
1
1
  export declare function sanitizeHtml(html: string): string;
2
+ /** 저장 직전 HTML 에서 업로드 스켈레톤을 걷어낸다. 없으면 원본 그대로. */
3
+ export declare function stripUploadSkeletonHtml(html: string): string;
2
4
  /**
3
5
  * 레거시 TipTap v2 커스텀 태그를 현재 형식으로 변환.
4
6
  * 에디터 content 로드 전, 또는 게시물 렌더링 전에 호출.
@@ -1 +1 @@
1
- {"version":3,"file":"sanitize.d.ts","sourceRoot":"","sources":["../../src/utils/sanitize.ts"],"names":[],"mappings":"AAyIA,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAoBjD;AA6BD;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CA2ExD;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,SAAM,GAAG,MAAM,CAWrE"}
1
+ {"version":3,"file":"sanitize.d.ts","sourceRoot":"","sources":["../../src/utils/sanitize.ts"],"names":[],"mappings":"AAqJA,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAoBjD;AAqCD,gDAAgD;AAChD,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAG5D;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAwExD;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,SAAM,GAAG,MAAM,CAWrE"}
@@ -57,7 +57,12 @@ function isAllowedIframeSrc(value) {
57
57
  }
58
58
  const ALLOWED_ATTRS = {
59
59
  a: new Set(["href", "title", "target", "rel"]),
60
- img: new Set(["src", "alt", "width", "height"]),
60
+ // `data-align` 은 미디어 툴바의 좌/중/우 정렬(`ResizableImage`). `style` 로 저장하면
61
+ // 여기서 지워져 왕복이 어긋나므로 속성으로 저장하고, 그 이름을 여기 허용한다.
62
+ img: new Set(["src", "alt", "width", "height", "data-align"]),
63
+ // 순서목록 마커(`OrderedListMarker`). `type` 은 마커 종류(1·a·A·i·I·kors·korc),
64
+ // `start` 는 시작 번호 — 지워지면 `c.` 로 시작한 목록이 `a.` 부터 다시 세는 것처럼 보인다.
65
+ ol: new Set(["start", "type"]),
61
66
  table: new Set(["style"]),
62
67
  col: new Set(["style", "width"]),
63
68
  td: new Set(["colspan", "rowspan", "colwidth", "style"]),
@@ -78,9 +83,16 @@ const ALLOWED_ATTRS = {
78
83
  "data-file-size",
79
84
  "data-mbus-src",
80
85
  "data-mbus-width",
86
+ // 미디어 툴바·높이 드래그가 저장하는 크기·정렬(`MbusVideo`).
87
+ "data-mbus-height",
88
+ "data-mbus-ratio",
89
+ "data-mbus-align",
81
90
  // 유튜브·Vimeo 등 바깥 영상(`VideoEmbed`). mbus 와 **다른 이름**을 쓴다.
82
91
  "data-video-src",
83
92
  "data-video-width",
93
+ "data-video-height",
94
+ "data-video-ratio",
95
+ "data-video-align",
84
96
  "data-card-title",
85
97
  "data-card-background",
86
98
  "data-card-height",
@@ -176,6 +188,19 @@ function sanitizeAttributes(tag, attrString) {
176
188
  }
177
189
  return result.join("");
178
190
  }
191
+ /*
192
+ * 업로드 스켈레톤(`extensions/UploadSkeleton`)의 직렬화 태그. 스켈레톤은 "업로드 중"
193
+ * 이라는 화면 상태지 내용이 아니라서 **저장 HTML 에 남으면 안 된다** — 이 패턴 하나를
194
+ * 내보내기(`stripUploadSkeletonHtml`)와 읽기(`transformLegacyHtml`)가 같이 쓴다.
195
+ * 태그 이름은 그쪽 `UPLOAD_SKELETON_NODE` 와 짝이다.
196
+ */
197
+ const UPLOAD_SKELETON_PATTERN = /<tiptap-upload-skeleton[^>]*>(?:<\/tiptap-upload-skeleton>)?/gi;
198
+ /** 저장 직전 HTML 에서 업로드 스켈레톤을 걷어낸다. 없으면 원본 그대로. */
199
+ export function stripUploadSkeletonHtml(html) {
200
+ if (!html || !html.includes("<tiptap-upload-skeleton"))
201
+ return html;
202
+ return html.replace(UPLOAD_SKELETON_PATTERN, "");
203
+ }
179
204
  /**
180
205
  * 레거시 TipTap v2 커스텀 태그를 현재 형식으로 변환.
181
206
  * 에디터 content 로드 전, 또는 게시물 렌더링 전에 호출.
@@ -233,8 +258,8 @@ export function transformLegacyHtml(html) {
233
258
  * prod 에디터가 그 형식을 몰라 단이 통째로 사라진다. 손해를 반대로 옮길 뿐이다.
234
259
  * 지금은 `LegacyBlock` 이 원본 마크업 그대로 품고 되쓴다(우선순위 100).
235
260
  */
236
- // <tiptap-upload-skeleton ...> 제거
237
- .replace(/<tiptap-upload-skeleton[^>]*>(?:<\/tiptap-upload-skeleton>)?/gi, ""));
261
+ // <tiptap-upload-skeleton ...> 제거 — 위 `UPLOAD_SKELETON_PATTERN` 하나로 관리
262
+ .replace(UPLOAD_SKELETON_PATTERN, ""));
238
263
  }
239
264
  export function stripHtmlToExcerpt(html, maxLen = 200) {
240
265
  const text = String(html || "")
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@teriusu/rich-editor",
3
- "version": "0.10.8",
3
+ "version": "0.11.0",
4
4
  "description": "TipTap 기반 Notion-style 리치 텍스트 에디터 (Svelte)",
5
5
  "type": "module",
6
6
  "svelte": "./dist/index.js",
@@ -16,6 +16,10 @@
16
16
  "svelte": "./dist/toolbars.js",
17
17
  "default": "./dist/toolbars.js"
18
18
  },
19
+ "./static": {
20
+ "types": "./dist/static/index.d.ts",
21
+ "default": "./dist/static/index.js"
22
+ },
19
23
  "./lowlight": {
20
24
  "types": "./dist/utils/lowlight.d.ts",
21
25
  "default": "./dist/utils/lowlight.js"
@@ -52,6 +56,7 @@
52
56
  "@tiptap/extension-highlight": "^3.20.4",
53
57
  "@tiptap/extension-image": "^3.20.4",
54
58
  "@tiptap/extension-link": "^3.20.4",
59
+ "@tiptap/extension-list": "3.22.2",
55
60
  "@tiptap/extension-placeholder": "^3.20.4",
56
61
  "@tiptap/extension-subscript": "^3.21.0",
57
62
  "@tiptap/extension-superscript": "^3.21.0",
@@ -68,6 +73,8 @@
68
73
  "@tiptap/pm": "^3.20.4",
69
74
  "@tiptap/starter-kit": "^3.22.0",
70
75
  "clsx": "^2.1.1",
76
+ "emojis-keywords": "2.0.0",
77
+ "emojis-list": "3.0.0",
71
78
  "highlight.js": "^11.11.1",
72
79
  "hls.js": "^1.6.16",
73
80
  "katex": "^0.16.45",