@teriusu/rich-editor 0.9.5 → 0.9.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 (53) hide show
  1. package/dist/components/BubbleToolbar.svelte +102 -49
  2. package/dist/components/BubbleToolbar.svelte.d.ts.map +1 -1
  3. package/dist/components/FixedToolbar.svelte +136 -214
  4. package/dist/components/FixedToolbar.svelte.d.ts +9 -1
  5. package/dist/components/FixedToolbar.svelte.d.ts.map +1 -1
  6. package/dist/components/InputModal.svelte +1 -1
  7. package/dist/components/MathModal.svelte +111 -1
  8. package/dist/components/MathModal.svelte.d.ts.map +1 -1
  9. package/dist/components/MediaPickerModal.svelte +206 -0
  10. package/dist/components/MediaPickerModal.svelte.d.ts +18 -0
  11. package/dist/components/MediaPickerModal.svelte.d.ts.map +1 -0
  12. package/dist/components/SlashCommandMenu.svelte +114 -13
  13. package/dist/components/SlashCommandMenu.svelte.d.ts +10 -1
  14. package/dist/components/SlashCommandMenu.svelte.d.ts.map +1 -1
  15. package/dist/components/TableBubbleMenu.svelte +71 -48
  16. package/dist/components/TableBubbleMenu.svelte.d.ts.map +1 -1
  17. package/dist/components/TipTapEditor.svelte +180 -24
  18. package/dist/components/TipTapEditor.svelte.d.ts +2 -0
  19. package/dist/components/TipTapEditor.svelte.d.ts.map +1 -1
  20. package/dist/extensions/NotionInputRules.d.ts +59 -0
  21. package/dist/extensions/NotionInputRules.d.ts.map +1 -0
  22. package/dist/extensions/NotionInputRules.js +141 -0
  23. package/dist/extensions/NotionInputRules.test.d.ts +2 -0
  24. package/dist/extensions/NotionInputRules.test.d.ts.map +1 -0
  25. package/dist/extensions/NotionInputRules.test.js +117 -0
  26. package/dist/extensions/VideoEmbed.d.ts +36 -0
  27. package/dist/extensions/VideoEmbed.d.ts.map +1 -0
  28. package/dist/extensions/VideoEmbed.js +246 -0
  29. package/dist/extensions/VideoEmbed.test.d.ts +2 -0
  30. package/dist/extensions/VideoEmbed.test.d.ts.map +1 -0
  31. package/dist/extensions/VideoEmbed.test.js +57 -0
  32. package/dist/index.d.ts +1 -0
  33. package/dist/index.d.ts.map +1 -1
  34. package/dist/index.js +13 -0
  35. package/dist/styles/editor.css +1 -1
  36. package/dist/types.d.ts +14 -2
  37. package/dist/types.d.ts.map +1 -1
  38. package/dist/types.js +20 -6
  39. package/dist/utils/lowlight.d.ts +17 -0
  40. package/dist/utils/lowlight.d.ts.map +1 -0
  41. package/dist/utils/lowlight.js +106 -0
  42. package/dist/utils/lowlight.test.d.ts +2 -0
  43. package/dist/utils/lowlight.test.d.ts.map +1 -0
  44. package/dist/utils/lowlight.test.js +56 -0
  45. package/dist/utils/sanitize.d.ts.map +1 -1
  46. package/dist/utils/sanitize.js +14 -0
  47. package/dist/utils/splitCell.d.ts +20 -0
  48. package/dist/utils/splitCell.d.ts.map +1 -0
  49. package/dist/utils/splitCell.js +74 -0
  50. package/dist/utils/table.d.ts +22 -0
  51. package/dist/utils/table.d.ts.map +1 -0
  52. package/dist/utils/table.js +68 -0
  53. package/package.json +6 -1
