@storkspear/post-editor-react 0.1.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 (192) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +12 -0
  3. package/dist/ColorPicker.d.ts +11 -0
  4. package/dist/ColorPicker.d.ts.map +1 -0
  5. package/dist/ColorPicker.js +30 -0
  6. package/dist/ColorPicker.js.map +1 -0
  7. package/dist/DrawioModal.d.ts +9 -0
  8. package/dist/DrawioModal.d.ts.map +1 -0
  9. package/dist/DrawioModal.js +97 -0
  10. package/dist/DrawioModal.js.map +1 -0
  11. package/dist/FontMenu.d.ts +14 -0
  12. package/dist/FontMenu.d.ts.map +1 -0
  13. package/dist/FontMenu.js +14 -0
  14. package/dist/FontMenu.js.map +1 -0
  15. package/dist/Icon.d.ts +69 -0
  16. package/dist/Icon.d.ts.map +1 -0
  17. package/dist/Icon.js +47 -0
  18. package/dist/Icon.js.map +1 -0
  19. package/dist/InsertHandle.d.ts +14 -0
  20. package/dist/InsertHandle.d.ts.map +1 -0
  21. package/dist/InsertHandle.js +60 -0
  22. package/dist/InsertHandle.js.map +1 -0
  23. package/dist/PickMenu.d.ts +61 -0
  24. package/dist/PickMenu.d.ts.map +1 -0
  25. package/dist/PickMenu.js +57 -0
  26. package/dist/PickMenu.js.map +1 -0
  27. package/dist/Popover.d.ts +20 -0
  28. package/dist/Popover.d.ts.map +1 -0
  29. package/dist/Popover.js +22 -0
  30. package/dist/Popover.js.map +1 -0
  31. package/dist/PostTitle.d.ts +33 -0
  32. package/dist/PostTitle.d.ts.map +1 -0
  33. package/dist/PostTitle.js +171 -0
  34. package/dist/PostTitle.js.map +1 -0
  35. package/dist/SelectionBubble.d.ts +6 -0
  36. package/dist/SelectionBubble.d.ts.map +1 -0
  37. package/dist/SelectionBubble.js +71 -0
  38. package/dist/SelectionBubble.js.map +1 -0
  39. package/dist/TableTools.d.ts +10 -0
  40. package/dist/TableTools.d.ts.map +1 -0
  41. package/dist/TableTools.js +200 -0
  42. package/dist/TableTools.js.map +1 -0
  43. package/dist/Toolbar.d.ts +17 -0
  44. package/dist/Toolbar.d.ts.map +1 -0
  45. package/dist/Toolbar.js +333 -0
  46. package/dist/Toolbar.js.map +1 -0
  47. package/dist/UrlPrompt.d.ts +24 -0
  48. package/dist/UrlPrompt.d.ts.map +1 -0
  49. package/dist/UrlPrompt.js +53 -0
  50. package/dist/UrlPrompt.js.map +1 -0
  51. package/dist/VariantMenu.d.ts +47 -0
  52. package/dist/VariantMenu.d.ts.map +1 -0
  53. package/dist/VariantMenu.js +21 -0
  54. package/dist/VariantMenu.js.map +1 -0
  55. package/dist/WidthToggle.d.ts +19 -0
  56. package/dist/WidthToggle.d.ts.map +1 -0
  57. package/dist/WidthToggle.js +17 -0
  58. package/dist/WidthToggle.js.map +1 -0
  59. package/dist/bubbleShows.d.ts +10 -0
  60. package/dist/bubbleShows.d.ts.map +1 -0
  61. package/dist/bubbleShows.js +29 -0
  62. package/dist/bubbleShows.js.map +1 -0
  63. package/dist/content-baked.d.ts +22 -0
  64. package/dist/content-baked.d.ts.map +1 -0
  65. package/dist/content-baked.js +21 -0
  66. package/dist/content-baked.js.map +1 -0
  67. package/dist/content.d.ts +29 -0
  68. package/dist/content.d.ts.map +1 -0
  69. package/dist/content.js +35 -0
  70. package/dist/content.js.map +1 -0
  71. package/dist/extensions/DiagramView.d.ts +26 -0
  72. package/dist/extensions/DiagramView.d.ts.map +1 -0
  73. package/dist/extensions/DiagramView.js +146 -0
  74. package/dist/extensions/DiagramView.js.map +1 -0
  75. package/dist/extensions/FigureView.d.ts +3 -0
  76. package/dist/extensions/FigureView.d.ts.map +1 -0
  77. package/dist/extensions/FigureView.js +108 -0
  78. package/dist/extensions/FigureView.js.map +1 -0
  79. package/dist/index.d.ts +100 -0
  80. package/dist/index.d.ts.map +1 -0
  81. package/dist/index.js +166 -0
  82. package/dist/index.js.map +1 -0
  83. package/dist/objects/ObjectFloat.d.ts +11 -0
  84. package/dist/objects/ObjectFloat.d.ts.map +1 -0
  85. package/dist/objects/ObjectFloat.js +42 -0
  86. package/dist/objects/ObjectFloat.js.map +1 -0
  87. package/dist/objects/ObjectLayer.d.ts +10 -0
  88. package/dist/objects/ObjectLayer.d.ts.map +1 -0
  89. package/dist/objects/ObjectLayer.js +20 -0
  90. package/dist/objects/ObjectLayer.js.map +1 -0
  91. package/dist/objects/blockSize.d.ts +30 -0
  92. package/dist/objects/blockSize.d.ts.map +1 -0
  93. package/dist/objects/blockSize.js +56 -0
  94. package/dist/objects/blockSize.js.map +1 -0
  95. package/dist/objects/code.d.ts +3 -0
  96. package/dist/objects/code.d.ts.map +1 -0
  97. package/dist/objects/code.js +55 -0
  98. package/dist/objects/code.js.map +1 -0
  99. package/dist/objects/diagram.d.ts +21 -0
  100. package/dist/objects/diagram.d.ts.map +1 -0
  101. package/dist/objects/diagram.js +109 -0
  102. package/dist/objects/diagram.js.map +1 -0
  103. package/dist/objects/diagramGlyphs.d.ts +12 -0
  104. package/dist/objects/diagramGlyphs.d.ts.map +1 -0
  105. package/dist/objects/diagramGlyphs.js +49 -0
  106. package/dist/objects/diagramGlyphs.js.map +1 -0
  107. package/dist/objects/divider.d.ts +22 -0
  108. package/dist/objects/divider.d.ts.map +1 -0
  109. package/dist/objects/divider.js +60 -0
  110. package/dist/objects/divider.js.map +1 -0
  111. package/dist/objects/figure.d.ts +3 -0
  112. package/dist/objects/figure.d.ts.map +1 -0
  113. package/dist/objects/figure.js +62 -0
  114. package/dist/objects/figure.js.map +1 -0
  115. package/dist/objects/imageRow.d.ts +3 -0
  116. package/dist/objects/imageRow.d.ts.map +1 -0
  117. package/dist/objects/imageRow.js +29 -0
  118. package/dist/objects/imageRow.js.map +1 -0
  119. package/dist/objects/quote.d.ts +6 -0
  120. package/dist/objects/quote.d.ts.map +1 -0
  121. package/dist/objects/quote.js +13 -0
  122. package/dist/objects/quote.js.map +1 -0
  123. package/dist/objects/quoteGlyphs.d.ts +6 -0
  124. package/dist/objects/quoteGlyphs.d.ts.map +1 -0
  125. package/dist/objects/quoteGlyphs.js +28 -0
  126. package/dist/objects/quoteGlyphs.js.map +1 -0
  127. package/dist/objects/quoteRow.d.ts +3 -0
  128. package/dist/objects/quoteRow.d.ts.map +1 -0
  129. package/dist/objects/quoteRow.js +61 -0
  130. package/dist/objects/quoteRow.js.map +1 -0
  131. package/dist/objects/registry.d.ts +60 -0
  132. package/dist/objects/registry.d.ts.map +1 -0
  133. package/dist/objects/registry.js +45 -0
  134. package/dist/objects/registry.js.map +1 -0
  135. package/dist/objects/table.d.ts +11 -0
  136. package/dist/objects/table.d.ts.map +1 -0
  137. package/dist/objects/table.js +136 -0
  138. package/dist/objects/table.js.map +1 -0
  139. package/dist/objects/useDomRect.d.ts +14 -0
  140. package/dist/objects/useDomRect.d.ts.map +1 -0
  141. package/dist/objects/useDomRect.js +32 -0
  142. package/dist/objects/useDomRect.js.map +1 -0
  143. package/dist/objects/useEngaged.d.ts +14 -0
  144. package/dist/objects/useEngaged.d.ts.map +1 -0
  145. package/dist/objects/useEngaged.js +41 -0
  146. package/dist/objects/useEngaged.js.map +1 -0
  147. package/dist/pickItems.d.ts +36 -0
  148. package/dist/pickItems.d.ts.map +1 -0
  149. package/dist/pickItems.js +40 -0
  150. package/dist/pickItems.js.map +1 -0
  151. package/dist/recentColors.d.ts +7 -0
  152. package/dist/recentColors.d.ts.map +1 -0
  153. package/dist/recentColors.js +30 -0
  154. package/dist/recentColors.js.map +1 -0
  155. package/dist/seeds/drawio/aws.d.ts +15 -0
  156. package/dist/seeds/drawio/aws.d.ts.map +1 -0
  157. package/dist/seeds/drawio/aws.js +15 -0
  158. package/dist/seeds/drawio/aws.js.map +1 -0
  159. package/dist/seeds/drawio/azure.d.ts +14 -0
  160. package/dist/seeds/drawio/azure.d.ts.map +1 -0
  161. package/dist/seeds/drawio/azure.js +14 -0
  162. package/dist/seeds/drawio/azure.js.map +1 -0
  163. package/dist/seeds/drawio/ec2docker.d.ts +15 -0
  164. package/dist/seeds/drawio/ec2docker.d.ts.map +1 -0
  165. package/dist/seeds/drawio/ec2docker.js +15 -0
  166. package/dist/seeds/drawio/ec2docker.js.map +1 -0
  167. package/dist/seeds/drawio/userflow.d.ts +21 -0
  168. package/dist/seeds/drawio/userflow.d.ts.map +1 -0
  169. package/dist/seeds/drawio/userflow.js +21 -0
  170. package/dist/seeds/drawio/userflow.js.map +1 -0
  171. package/dist/seeds/mermaid.d.ts +19 -0
  172. package/dist/seeds/mermaid.d.ts.map +1 -0
  173. package/dist/seeds/mermaid.js +82 -0
  174. package/dist/seeds/mermaid.js.map +1 -0
  175. package/dist/useAnchoredPos.d.ts +22 -0
  176. package/dist/useAnchoredPos.d.ts.map +1 -0
  177. package/dist/useAnchoredPos.js +57 -0
  178. package/dist/useAnchoredPos.js.map +1 -0
  179. package/dist/useDismiss.d.ts +17 -0
  180. package/dist/useDismiss.d.ts.map +1 -0
  181. package/dist/useDismiss.js +36 -0
  182. package/dist/useDismiss.js.map +1 -0
  183. package/dist/useLastVariant.d.ts +9 -0
  184. package/dist/useLastVariant.d.ts.map +1 -0
  185. package/dist/useLastVariant.js +33 -0
  186. package/dist/useLastVariant.js.map +1 -0
  187. package/dist/useUploadCount.d.ts +3 -0
  188. package/dist/useUploadCount.d.ts.map +1 -0
  189. package/dist/useUploadCount.js +9 -0
  190. package/dist/useUploadCount.js.map +1 -0
  191. package/package.json +57 -0
  192. package/src/editor-ui.css +737 -0