@@ -0,0 +1,74 @@
1
+ import { TableMap, splitCellWithType } from "@tiptap/pm/tables";
2
+ /**
3
+ * 셀 분할 — **헤더 표시를 원래 자리로 되돌린다**.
4
+ *
5
+ * ⚠️ 기본 `splitCell()` 은 나눈 조각을 **원래 셀 타입 그대로** 만든다. 그런데 헤더 셀과
6
+ * 일반 셀을 병합하면 결과가 `tableHeader` 가 되므로, 그걸 다시 나누면 **원래 일반 셀이던
7
+ * 자리까지 전부 헤더로 남는다.** 그 상태는 `헤더 행/열 토글` 로도 못 고친다 — 그건 행·열
8
+ * 전체를 뒤집는 것이라 표 한가운데 남은 헤더 셀에는 닿지 않는다(사용자 지적: "헤더표시가
9
+ * 남아서 없앨수가 없어").
10
+ *
11
+ * 그래서 나눌 때 **자리에 맞는 타입**을 직접 정해 준다:
12
+ * · 표에 헤더 행이 있으면 → 첫 행 조각은 헤더
13
+ * · 표에 헤더 열이 있으면 → 첫 열 조각은 헤더
14
+ * · 그 밖은 전부 일반 셀
15
+ *
16
+ * ⚠️ "헤더 행이 있는가" 는 **병합에 끼지 않은 셀**로 판정한다. 병합 결과 셀은 그 자체가
17
+ * 헤더 타입일 수 있어서, 그걸 근거로 삼으면 "헤더가 있다" 고 잘못 읽는다.
18
+ */
19
+ export function splitCellPreservingHeaders(editor) {
20
+ const { state, view } = editor;
21
+ const { schema, selection } = state;
22
+ const headerType = schema.nodes.tableHeader;
23
+ const cellType = schema.nodes.tableCell;
24
+ if (!headerType || !cellType)
25
+ return editor.chain().focus().splitCell().run();
26
+ /* 지금 셀이 속한 표를 찾는다. */
27
+ const $from = selection.$from;
28
+ let table = null;
29
+ for (let d = $from.depth; d > 0; d--) {
30
+ const node = $from.node(d);
31
+ if (node.type.name === "table") {
32
+ table = node;
33
+ break;
34
+ }
35
+ }
36
+ if (!table)
37
+ return editor.chain().focus().splitCell().run();
38
+ const map = TableMap.get(table);
39
+ /** (r,c) 자리의 셀. 병합된 자리는 같은 셀을 여러 번 가리킨다. */
40
+ const cellAt = (r, c) => {
41
+ const pos = map.map[r * map.width + c];
42
+ return pos === undefined ? null : table.nodeAt(pos);
43
+ };
44
+ /** 그 자리가 **자기 칸 하나만 차지하는** 셀인지 — 병합 셀은 근거로 쓰지 않는다. */
45
+ const isPlain = (r, c) => {
46
+ const cell = cellAt(r, c);
47
+ if (!cell)
48
+ return false;
49
+ const { colspan = 1, rowspan = 1 } = cell.attrs;
50
+ return colspan === 1 && rowspan === 1;
51
+ };
52
+ const isHeader = (r, c) => cellAt(r, c)?.type === headerType;
53
+ let hasHeaderRow = false;
54
+ for (let c = 0; c < map.width; c++) {
55
+ if (isPlain(0, c) && isHeader(0, c)) {
56
+ hasHeaderRow = true;
57
+ break;
58
+ }
59
+ }
60
+ let hasHeaderCol = false;
61
+ for (let r = 0; r < map.height; r++) {
62
+ if (isPlain(r, 0) && isHeader(r, 0)) {
63
+ hasHeaderCol = true;
64
+ break;
65
+ }
66
+ }
67
+ return splitCellWithType(({ row, col }) => {
68
+ if (hasHeaderRow && row === 0)
69
+ return headerType;
70
+ if (hasHeaderCol && col === 0)
71
+ return headerType;
72
+ return cellType;
73
+ })(state, view.dispatch);
74
+ }
@@ -0,0 +1,22 @@
1
+ import type { Editor } from "@tiptap/core";
2
+ /**
3
+ * 표를 넣되 **열 너비를 미리 박아 둔다**.
4
+ *
5
+ * ⚠️ 왜 필요한가 — `insertTable` 만 부르면 열에 `colwidth` 가 없다. 그 상태에서 표를
6
+ * 화면 폭에 맞추려면 CSS 로 `table { width: 100% }` 를 거는 수밖에 없는데, 그러면
7
+ * **표 전체 폭이 컨테이너에 묶여** 맨 오른쪽 열을 줄여도 앞 열들이 그만큼 늘어나
8
+ * 상쇄된다 — 표 크기가 안 줄어드는 것처럼 보인다(사용자 지적, A/B 로 재현:
9
+ * `width:100%` 없을 때 표 182px / 있을 때 407px 고정에 마지막 열만 55px).
10
+ *
11
+ * 열 너비를 처음부터 넣어 두면 **표 폭 = `colwidth` 합**이 되어, 처음엔 화면을 꽉 채우고
12
+ * 이후에는 어느 열이든 끌어서 표를 줄일 수 있다.
13
+ *
14
+ * ⚠️ 옛 문서의 표에는 `colwidth` 가 없다. 그쪽이 좁아지지 않도록 CSS 는
15
+ * **`colwidth` 가 없는 표에만** `width: 100%` 를 건다(소비 앱 `layout`/`TipTap.svelte`).
16
+ */
17
+ export declare function insertTableSized(editor: Editor, opts: {
18
+ rows: number;
19
+ cols: number;
20
+ withHeaderRow: boolean;
21
+ }): void;
22
+ //# sourceMappingURL=table.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"table.d.ts","sourceRoot":"","sources":["../../src/utils/table.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAE3C;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,gBAAgB,CAC/B,MAAM,EAAE,MAAM,EACd,IAAI,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,OAAO,CAAA;CAAE,GAC1D,IAAI,CAoCN"}
@@ -0,0 +1,68 @@
1
+ /**
2
+ * 표를 넣되 **열 너비를 미리 박아 둔다**.
3
+ *
4
+ * ⚠️ 왜 필요한가 — `insertTable` 만 부르면 열에 `colwidth` 가 없다. 그 상태에서 표를
5
+ * 화면 폭에 맞추려면 CSS 로 `table { width: 100% }` 를 거는 수밖에 없는데, 그러면
6
+ * **표 전체 폭이 컨테이너에 묶여** 맨 오른쪽 열을 줄여도 앞 열들이 그만큼 늘어나
7
+ * 상쇄된다 — 표 크기가 안 줄어드는 것처럼 보인다(사용자 지적, A/B 로 재현:
8
+ * `width:100%` 없을 때 표 182px / 있을 때 407px 고정에 마지막 열만 55px).
9
+ *
10
+ * 열 너비를 처음부터 넣어 두면 **표 폭 = `colwidth` 합**이 되어, 처음엔 화면을 꽉 채우고
11
+ * 이후에는 어느 열이든 끌어서 표를 줄일 수 있다.
12
+ *
13
+ * ⚠️ 옛 문서의 표에는 `colwidth` 가 없다. 그쪽이 좁아지지 않도록 CSS 는
14
+ * **`colwidth` 가 없는 표에만** `width: 100%` 를 건다(소비 앱 `layout`/`TipTap.svelte`).
15
+ */
16
+ export function insertTableSized(editor, opts) {
17
+ const { rows, cols, withHeaderRow } = opts;
18
+ editor.chain().focus().insertTable({ rows, cols, withHeaderRow }).run();
19
+ const per = columnWidth(editor, cols);
20
+ if (!per)
21
+ return;
22
+ const { state } = editor;
23
+ const { $from } = state.selection;
24
+ /* 방금 만든 표를 selection 조상에서 찾는다. */
25
+ let tableNode = null;
26
+ let tablePos = -1;
27
+ for (let d = $from.depth; d > 0; d--) {
28
+ const node = $from.node(d);
29
+ if (node.type.name === "table") {
30
+ tableNode = node;
31
+ tablePos = $from.before(d);
32
+ break;
33
+ }
34
+ }
35
+ const firstRow = tableNode?.firstChild;
36
+ if (!tableNode || !firstRow)
37
+ return;
38
+ /*
39
+ * 첫 행의 셀들에만 넣으면 된다 — `colwidth` 는 열 단위라 나머지 행은 따라온다.
40
+ * 위치: 표 시작(+1) → 첫 행 시작(+1) → 첫 셀.
41
+ */
42
+ const tr = state.tr;
43
+ let pos = tablePos + 2;
44
+ firstRow.forEach((cell) => {
45
+ tr.setNodeMarkup(pos, undefined, { ...cell.attrs, colwidth: [per] });
46
+ pos += cell.nodeSize;
47
+ });
48
+ editor.view.dispatch(tr);
49
+ }
50
+ /**
51
+ * 열 하나에 줄 픽셀 너비. 본문에서 좌우 여백을 뺀 폭을 열 수로 나눈다.
52
+ *
53
+ * ⚠️ 테두리 때문에 딱 나누어떨어지지 않는다. 조금 **작게** 잡아야 표가 컨테이너를 넘겨
54
+ * 가로 스크롤을 만들지 않는다.
55
+ */
56
+ function columnWidth(editor, cols) {
57
+ const dom = editor.view.dom;
58
+ if (!dom?.clientWidth)
59
+ return null;
60
+ const cs = getComputedStyle(dom);
61
+ const inner = dom.clientWidth - parseFloat(cs.paddingLeft || "0") - parseFloat(cs.paddingRight || "0");
62
+ if (!Number.isFinite(inner) || inner <= 0)
63
+ return null;
64
+ /* 표 테두리(2px)와 셀 경계 몫을 덜어 낸다. */
65
+ const usable = inner - 2 - cols;
66
+ /* `cellMinWidth` 기본값(25) 아래로는 의미가 없다. */
67
+ return Math.max(25, Math.floor(usable / cols));
68
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@teriusu/rich-editor",
3
- "version": "0.9.5",
3
+ "version": "0.9.7",
4
4
  "description": "TipTap 기반 Notion-style 리치 텍스트 에디터 (Svelte)",
5
5
  "type": "module",
6
6
  "svelte": "./dist/index.js",
@@ -11,6 +11,10 @@
11
11
  "svelte": "./dist/index.js",
12
12
  "default": "./dist/index.js"
13
13
  },
14
+ "./lowlight": {
15
+ "types": "./dist/utils/lowlight.d.ts",
16
+ "default": "./dist/utils/lowlight.js"
17
+ },
14
18
  "./styles": {
15
19
  "style": "./dist/styles/editor.css",
16
20
  "default": "./dist/styles/editor.css"
@@ -30,6 +34,7 @@
30
34
  },
31
35
  "dependencies": {
32
36
  "@tiptap/core": "^3.20.4",
37
+ "@tiptap/extension-blockquote": "^3.22.2",
33
38
  "@tiptap/extension-bubble-menu": "^3.20.4",
34
39
  "@tiptap/extension-character-count": "^3.21.0",
35
40
  "@tiptap/extension-code-block-lowlight": "^3.20.5",