@@ -0,0 +1,47 @@
1
+ import { type ReactNode, type RefObject } from 'react';
2
+ export type VariantItem<T extends string> = {
3
+ value: T;
4
+ label: string;
5
+ /** 한 줄 설명 (리스트형에서만 표시) */
6
+ desc?: string;
7
+ /**
8
+ * 미리보기 — **진짜 렌더**를 넣는다.
9
+ * 아이콘을 따로 그리면 실제 결과와 어긋난다(드리프트).
10
+ * 예: <hr data-kind="wave" /> 를 그대로 넣으면 prose.css 가 본문과 똑같이 그린다.
11
+ */
12
+ preview: React.ReactNode;
13
+ };
14
+ /**
15
+ * 목록에 섞어 넣는 **구역 제목** — 격자 한 줄을 통째로 쓴다.
16
+ *
17
+ * 다이어그램 메뉴가 처음 필요로 했다: 여덟 개가 한 판에 놓이는데 다섯은 Mermaid 로 글을
18
+ * 쓰고 셋은 Draw.io 캔버스를 연다. 그 갈림이 안 보이면 「AWS 말고 다른 배치도는 어디서
19
+ * 그리나」를 또 묻게 된다. 제목이 없던 동안 그 질문이 실제로 나왔다.
20
+ *
21
+ * 값이 없으므로 고를 수 없다 — `button` 이 아니라 그냥 글이다.
22
+ */
23
+ export type VariantHeading = {
24
+ heading: string;
25
+ hint?: string;
26
+ };
27
+ export type VariantRow<T extends string> = VariantItem<T> | VariantHeading;
28
+ /**
29
+ * 여러 모양 중 하나를 고르는 메뉴 — 구분선 8종·인용구 6종·표 프리셋·목록 모양이
30
+ * 전부 같은 상호작용이라 하나로 통일한다.
31
+ *
32
+ * 블록 밖이면 "삽입", 블록 안이면 현재값이 켜진 채로 "바꾸기" 로 동작한다.
33
+ * (호출부가 onPick 안에서 판단한다 — 메뉴는 그걸 모른다.)
34
+ */
35
+ export declare function VariantMenu<T extends string>({ items, value, columns, onPick, onClose, ariaLabel, anchorRef, footer, }: {
36
+ items: readonly VariantRow<T>[];
37
+ value?: T | null;
38
+ columns?: 1 | 3 | 4;
39
+ onPick: (v: T) => void;
40
+ onClose: () => void;
41
+ ariaLabel: string;
42
+ /** 이 요소 바로 아래에 뜬다. 없으면 부모 기준(absolute) */
43
+ anchorRef?: RefObject<HTMLElement | null>;
44
+ /** 목록 아래에 덧붙이는 줄 (예: 표 테두리의 「선 색」) */
45
+ footer?: ReactNode;
46
+ }): import("react").JSX.Element;
47
+ //# sourceMappingURL=VariantMenu.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"VariantMenu.d.ts","sourceRoot":"","sources":["../src/VariantMenu.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAU,KAAK,SAAS,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAK9D,MAAM,MAAM,WAAW,CAAC,CAAC,SAAS,MAAM,IAAI;IAC1C,KAAK,EAAE,CAAC,CAAA;IACR,KAAK,EAAE,MAAM,CAAA;IACb,0BAA0B;IAC1B,IAAI,CAAC,EAAE,MAAM,CAAA;IACb;;;;OAIG;IACH,OAAO,EAAE,KAAK,CAAC,SAAS,CAAA;CACzB,CAAA;AAED;;;;;;;;GAQG;AACH,MAAM,MAAM,cAAc,GAAG;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAAA;AAC/D,MAAM,MAAM,UAAU,CAAC,CAAC,SAAS,MAAM,IAAI,WAAW,CAAC,CAAC,CAAC,GAAG,cAAc,CAAA;AAK1E;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,CAAC,SAAS,MAAM,EAAE,EAC5C,KAAK,EAAE,KAAK,EAAE,OAAW,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,GACzE,EAAE;IACD,KAAK,EAAE,SAAS,UAAU,CAAC,CAAC,CAAC,EAAE,CAAA;IAC/B,KAAK,CAAC,EAAE,CAAC,GAAG,IAAI,CAAA;IAChB,OAAO,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;IACnB,MAAM,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,IAAI,CAAA;IACtB,OAAO,EAAE,MAAM,IAAI,CAAA;IACnB,SAAS,EAAE,MAAM,CAAA;IACjB,0CAA0C;IAC1C,SAAS,CAAC,EAAE,SAAS,CAAC,WAAW,GAAG,IAAI,CAAC,CAAA;IACzC,sCAAsC;IACtC,MAAM,CAAC,EAAE,SAAS,CAAA;CACnB,+BA+BA"}
@@ -0,0 +1,21 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useRef } from 'react';
3
+ import { useDismiss } from './useDismiss.js';
4
+ import { useAnchoredPos } from './useAnchoredPos.js';
5
+ import { Portal } from './Popover.js';
6
+ const isHeading = (r) => 'heading' in r;
7
+ /**
8
+ * 여러 모양 중 하나를 고르는 메뉴 — 구분선 8종·인용구 6종·표 프리셋·목록 모양이
9
+ * 전부 같은 상호작용이라 하나로 통일한다.
10
+ *
11
+ * 블록 밖이면 "삽입", 블록 안이면 현재값이 켜진 채로 "바꾸기" 로 동작한다.
12
+ * (호출부가 onPick 안에서 판단한다 — 메뉴는 그걸 모른다.)
13
+ */
14
+ export function VariantMenu({ items, value, columns = 1, onPick, onClose, ariaLabel, anchorRef, footer, }) {
15
+ const ref = useRef(null);
16
+ const width = columns === 4 ? 340 : columns === 3 ? 260 : 214;
17
+ const pos = useAnchoredPos(anchorRef ?? { current: null }, !!anchorRef, width, ref);
18
+ useDismiss(true, ref, onClose, anchorRef);
19
+ return (_jsx(Portal, { active: !!pos, children: _jsxs("div", { "data-editor-ui": "", className: `vm vm-col-${columns}${pos ? ' is-fixed' : ''}`, ref: ref, role: "menu", "aria-label": ariaLabel, style: pos ? { position: 'fixed', left: pos.left, top: pos.top, maxHeight: pos.maxHeight, overflowY: 'auto' } : undefined, children: [items.map((it) => isHeading(it) ? (_jsxs("p", { className: "vm-head", role: "presentation", children: [_jsx("b", { children: it.heading }), it.hint && _jsx("i", { children: it.hint })] }, `h:${it.heading}`)) : (_jsxs("button", { type: "button", role: "menuitemradio", "aria-checked": value === it.value, className: `vm-item${value === it.value ? ' is-on' : ''}`, title: it.label, onMouseDown: (e) => e.preventDefault(), onClick: () => { onPick(it.value); onClose(); }, children: [_jsx("span", { className: "vm-preview prose", children: it.preview }), _jsxs("span", { className: "vm-text", children: [_jsx("b", { children: it.label }), it.desc && _jsx("i", { children: it.desc })] })] }, it.value))), footer] }) }));
20
+ }
21
+ //# sourceMappingURL=VariantMenu.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"VariantMenu.js","sourceRoot":"","sources":["../src/VariantMenu.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ import { type ProseWidth } from '@storkspear/post-editor-core/proseWidth';
2
+ /**
3
+ * 본문 폭 고르개 — 「가운데폭 / 전체폭」.
4
+ *
5
+ * **라이브러리가 들고 있다.** 이 버튼이 시연 껍데기에만 있으면 소비자마다 다시 만들게 되고,
6
+ * 그때마다 `width` 에 무엇을 넘겨야 하는지도 다시 알아내야 한다.
7
+ * 편집기와 발행기가 같은 값을 받아야 두 화면이 같아지므로, 그 값을 만드는 곳도 한 곳이어야 한다.
8
+ *
9
+ * 놓을 자리는 소비자가 정한다(머리줄·도구줄 어디든). 상태는 소비자가 들고 있는다 —
10
+ * 그래야 발행 쪽 `PostContent` 에 같은 값을 넘길 수 있다.
11
+ */
12
+ export declare function WidthToggle({ value, onChange, centered, className }: {
13
+ value: ProseWidth;
14
+ onChange: (v: ProseWidth) => void;
15
+ /** 「가운데폭」이 뜻하는 폭. 기본 `CENTERED_WIDTH` — 저장·재열기·발행이 같은 값을 봐야 한다 */
16
+ centered?: ProseWidth;
17
+ className?: string;
18
+ }): import("react").JSX.Element;
19
+ //# sourceMappingURL=WidthToggle.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WidthToggle.d.ts","sourceRoot":"","sources":["../src/WidthToggle.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAkB,KAAK,UAAU,EAAE,MAAM,yCAAyC,CAAA;AAEzF;;;;;;;;;GASG;AACH,wBAAgB,WAAW,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAyB,EAAE,SAAS,EAAE,EAAE;IACrF,KAAK,EAAE,UAAU,CAAA;IACjB,QAAQ,EAAE,CAAC,CAAC,EAAE,UAAU,KAAK,IAAI,CAAA;IACjC,kEAAkE;IAClE,QAAQ,CAAC,EAAE,UAAU,CAAA;IACrB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB,+BAYA"}
@@ -0,0 +1,17 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { CENTERED_WIDTH } from '@storkspear/post-editor-core/proseWidth';
3
+ /**
4
+ * 본문 폭 고르개 — 「가운데폭 / 전체폭」.
5
+ *
6
+ * **라이브러리가 들고 있다.** 이 버튼이 시연 껍데기에만 있으면 소비자마다 다시 만들게 되고,
7
+ * 그때마다 `width` 에 무엇을 넘겨야 하는지도 다시 알아내야 한다.
8
+ * 편집기와 발행기가 같은 값을 받아야 두 화면이 같아지므로, 그 값을 만드는 곳도 한 곳이어야 한다.
9
+ *
10
+ * 놓을 자리는 소비자가 정한다(머리줄·도구줄 어디든). 상태는 소비자가 들고 있는다 —
11
+ * 그래야 발행 쪽 `PostContent` 에 같은 값을 넘길 수 있다.
12
+ */
13
+ export function WidthToggle({ value, onChange, centered = CENTERED_WIDTH, className }) {
14
+ const isFull = value === 'full';
15
+ return (_jsxs("div", { "data-editor-ui": "", className: `wt${className ? ` ${className}` : ''}`, role: "group", "aria-label": "\uBCF8\uBB38 \uD3ED", children: [_jsx("button", { type: "button", className: isFull ? '' : 'is-on', "aria-pressed": !isFull, title: "\uAE00\uC904\uC744 \uC77D\uAE30 \uC88B\uC740 \uD3ED\uC73C\uB85C \uC881\uD799\uB2C8\uB2E4", onMouseDown: (e) => e.preventDefault(), onClick: () => onChange(centered), children: "\uAC00\uC6B4\uB370\uD3ED" }), _jsx("button", { type: "button", className: isFull ? 'is-on' : '', "aria-pressed": isFull, title: "\uAE00\uC904\uAE4C\uC9C0 \uD654\uBA74 \uB05D\uAE4C\uC9C0 \uC501\uB2C8\uB2E4", onMouseDown: (e) => e.preventDefault(), onClick: () => onChange('full'), children: "\uC804\uCCB4\uD3ED" })] }));
16
+ }
17
+ //# sourceMappingURL=WidthToggle.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WidthToggle.js","sourceRoot":"","sources":["../src/WidthToggle.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,cAAc,EAAmB,MAAM,yCAAyC,CAAA;AAEzF;;;;;;;;;GASG;AACH,MAAM,UAAU,WAAW,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,GAAG,cAAc,EAAE,SAAS,EAMlF;IACC,MAAM,MAAM,GAAG,KAAK,KAAK,MAAM,CAAA;IAC/B,OAAO,CACL,iCAAoB,EAAE,EAAC,SAAS,EAAE,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,IAAI,EAAC,OAAO,gBAAY,qBAAM,aACvG,iBAAQ,IAAI,EAAC,QAAQ,EAAC,SAAS,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,kBAAgB,CAAC,MAAM,EAC3E,KAAK,EAAC,0FAAoB,EAC1B,WAAW,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,cAAc,EAAE,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,yCAAe,EAC1F,iBAAQ,IAAI,EAAC,QAAQ,EAAC,SAAS,EAAE,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,kBAAgB,MAAM,EAC1E,KAAK,EAAC,6EAAiB,EACvB,WAAW,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,cAAc,EAAE,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,mCAAc,IACnF,CACP,CAAA;AACH,CAAC"}
@@ -0,0 +1,10 @@
1
+ import type { Editor } from '@tiptap/react';
2
+ /**
3
+ * 글자 버블이 뜰 상황인가 — **한 곳에서만** 정한다.
4
+ *
5
+ * 두 곳에서 쓴다: 버블 자신(`shouldShow`)과, 버블이 뜨면 내려가야 하는 블록 바(`ObjectFloat`).
6
+ * 조건을 양쪽에 적어 두면 언젠가 어긋나고, 어긋나는 순간 **바 두 개가 겹친다**
7
+ *.
8
+ */
9
+ export declare function bubbleShows(editor: Editor): boolean;
10
+ //# sourceMappingURL=bubbleShows.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bubbleShows.d.ts","sourceRoot":"","sources":["../src/bubbleShows.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,eAAe,CAAA;AAE3C;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAenD"}
@@ -0,0 +1,29 @@
1
+ import { TextSelection } from '@tiptap/pm/state';
2
+ /**
3
+ * 글자 버블이 뜰 상황인가 — **한 곳에서만** 정한다.
4
+ *
5
+ * 두 곳에서 쓴다: 버블 자신(`shouldShow`)과, 버블이 뜨면 내려가야 하는 블록 바(`ObjectFloat`).
6
+ * 조건을 양쪽에 적어 두면 언젠가 어긋나고, 어긋나는 순간 **바 두 개가 겹친다**
7
+ *.
8
+ */
9
+ export function bubbleShows(editor) {
10
+ if (!editor.isEditable)
11
+ return false;
12
+ const { selection } = editor.state;
13
+ // NodeSelection(구분선·사진 등)도 from!==to 라 글자 버블이 떴다. 글자를 잡았을 때만이다.
14
+ if (!(selection instanceof TextSelection))
15
+ return false;
16
+ if (editor.isActive('codeBlock'))
17
+ return false;
18
+ if (selection.from !== selection.to)
19
+ return true;
20
+ /**
21
+ * 인용구는 커서만 들어와도 띄운다.
22
+ *
23
+ * 전에는 인용 위에 따로 뜨는 바가 있었는데, 글자를 잡으면 그 바와 글자 버블이 **둘 다** 떠서
24
+ * 서로 포갰다. 바를 없애고 이 하나로 합쳤다.
25
+ * 인용 안에서는 글자 서식이 인용 전체에 걸리므로(quoteWide) 잡은 글자가 없어도 할 일이 있다.
26
+ */
27
+ return editor.isActive('blockquote');
28
+ }
29
+ //# sourceMappingURL=bubbleShows.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bubbleShows.js","sourceRoot":"","sources":["../src/bubbleShows.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAA;AAGhD;;;;;;GAMG;AACH,MAAM,UAAU,WAAW,CAAC,MAAc;IACxC,IAAI,CAAC,MAAM,CAAC,UAAU;QAAE,OAAO,KAAK,CAAA;IACpC,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,CAAC,KAAK,CAAA;IAClC,gEAAgE;IAChE,IAAI,CAAC,CAAC,SAAS,YAAY,aAAa,CAAC;QAAE,OAAO,KAAK,CAAA;IACvD,IAAI,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAC;QAAE,OAAO,KAAK,CAAA;IAC9C,IAAI,SAAS,CAAC,IAAI,KAAK,SAAS,CAAC,EAAE;QAAE,OAAO,IAAI,CAAA;IAChD;;;;;;OAMG;IACH,OAAO,MAAM,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAA;AACtC,CAAC"}
@@ -0,0 +1,22 @@
1
+ import { type ProseWidth } from '@storkspear/post-editor-core/proseWidth';
2
+ /**
3
+ * 발행 결과를 **구워서** 그리는 쪽 — 코드 하이라이팅·줄번호·표 감싸개까지.
4
+ *
5
+ * `PostContent`(`./content`)와 왜 나뉘어 있는가:
6
+ * `toPublishedHtml` 은 hast 파서(parse5)와 하이라이터(언어 12종)를 끌고 온다. 방문자에게 가는
7
+ * 공개 페이지가 그걸 받으면 번들이 두 배가 된다 — 실제로 255KB 가 512KB 가 됐다. 그래서
8
+ * **공개 페이지는 `./content` 의 가벼운 쪽**을 쓰고, 굽기는 두 곳에서만 한다.
9
+ *
10
+ * 1. 정적 생성기(Node) — 발행할 때 한 번 구워서 파일로 떨군다. 방문자는 구운 결과만 받는다
11
+ * 2. 관리 콘솔처럼 **저장된 원본을 그대로 보여주는 내부 화면** — 발행 단계를 거치지 않으므로
12
+ * 볼 때 굽는다. 내부 화면이라 번들 크기가 제약이 아니다
13
+ *
14
+ * 굽지 않으면 코드블록이 민짜 글자로 나오고 편집 화면과 달라 보인다.
15
+ */
16
+ export declare function BakedPostContent({ html, className, width }: {
17
+ html: string;
18
+ className?: string;
19
+ /** 본문 폭. 편집 화면에 준 것과 **같은 값**을 줘야 두 화면이 같아 보인다. */
20
+ width?: ProseWidth;
21
+ }): import("react").JSX.Element;
22
+ //# sourceMappingURL=content-baked.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"content-baked.d.ts","sourceRoot":"","sources":["../src/content-baked.tsx"],"names":[],"mappings":"AACA,OAAO,EAAmB,KAAK,UAAU,EAAE,MAAM,yCAAyC,CAAA;AAG1F;;;;;;;;;;;;;GAaG;AACH,wBAAgB,gBAAgB,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,EAAE;IAC3D,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,mDAAmD;IACnD,KAAK,CAAC,EAAE,UAAU,CAAA;CACnB,+BAOA"}
@@ -0,0 +1,21 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { proseWidthStyle } from '@storkspear/post-editor-core/proseWidth';
3
+ import { toPublishedHtml } from '@storkspear/post-editor-core/serialize';
4
+ /**
5
+ * 발행 결과를 **구워서** 그리는 쪽 — 코드 하이라이팅·줄번호·표 감싸개까지.
6
+ *
7
+ * `PostContent`(`./content`)와 왜 나뉘어 있는가:
8
+ * `toPublishedHtml` 은 hast 파서(parse5)와 하이라이터(언어 12종)를 끌고 온다. 방문자에게 가는
9
+ * 공개 페이지가 그걸 받으면 번들이 두 배가 된다 — 실제로 255KB 가 512KB 가 됐다. 그래서
10
+ * **공개 페이지는 `./content` 의 가벼운 쪽**을 쓰고, 굽기는 두 곳에서만 한다.
11
+ *
12
+ * 1. 정적 생성기(Node) — 발행할 때 한 번 구워서 파일로 떨군다. 방문자는 구운 결과만 받는다
13
+ * 2. 관리 콘솔처럼 **저장된 원본을 그대로 보여주는 내부 화면** — 발행 단계를 거치지 않으므로
14
+ * 볼 때 굽는다. 내부 화면이라 번들 크기가 제약이 아니다
15
+ *
16
+ * 굽지 않으면 코드블록이 민짜 글자로 나오고 편집 화면과 달라 보인다.
17
+ */
18
+ export function BakedPostContent({ html, className, width }) {
19
+ return (_jsx("div", { className: `prose${className ? ` ${className}` : ''}`, style: proseWidthStyle(width), children: _jsx("div", { className: "prose-body", dangerouslySetInnerHTML: { __html: toPublishedHtml(html) } }) }));
20
+ }
21
+ //# sourceMappingURL=content-baked.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"content-baked.js","sourceRoot":"","sources":["../src/content-baked.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,eAAe,EAAmB,MAAM,yCAAyC,CAAA;AAC1F,OAAO,EAAE,eAAe,EAAE,MAAM,wCAAwC,CAAA;AAExE;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,gBAAgB,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAKxD;IACC,OAAO,CACL,cAAK,SAAS,EAAE,QAAQ,SAAS,CAAC,CAAC,CAAC,IAAI,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,EACxD,KAAK,EAAE,eAAe,CAAC,KAAK,CAA8B,YAC1D,cAAK,SAAS,EAAC,YAAY,EAAC,uBAAuB,EAAE,EAAE,MAAM,EAAE,eAAe,CAAC,IAAI,CAAC,EAAE,GAAI,GACtF,CACP,CAAA;AACH,CAAC"}
@@ -0,0 +1,29 @@
1
+ import { type ProseWidth } from '@storkspear/post-editor-core/proseWidth';
2
+ import { type BannerBleed, type TitleValue } from '@storkspear/post-editor-core/title';
3
+ /**
4
+ * 발행된 글을 그리는 쪽 — **편집기 코드가 한 바이트도 들어가지 않는다.**
5
+ *
6
+ * 이 파일이 따로 있는 이유가 그것이다. `index.tsx` 에서 가져오면 편집기 전체가 딸려 오므로
7
+ * 공개 빌드는 반드시 `@storkspear/post-editor-react/content` 로 가져와야 한다.
8
+ * (공개 정적 사이트에 편집 코드가 실리면, 숨긴 버튼은 개발자 도구로 되살아난다.)
9
+ */
10
+ export declare function PostContent({ html, className, width }: {
11
+ html: string;
12
+ className?: string;
13
+ /** 본문 폭. 편집 화면에 준 것과 **같은 값**을 줘야 두 화면이 같아 보인다. */
14
+ width?: ProseWidth;
15
+ }): import("react").JSX.Element;
16
+ /**
17
+ * 발행된 제목(배너 포함).
18
+ *
19
+ * **편집 화면과 같은 렌더러를 쓰지 않는다** — `PostTitle` 은 편집 요소라 편집기를 끌고 온다.
20
+ * 대신 둘 다 core 의 `renderPostHead` 가 정한 **같은 DOM 구조**를 그린다.
21
+ * 정적 생성기(Node)도 같은 함수를 부르므로 세 곳이 한 정의를 본다.
22
+ */
23
+ export declare function PostHead({ title, width, bleed }: {
24
+ title: TitleValue;
25
+ width?: ProseWidth;
26
+ /** 편집 화면에 준 것과 **같은 값**이어야 두 화면이 같아진다 */
27
+ bleed?: BannerBleed;
28
+ }): import("react").JSX.Element;
29
+ //# sourceMappingURL=content.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"content.d.ts","sourceRoot":"","sources":["../src/content.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAmB,KAAK,UAAU,EAAE,MAAM,yCAAyC,CAAA;AAC1F,OAAO,EAAkB,KAAK,WAAW,EAAE,KAAK,UAAU,EAAE,MAAM,oCAAoC,CAAA;AAGtG;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,EAAE;IACtD,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,mDAAmD;IACnD,KAAK,CAAC,EAAE,UAAU,CAAA;CACnB,+BAgBA;AAED;;;;;;GAMG;AACH,wBAAgB,QAAQ,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,KAAmB,EAAE,EAAE;IAC9D,KAAK,EAAE,UAAU,CAAA;IACjB,KAAK,CAAC,EAAE,UAAU,CAAA;IAClB,yCAAyC;IACzC,KAAK,CAAC,EAAE,WAAW,CAAA;CACpB,+BAKA"}
@@ -0,0 +1,35 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ // **배럴(index)에서 가져오면 안 된다** — 확장·명령이 통째로 딸려 와 공개 빌드에 편집기가 실린다.
3
+ import { proseWidthStyle } from '@storkspear/post-editor-core/proseWidth';
4
+ import { renderPostHead } from '@storkspear/post-editor-core/title';
5
+ import { resolveAttachmentSrc } from '@storkspear/post-editor-core/attachments';
6
+ /**
7
+ * 발행된 글을 그리는 쪽 — **편집기 코드가 한 바이트도 들어가지 않는다.**
8
+ *
9
+ * 이 파일이 따로 있는 이유가 그것이다. `index.tsx` 에서 가져오면 편집기 전체가 딸려 오므로
10
+ * 공개 빌드는 반드시 `@storkspear/post-editor-react/content` 로 가져와야 한다.
11
+ * (공개 정적 사이트에 편집 코드가 실리면, 숨긴 버튼은 개발자 도구로 되살아난다.)
12
+ */
13
+ export function PostContent({ html, className, width }) {
14
+ /**
15
+ * `attachment://{id}` 를 여기서 푼다 — 소비자가 기억해야 하는 일로 두지 않는다.
16
+ * 본문에는 주소가 아니라 참조만 들어 있으므로, 그대로 그리면 사진 자리가 전부 빈다.
17
+ *
18
+ * **`toPublishedHtml` 을 부르지 않는다.** 그쪽은 hast 파이프라인(parse5·하이라이터)을
19
+ * 끌고 와서, 편집기 코드가 한 바이트도 안 들어간다는 이 파일의 전제를 깬다 — 한 번
20
+ * 그렇게 만들었다가 공개 빌드가 220KB 에서 512KB 가 됐다. 표 감싸개·코드 굽기 같은
21
+ * 구조 보정은 발행 시점(정적 생성기, Node)에 한 번 하고 그 결과를 여기에 넘긴다.
22
+ */
23
+ return (_jsx("div", { className: `prose${className ? ` ${className}` : ''}`, style: proseWidthStyle(width), children: _jsx("div", { className: "prose-body", dangerouslySetInnerHTML: { __html: resolveAttachmentSrc(html) } }) }));
24
+ }
25
+ /**
26
+ * 발행된 제목(배너 포함).
27
+ *
28
+ * **편집 화면과 같은 렌더러를 쓰지 않는다** — `PostTitle` 은 편집 요소라 편집기를 끌고 온다.
29
+ * 대신 둘 다 core 의 `renderPostHead` 가 정한 **같은 DOM 구조**를 그린다.
30
+ * 정적 생성기(Node)도 같은 함수를 부르므로 세 곳이 한 정의를 본다.
31
+ */
32
+ export function PostHead({ title, width, bleed = 'container' }) {
33
+ return (_jsx("div", { style: proseWidthStyle(width), dangerouslySetInnerHTML: { __html: renderPostHead(title, bleed) } }));
34
+ }
35
+ //# sourceMappingURL=content.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"content.js","sourceRoot":"","sources":["../src/content.tsx"],"names":[],"mappings":";AACA,+DAA+D;AAC/D,OAAO,EAAE,eAAe,EAAmB,MAAM,yCAAyC,CAAA;AAC1F,OAAO,EAAE,cAAc,EAAqC,MAAM,oCAAoC,CAAA;AACtG,OAAO,EAAE,oBAAoB,EAAE,MAAM,0CAA0C,CAAA;AAE/E;;;;;;GAMG;AACH,MAAM,UAAU,WAAW,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAKnD;IACC;;;;;;;;OAQG;IACH,OAAO,CACL,cAAK,SAAS,EAAE,QAAQ,SAAS,CAAC,CAAC,CAAC,IAAI,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,EACxD,KAAK,EAAE,eAAe,CAAC,KAAK,CAA8B,YAC1D,cAAK,SAAS,EAAC,YAAY,EAAC,uBAAuB,EAAE,EAAE,MAAM,EAAE,oBAAoB,CAAC,IAAI,CAAC,EAAE,GAAI,GAC3F,CACP,CAAA;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,QAAQ,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,GAAG,WAAW,EAK3D;IACC,OAAO,CACL,cAAK,KAAK,EAAE,eAAe,CAAC,KAAK,CAA8B,EAC7D,uBAAuB,EAAE,EAAE,MAAM,EAAE,cAAc,CAAC,KAAK,EAAE,KAAK,CAAC,EAAE,GAAI,CACxE,CAAA;AACH,CAAC"}
@@ -0,0 +1,26 @@
1
+ import { type NodeViewProps } from '@tiptap/react';
2
+ /**
3
+ * 다이어그램 NodeView.
4
+ *
5
+ * ## 무엇을 그리나
6
+ *
7
+ * 문서에 든 `svg` 를 그대로 보여 준다. **원본(`src`)이 바뀌면 다시 굽는다** — mermaid 는
8
+ * 여기서(브라우저) 굽고, draw.io 는 모달이 돌려준 것을 받는다.
9
+ *
10
+ * ## 안 잡았을 때는 발행본과 똑같다
11
+ *
12
+ * 잡아야 편집 껍데기(`.dg-shell`)가 생기고, 그 안에서 미리보기와 원본 칸이 테두리 하나로
13
+ * 묶인다. 이 껍데기는 **편집기에만 있다** — `getHTML()` 은 스키마의 `renderHTML`
14
+ * (figure + figcaption)을 쓰므로 NodeView 의 DOM 은 발행에 안 나간다.
15
+ *
16
+ * 이렇게 가른 이유: 안 잡았을 때까지 껍데기를 씌우면 그림 상자가 `fit-content` 를 잃고
17
+ * 글 폭을 다 먹는다. 그러면 편집 화면과 발행 페이지가 달라진다.
18
+ *
19
+ * ## 왜 `dangerouslySetInnerHTML` 인가
20
+ *
21
+ * SVG 는 문자열이라 React 엘리먼트로 못 넣는다. 대신 **들어오기 전에 씻는다** —
22
+ * `normalizeDoc` 이 문서 입구에서, `renderMermaid` 가 만드는 자리에서, 발행이 마지막에.
23
+ * 세 곳 다 같은 `safeSvg` 다.
24
+ */
25
+ export declare function DiagramView({ node, updateAttributes, selected, editor, getPos }: NodeViewProps): import("react").JSX.Element;
26
+ //# sourceMappingURL=DiagramView.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DiagramView.d.ts","sourceRoot":"","sources":["../../src/extensions/DiagramView.tsx"],"names":[],"mappings":"AACA,OAAO,EAAoC,KAAK,aAAa,EAAE,MAAM,eAAe,CAAA;AAIpF;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,WAAW,CAAC,EAAE,IAAI,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,aAAa,+BAkK9F"}
@@ -0,0 +1,146 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react';
3
+ import { NodeViewContent, NodeViewWrapper } from '@tiptap/react';
4
+ import { mermaidError, mermaidKind, renderMermaid } from '@storkspear/post-editor-core/mermaid';
5
+ /**
6
+ * 다이어그램 NodeView.
7
+ *
8
+ * ## 무엇을 그리나
9
+ *
10
+ * 문서에 든 `svg` 를 그대로 보여 준다. **원본(`src`)이 바뀌면 다시 굽는다** — mermaid 는
11
+ * 여기서(브라우저) 굽고, draw.io 는 모달이 돌려준 것을 받는다.
12
+ *
13
+ * ## 안 잡았을 때는 발행본과 똑같다
14
+ *
15
+ * 잡아야 편집 껍데기(`.dg-shell`)가 생기고, 그 안에서 미리보기와 원본 칸이 테두리 하나로
16
+ * 묶인다. 이 껍데기는 **편집기에만 있다** — `getHTML()` 은 스키마의 `renderHTML`
17
+ * (figure + figcaption)을 쓰므로 NodeView 의 DOM 은 발행에 안 나간다.
18
+ *
19
+ * 이렇게 가른 이유: 안 잡았을 때까지 껍데기를 씌우면 그림 상자가 `fit-content` 를 잃고
20
+ * 글 폭을 다 먹는다. 그러면 편집 화면과 발행 페이지가 달라진다.
21
+ *
22
+ * ## 왜 `dangerouslySetInnerHTML` 인가
23
+ *
24
+ * SVG 는 문자열이라 React 엘리먼트로 못 넣는다. 대신 **들어오기 전에 씻는다** —
25
+ * `normalizeDoc` 이 문서 입구에서, `renderMermaid` 가 만드는 자리에서, 발행이 마지막에.
26
+ * 세 곳 다 같은 `safeSvg` 다.
27
+ */
28
+ export function DiagramView({ node, updateAttributes, selected, editor, getPos }) {
29
+ const engine = node.attrs.engine ?? 'mermaid';
30
+ const align = node.attrs.align ?? 'center';
31
+ const width = Number(node.attrs.width) || 100;
32
+ const src = String(node.attrs.src ?? '');
33
+ const svg = String(node.attrs.svg ?? '');
34
+ const [err, setErr] = useState(null);
35
+ const [busy, setBusy] = useState(false);
36
+ /**
37
+ * 마지막으로 구운 원본. 같은 원본을 두 번 굽지 않는다 —
38
+ * `updateAttributes` 가 다시 렌더를 부르므로, 이게 없으면 무한히 돈다.
39
+ */
40
+ const bakedRef = useRef('');
41
+ useEffect(() => { if (svg && !bakedRef.current)
42
+ bakedRef.current = src; }, []);
43
+ useEffect(() => {
44
+ if (engine !== 'mermaid')
45
+ return;
46
+ if (!src.trim()) {
47
+ setErr(null);
48
+ /* 원본을 지웠으면 그림도 지운다. 안 그러면 **옛 그림이 남아** 빈 칸 위에 떠 있다 */
49
+ bakedRef.current = '';
50
+ if (svg)
51
+ updateAttributes({ svg: '' });
52
+ return;
53
+ }
54
+ if (bakedRef.current === src && svg)
55
+ return;
56
+ let alive = true;
57
+ setBusy(true);
58
+ /* 타자 중에는 굽지 않는다 — 300ms 쉬면 그때 한 번 */
59
+ const id = setTimeout(async () => {
60
+ const out = await renderMermaid(src);
61
+ if (!alive)
62
+ return;
63
+ setBusy(false);
64
+ bakedRef.current = src;
65
+ /* 문법이 깨진 동안에도 **앞 그림은 남긴다** — 한 글자 칠 때마다 사라지면 못 쓴다 */
66
+ if (out.error) {
67
+ setErr(out.error);
68
+ return;
69
+ }
70
+ setErr(null);
71
+ if (out.svg !== svg)
72
+ updateAttributes({ svg: out.svg });
73
+ }, 300);
74
+ return () => { alive = false; clearTimeout(id); setBusy(false); };
75
+ }, [src, engine]);
76
+ const onSrc = useCallback((v) => { setErr(null); updateAttributes({ src: v }); }, [updateAttributes]);
77
+ /**
78
+ * 원본 칸은 **내용만큼 자란다.**
79
+ * `rows` 로 잡았더니 바닥이 4줄(≈150px)이라 한 줄짜리 원본에도 빈 칸이 한참 남았다.
80
+ * 소스코드 블록의 설명 입력칸(`.cb-note-input`)이 쓰는 수법을 그대로 쓴다.
81
+ */
82
+ const taRef = useRef(null);
83
+ const fit = useCallback(() => {
84
+ const el = taRef.current;
85
+ if (!el)
86
+ return;
87
+ el.style.height = 'auto';
88
+ el.style.height = `${el.scrollHeight}px`;
89
+ }, []);
90
+ useLayoutEffect(fit, [src, selected, engine]);
91
+ const editable = editor.isEditable;
92
+ /**
93
+ * 편집 껍데기는 **한 번 열리면 밖을 누를 때까지 열려 있다.**
94
+ *
95
+ * `selected` 만 보고 열었더니 **첫 글자에 닫혔다.** 원본을 고치면 `updateAttributes` 가
96
+ * `setNodeMarkup` 을 부르고, 노드가 새것으로 바뀌면서 ProseMirror 의 NodeSelection 이
97
+ * 글자 선택으로 떨어진다 — 잡은 표시도 같이 사라진다. 그러면 칸이 통째로 언마운트돼
98
+ * 커서까지 날아갔다(실측: 한 글자 치면 `activeElement` 가 body 로 돌아왔다).
99
+ *
100
+ * 그래서 여는 것은 `selected` 가, **닫는 것은 바깥 클릭이** 맡는다.
101
+ * 다른 블록을 누르면 그 mousedown 이 이 노드 밖이라 알아서 닫힌다.
102
+ */
103
+ const rootRef = useRef(null);
104
+ const [latched, setLatched] = useState(false);
105
+ useEffect(() => { if (selected)
106
+ setLatched(true); }, [selected]);
107
+ useEffect(() => {
108
+ if (!latched)
109
+ return;
110
+ const onDown = (e) => {
111
+ if (rootRef.current?.contains(e.target))
112
+ return;
113
+ setLatched(false);
114
+ };
115
+ document.addEventListener('mousedown', onDown, true);
116
+ return () => document.removeEventListener('mousedown', onDown, true);
117
+ }, [latched]);
118
+ const open = editable && (selected || latched);
119
+ const kind = engine === 'mermaid' ? mermaidKind(src) : '도면';
120
+ const problem = err ? mermaidError(err) : null;
121
+ /**
122
+ * 그림을 누르면 **이 노드를 잡는다.**
123
+ *
124
+ * 그냥 두면 클릭이 캡션의 글자 선택으로 떨어져 `ProseMirror-selectednode` 가 안 붙고,
125
+ * 잡은 표시도 도구줄도 안 나온다(실제로 그랬다). 사진도 같은 이유로 제 플러그인에서
126
+ * 이 일을 한다 — 캡션을 품은 노드는 클릭을 스스로 해석해 줘야 한다.
127
+ */
128
+ const grab = (e) => {
129
+ if (!editor.isEditable)
130
+ return;
131
+ const pos = typeof getPos === 'function' ? getPos() : null;
132
+ if (pos == null)
133
+ return;
134
+ e.preventDefault();
135
+ editor.chain().focus().setNodeSelection(pos).run();
136
+ };
137
+ const body = (_jsxs("div", { className: "dg-body", contentEditable: false, onMouseDown: grab, children: [svg
138
+ ? _jsx("div", { className: "dg-svg", dangerouslySetInnerHTML: { __html: svg } })
139
+ : _jsx("div", { className: "dg-empty", children: engine === 'drawio'
140
+ ? '「그리기」를 눌러 Draw.io 에서 그리세요'
141
+ : '아래 칸에 Mermaid 문법을 쓰면 여기 그려집니다' }), busy && _jsx("span", { className: "dg-busy", "aria-hidden": "true" })] }));
142
+ return (_jsxs(NodeViewWrapper, { as: 'figure', className: "dg", "data-diagram": "", "data-engine": engine, "data-align": align, "data-selected": selected ? '' : undefined, style: { width: `${width}%` }, ref: rootRef, children: [open ? (_jsxs("div", { className: "dg-shell", contentEditable: false, "data-editor-ui": "", children: [body, _jsxs("div", { className: "dg-panel", children: [_jsxs("div", { className: "dg-bar", children: [_jsx("b", { children: engine === 'mermaid' ? 'Mermaid' : 'Draw.io' }), _jsx("span", { className: "dg-kind", children: kind })] }), engine === 'mermaid' && (_jsx("textarea", { ref: taRef, className: "dg-input", value: src, spellCheck: false, placeholder: 'sequenceDiagram\n 사용자->>서버: 로그인', onChange: (e) => { onSrc(e.target.value); fit(); },
143
+ /* 편집기가 이 타자를 제 것으로 먹지 않게 */
144
+ onKeyDown: (e) => e.stopPropagation(), onPaste: (e) => e.stopPropagation() })), problem && (_jsxs("p", { className: "dg-err", role: "alert", children: [problem.head, problem.detail && _jsx("code", { children: problem.detail })] }))] })] })) : body, _jsx(NodeViewContent, { as: 'figcaption' })] }));
145
+ }
146
+ //# sourceMappingURL=DiagramView.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DiagramView.js","sourceRoot":"","sources":["../../src/extensions/DiagramView.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import { type NodeViewProps } from '@tiptap/react';
2
+ export declare function FigureView({ node, updateAttributes, selected, editor, getPos }: NodeViewProps): import("react").JSX.Element;
3
+ //# sourceMappingURL=FigureView.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FigureView.d.ts","sourceRoot":"","sources":["../../src/extensions/FigureView.tsx"],"names":[],"mappings":"AACA,OAAO,EAAoC,KAAK,aAAa,EAAE,MAAM,eAAe,CAAA;AAWpF,wBAAgB,UAAU,CAAC,EAAE,IAAI,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,aAAa,+BAyH7F"}
@@ -0,0 +1,108 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useCallback, useRef, useState } from 'react';
3
+ import { NodeViewContent, NodeViewWrapper } from '@tiptap/react';
4
+ import { resolveSrc } from '@storkspear/post-editor-core/attachments';
5
+ /** 8방향 손잡이 — 모델이 폭 하나뿐이므로 세로/대각도 폭으로 환산한다 */
6
+ const GRIPS = [
7
+ { k: 'nw', dx: -1, dy: -1 }, { k: 'n', dx: 0, dy: -1 }, { k: 'ne', dx: 1, dy: -1 },
8
+ { k: 'w', dx: -1, dy: 0 }, { k: 'e', dx: 1, dy: 0 },
9
+ { k: 'sw', dx: -1, dy: 1 }, { k: 's', dx: 0, dy: 1 }, { k: 'se', dx: 1, dy: 1 },
10
+ ];
11
+ export function FigureView({ node, updateAttributes, selected, editor, getPos }) {
12
+ const width = Number(node.attrs.width) || 100;
13
+ const align = node.attrs.align ?? 'center';
14
+ const href = node.attrs.href;
15
+ const wrapRef = useRef(null);
16
+ const [dragging, setDragging] = useState(false);
17
+ const [live, setLive] = useState(null);
18
+ const onDown = useCallback((dx, dy) => (e) => {
19
+ if (!editor.isEditable)
20
+ return;
21
+ e.preventDefault();
22
+ e.stopPropagation();
23
+ const fig = wrapRef.current;
24
+ const hostW = fig?.parentElement?.getBoundingClientRect().width ?? 1;
25
+ const img = fig?.querySelector('img');
26
+ const ratio = img ? img.getBoundingClientRect().width / Math.max(1, img.getBoundingClientRect().height) : 1;
27
+ const startX = e.clientX, startY = e.clientY, startW = width;
28
+ const target = e.currentTarget;
29
+ target.setPointerCapture(e.pointerId);
30
+ setDragging(true);
31
+ let latest = startW;
32
+ const move = (ev) => {
33
+ const mx = (ev.clientX - startX) * dx;
34
+ const my = (ev.clientY - startY) * dy * ratio;
35
+ // 세로/대각은 더 크게 움직인 쪽을 따른다
36
+ const signed = Math.abs(mx) >= Math.abs(my) ? mx : my;
37
+ // 가운데 정렬은 양쪽이 같이 움직이므로 두 배
38
+ const k = align === 'center' ? 2 : 1;
39
+ const next = Math.round(Math.min(100, Math.max(15, startW + (signed * k * 100) / hostW)));
40
+ // 이미지가 아직 안 실렸으면 ratio 가 무한대가 되어 NaN 이 나온다.
41
+ // NaN 이 폭으로 들어가면 style="width:NaN%" 가 되어 이미지가 통째로 사라진다.
42
+ latest = Number.isFinite(next) ? next : startW;
43
+ setLive(latest);
44
+ };
45
+ const up = () => {
46
+ try {
47
+ target.releasePointerCapture(e.pointerId);
48
+ }
49
+ catch { /* 이미 풀렸으면 그만 */ }
50
+ target.removeEventListener('pointermove', move);
51
+ target.removeEventListener('pointerup', up);
52
+ target.removeEventListener('pointercancel', up);
53
+ target.removeEventListener('lostpointercapture', up);
54
+ setDragging(false);
55
+ setLive(null);
56
+ if (latest !== startW)
57
+ updateAttributes({ width: latest });
58
+ };
59
+ target.addEventListener('pointermove', move);
60
+ target.addEventListener('pointerup', up);
61
+ // 브라우저가 포인터를 회수하면 pointerup 이 안 온다 — 리스너가 남아
62
+ // 버튼을 뗀 뒤에도 마우스만 움직이면 크기가 계속 바뀐다
63
+ target.addEventListener('pointercancel', up);
64
+ target.addEventListener('lostpointercapture', up);
65
+ }, [editor, updateAttributes, width, align]);
66
+ /**
67
+ * 그림을 누르면 그림이 잡히게 한다.
68
+ *
69
+ * figure 안에는 편집 가능한 영역(캡션)이 있어서 ProseMirror 는 이 노드를 텍스트 블록처럼 다룬다.
70
+ * 그런데 이미지는 글자가 아니라 브라우저가 커서를 옮기지 않고, 그래서 선택이 **아무 것도 안 바뀐다** —
71
+ * 눌러도 도구가 안 뜨고, 한 번 놓치면 다시 잡을 방법이 없다.
72
+ *
73
+ * **mousedown 이 아니라 click 에서 한다.** mousedown 에서 하면 ProseMirror 가 mouseup 때
74
+ * 자기 선택으로 덮어써서 커서가 캡션으로 들어간다. `preventDefault()` 로 막으면 안 된다 —
75
+ * 그 한 줄이 **끌기 시작까지 같이 취소해** 사진이 아예 안 끌렸다.
76
+ * click 은 mouseup 뒤라 덮어쓸 것이 없고, 드래그로 끝난 경우엔 아예 안 온다.
77
+ */
78
+ const pick = useCallback((ev) => {
79
+ if (!editor.isEditable)
80
+ return;
81
+ if (ev.target.closest('.fig-grip'))
82
+ return; // 크기 손잡이는 제 일을 한다
83
+ const pos = typeof getPos === 'function' ? getPos() : null;
84
+ if (typeof pos !== 'number')
85
+ return;
86
+ editor.commands.setNodeSelection(pos);
87
+ }, [editor, getPos]);
88
+ const shown = live ?? width;
89
+ /**
90
+ * 사진 행 안인가 — 행 안에서는 **폭을 사진이 정하지 않는다**(행이 정한다).
91
+ * 손잡이와 % 배지를 그대로 두면 끌 수는 있는데 화면은 안 변해 고장으로 보인다.
92
+ */
93
+ const inRow = (() => {
94
+ const pos = typeof getPos === 'function' ? getPos() : null;
95
+ if (pos == null)
96
+ return false;
97
+ try {
98
+ return editor.state.doc.resolve(pos).parent.type.name === 'imageRow';
99
+ }
100
+ catch {
101
+ return false;
102
+ }
103
+ })();
104
+ // 본문에는 attachment://{id} 만 있다 — 화면에 그릴 때만 주소로 푼다
105
+ const img = _jsx("img", { src: resolveSrc(node.attrs.src) || undefined, alt: node.attrs.alt ?? '', draggable: editor.isEditable });
106
+ return (_jsxs(NodeViewWrapper, { as: "figure", ref: wrapRef, "data-figure": "", "data-align": align, "data-caption": node.attrs.caption ?? 'below', style: { width: `${shown}%` }, className: selected || dragging ? 'is-active' : undefined, children: [_jsxs("div", { className: "fig-frame", contentEditable: false, onClick: pick, draggable: editor.isEditable, "data-drag-handle": "", children: [href ? _jsx("a", { href: href, rel: "noopener", target: "_blank", onClick: (e) => e.preventDefault(), children: img }) : img, editor.isEditable && !inRow && (_jsxs(_Fragment, { children: [GRIPS.map((g) => (_jsx("span", { className: `fig-grip fig-grip-${g.k}`, "data-ui": "", draggable: false, onDragStart: (ev) => ev.preventDefault(), onPointerDown: onDown(g.dx, g.dy) }, g.k))), (dragging || selected) && _jsxs("span", { className: "fig-size", "data-ui": "", children: [shown, "%"] })] }))] }), _jsx(NodeViewContent, { as: 'figcaption' })] }));
107
+ }
108
+ //# sourceMappingURL=FigureView.js.map