@teriusu/rich-editor 0.5.3 → 0.6.1

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.
package/README.md CHANGED
@@ -1,25 +1,25 @@
1
- # @hancomac/rich-editor
1
+ # @teriusu/rich-editor
2
2
 
3
3
  TipTap v3 기반 리치 텍스트 에디터. Svelte 5 컴포넌트 라이브러리.
4
4
 
5
5
  ## 설치
6
6
 
7
7
  ```bash
8
- npm install @hancomac/rich-editor
8
+ npm install @teriusu/rich-editor
9
9
  ```
10
10
 
11
11
  Tailwind CSS v4 사용 시 `@source` 추가:
12
12
  ```css
13
- @import "@hancomac/rich-editor/styles";
14
- @source "../node_modules/@hancomac/rich-editor/dist";
13
+ @import "@teriusu/rich-editor/styles";
14
+ @source "../node_modules/@teriusu/rich-editor/dist";
15
15
  ```
16
16
 
17
17
  ## 기본 사용법
18
18
 
19
19
  ```svelte
20
20
  <script>
21
- import { TipTapEditor } from '@hancomac/rich-editor';
22
- import '@hancomac/rich-editor/styles';
21
+ import { TipTapEditor } from '@teriusu/rich-editor';
22
+ import '@teriusu/rich-editor/styles';
23
23
 
24
24
  let html = $state('');
25
25
  </script>
@@ -121,7 +121,7 @@ TipTap v2 커스텀 태그를 자동 변환합니다. 에디터 로드 시 `tran
121
121
  게시물 조회 페이지(읽기 전용)에서도 적용:
122
122
 
123
123
  ```js
124
- import { transformLegacyHtml } from '@hancomac/rich-editor';
124
+ import { transformLegacyHtml } from '@teriusu/rich-editor';
125
125
 
126
126
  const html = transformLegacyHtml(post.content);
127
127
  ```
@@ -132,7 +132,7 @@ const html = transformLegacyHtml(post.content);
132
132
 
133
133
  ```svelte
134
134
  <script>
135
- import { TipTapEditor } from '@hancomac/rich-editor';
135
+ import { TipTapEditor } from '@teriusu/rich-editor';
136
136
  import { Node } from '@tiptap/core';
137
137
 
138
138
  const MyExtension = Node.create({ /* ... */ });
@@ -159,13 +159,46 @@ const html = transformLegacyHtml(post.content);
159
159
  ### 타입
160
160
  `UploadHandler`, `FileResolver`, `FileResolveResult`, `TipTapEditorProps`, `SlashMenuItem`
161
161
 
162
- ## 빌드
162
+ ## 빌드 / 배포
163
163
 
164
164
  ```bash
165
165
  npm run build # svelte-package → dist/
166
- npm version patch # 버전 올리기
166
+ npm test # vitest
167
167
  ```
168
168
 
169
+ 배포는 **main 브랜치 push 시 GitHub Actions가 자동 수행** (`.github/workflows/publish.yml`).
170
+ 수동 `npm publish`는 사용하지 않음.
171
+
172
+ ### 버전 정책 (SemVer)
173
+
174
+ | 단계 | 조건 | 커밋 타입 |
175
+ |------|------|-----------|
176
+ | **patch** (`0.0.x`) | 버그 수정, 내부 리팩토링, 스타일 조정, 의존성 patch | `fix:`, `chore:`, `refactor:`, `style:` |
177
+ | **minor** (`0.x.0`) | 하위 호환 기능 추가 (새 prop, 새 extension, 새 toolbar feature) | `feat:` |
178
+ | **major** (`x.0.0`) | 깨는 변경 (prop 제거·rename, peer dep major bump, export 경로 변경) | `feat!:` / `BREAKING CHANGE:` |
179
+
180
+ > `0.x`대에서는 관행상 **minor도 breaking일 수 있음**. 호스트 앱은 `^0.5.3` 대신 `~0.5.3` 사용을 권장.
181
+
182
+ ### 릴리스 플로우
183
+
184
+ 1. **patch만 필요한 변경**: 소스 수정 후 `main`에 push → CI가 `package.json`의 버전이 이미 publish된 최신과 같으면 **자동 patch bump** 후 publish
185
+ 2. **minor / major 변경**: push 전에 수동 bump
186
+ ```bash
187
+ npm version minor --no-git-tag-version # 또는 major
188
+ git add package.json package-lock.json
189
+ git commit -m "chore: v$(node -p 'require(\"./package.json\").version')"
190
+ git push
191
+ ```
192
+ CI가 "이미 bump됨"을 감지하고 그 버전으로 publish.
193
+
194
+ ### 자동 전파
195
+
196
+ publish 성공 시 `repository_dispatch` (`rich-editor-published`) 이벤트가 다음 repo로 전송되고, 각 repo의 `update-*` 워크플로우가 `package.json`을 새 버전으로 업데이트 + 커밋:
197
+ - `teriusu-ko/hancomac` → `@teriusu/rich-editor`
198
+ - `HancomAC/arcturus-next` → `apps/jungol`, `apps/codepass`의 `@teriusu/rich-editor`
199
+
200
+ `teriusu-ko/teriusu`는 현재 수동 업데이트 (hook 미설정 — `GH_PAT` secret 추가 시 연결 가능).
201
+
169
202
  ## 기술 스택
170
203
 
171
204
  | 구분 | 기술 |
@@ -22,9 +22,17 @@
22
22
  Code,
23
23
  } from "lucide-svelte";
24
24
  import { cn } from "../utils/cn";
25
- import type { ToolbarFeature } from "../types";
25
+ import type { ToolbarFeature, PromptHandler } from "../types";
26
26
 
27
- let { editor, features }: { editor: Editor; features: Set<ToolbarFeature> } = $props();
27
+ let {
28
+ editor,
29
+ features,
30
+ onPromptLink,
31
+ }: {
32
+ editor: Editor;
33
+ features: Set<ToolbarFeature>;
34
+ onPromptLink?: PromptHandler;
35
+ } = $props();
28
36
 
29
37
  const has = (f: ToolbarFeature) => features.has(f);
30
38
 
@@ -39,9 +47,11 @@
39
47
  return "본문";
40
48
  }
41
49
 
42
- function addLink() {
50
+ async function addLink() {
43
51
  const previousUrl = editor.getAttributes("link").href || "";
44
- const url = window.prompt("링크 URL을 입력하세요", previousUrl);
52
+ const url = onPromptLink
53
+ ? await onPromptLink(previousUrl)
54
+ : window.prompt("링크 URL을 입력하세요", previousUrl);
45
55
  if (url === null) return;
46
56
  if (url === "") {
47
57
  editor.chain().focus().extendMarkRange("link").unsetLink().run();
@@ -83,14 +93,14 @@
83
93
  </script>
84
94
 
85
95
  <div bind:this={menuEl} class="bubble-toolbar-container" style="visibility: hidden">
86
- <div class="flex items-center gap-0.5 px-1.5 py-1 bg-foreground rounded-lg shadow-xl">
96
+ <div class="flex items-center gap-0.5 px-1.5 py-1 bg-foreground rounded-full shadow-xl">
87
97
  {#if has('h1') || has('h2') || has('h3')}
88
98
  <!-- Block type selector -->
89
99
  <div class="relative">
90
100
  <button
91
101
  type="button"
92
102
  onclick={() => (showHeadings = !showHeadings)}
93
- class="flex items-center gap-1 px-2 py-1.5 rounded-md text-xs font-medium text-white/80 hover:text-white hover:bg-white/10 transition-colors"
103
+ class="flex items-center gap-1 px-2.5 py-1.5 rounded-full text-xs font-medium text-white/80 hover:text-white hover:bg-white/10 transition-colors"
94
104
  >
95
105
  <Type size={12} />
96
106
  {getCurrentBlockLabel()}
@@ -154,7 +164,7 @@
154
164
  title="굵게"
155
165
  aria-label="굵게"
156
166
  class={cn(
157
- "p-1.5 rounded-md transition-colors",
167
+ "p-1.5 rounded-full transition-colors",
158
168
  editor.isActive("bold")
159
169
  ? "bg-white/20 text-white"
160
170
  : "text-white/70 hover:text-white hover:bg-white/10",
@@ -170,7 +180,7 @@
170
180
  title="기울임"
171
181
  aria-label="기울임"
172
182
  class={cn(
173
- "p-1.5 rounded-md transition-colors",
183
+ "p-1.5 rounded-full transition-colors",
174
184
  editor.isActive("italic")
175
185
  ? "bg-white/20 text-white"
176
186
  : "text-white/70 hover:text-white hover:bg-white/10",
@@ -186,7 +196,7 @@
186
196
  title="밑줄"
187
197
  aria-label="밑줄"
188
198
  class={cn(
189
- "p-1.5 rounded-md transition-colors",
199
+ "p-1.5 rounded-full transition-colors",
190
200
  editor.isActive("underline")
191
201
  ? "bg-white/20 text-white"
192
202
  : "text-white/70 hover:text-white hover:bg-white/10",
@@ -202,7 +212,7 @@
202
212
  title="취소선"
203
213
  aria-label="취소선"
204
214
  class={cn(
205
- "p-1.5 rounded-md transition-colors",
215
+ "p-1.5 rounded-full transition-colors",
206
216
  editor.isActive("strike")
207
217
  ? "bg-white/20 text-white"
208
218
  : "text-white/70 hover:text-white hover:bg-white/10",
@@ -218,7 +228,7 @@
218
228
  title="하이라이트"
219
229
  aria-label="하이라이트"
220
230
  class={cn(
221
- "p-1.5 rounded-md transition-colors",
231
+ "p-1.5 rounded-full transition-colors",
222
232
  editor.isActive("highlight")
223
233
  ? "bg-white/20 text-white"
224
234
  : "text-white/70 hover:text-white hover:bg-white/10",
@@ -234,7 +244,7 @@
234
244
  title="코드"
235
245
  aria-label="코드"
236
246
  class={cn(
237
- "p-1.5 rounded-md transition-colors",
247
+ "p-1.5 rounded-full transition-colors",
238
248
  editor.isActive("code")
239
249
  ? "bg-white/20 text-white"
240
250
  : "text-white/70 hover:text-white hover:bg-white/10",
@@ -253,7 +263,7 @@
253
263
  title="링크"
254
264
  aria-label="링크"
255
265
  class={cn(
256
- "p-1.5 rounded-md transition-colors",
266
+ "p-1.5 rounded-full transition-colors",
257
267
  editor.isActive("link")
258
268
  ? "bg-white/20 text-white"
259
269
  : "text-white/70 hover:text-white hover:bg-white/10",
@@ -274,7 +284,7 @@
274
284
  title="왼쪽 정렬"
275
285
  aria-label="왼쪽 정렬"
276
286
  class={cn(
277
- "p-1.5 rounded-md transition-colors",
287
+ "p-1.5 rounded-full transition-colors",
278
288
  editor.isActive({ textAlign: "left" })
279
289
  ? "bg-white/20 text-white"
280
290
  : "text-white/70 hover:text-white hover:bg-white/10",
@@ -290,7 +300,7 @@
290
300
  title="가운데 정렬"
291
301
  aria-label="가운데 정렬"
292
302
  class={cn(
293
- "p-1.5 rounded-md transition-colors",
303
+ "p-1.5 rounded-full transition-colors",
294
304
  editor.isActive({ textAlign: "center" })
295
305
  ? "bg-white/20 text-white"
296
306
  : "text-white/70 hover:text-white hover:bg-white/10",
@@ -306,7 +316,7 @@
306
316
  title="오른쪽 정렬"
307
317
  aria-label="오른쪽 정렬"
308
318
  class={cn(
309
- "p-1.5 rounded-md transition-colors",
319
+ "p-1.5 rounded-full transition-colors",
310
320
  editor.isActive({ textAlign: "right" })
311
321
  ? "bg-white/20 text-white"
312
322
  : "text-white/70 hover:text-white hover:bg-white/10",
@@ -1,8 +1,9 @@
1
1
  import type { Editor } from "@tiptap/core";
2
- import type { ToolbarFeature } from "../types";
2
+ import type { ToolbarFeature, PromptHandler } from "../types";
3
3
  type $$ComponentProps = {
4
4
  editor: Editor;
5
5
  features: Set<ToolbarFeature>;
6
+ onPromptLink?: PromptHandler;
6
7
  };
7
8
  declare const BubbleToolbar: import("svelte").Component<$$ComponentProps, {}, "">;
8
9
  type BubbleToolbar = ReturnType<typeof BubbleToolbar>;
@@ -1 +1 @@
1
- {"version":3,"file":"BubbleToolbar.svelte.d.ts","sourceRoot":"","sources":["../../src/components/BubbleToolbar.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAqB3C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAE9C,KAAK,gBAAgB,GAAI;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,GAAG,CAAC,cAAc,CAAC,CAAA;CAAE,CAAC;AA0O5E,QAAA,MAAM,aAAa,sDAAwC,CAAC;AAC5D,KAAK,aAAa,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;AACtD,eAAe,aAAa,CAAC"}
1
+ {"version":3,"file":"BubbleToolbar.svelte.d.ts","sourceRoot":"","sources":["../../src/components/BubbleToolbar.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAqB3C,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAE7D,KAAK,gBAAgB,GAAI;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,GAAG,CAAC,cAAc,CAAC,CAAC;IAC9B,YAAY,CAAC,EAAE,aAAa,CAAC;CAC9B,CAAC;AAgPJ,QAAA,MAAM,aAAa,sDAAwC,CAAC;AAC5D,KAAK,aAAa,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;AACtD,eAAe,aAAa,CAAC"}
@@ -43,7 +43,7 @@
43
43
  } from "lucide-svelte";
44
44
  import { cn } from "../utils/cn";
45
45
  import InputModal from "./InputModal.svelte";
46
- import type { ToolbarFeature } from "../types";
46
+ import type { ToolbarFeature, PromptHandler } from "../types";
47
47
 
48
48
  let {
49
49
  editor,
@@ -51,12 +51,16 @@
51
51
  onPdfClick,
52
52
  onFileClick,
53
53
  onVideoClick,
54
+ onPromptLink,
55
+ onPromptImage,
54
56
  }: {
55
57
  editor: Editor;
56
58
  features: Set<ToolbarFeature>;
57
59
  onPdfClick: () => void;
58
60
  onFileClick?: () => void;
59
61
  onVideoClick?: () => void;
62
+ onPromptLink?: PromptHandler;
63
+ onPromptImage?: PromptHandler;
60
64
  } = $props();
61
65
 
62
66
  const has = (f: ToolbarFeature) => features.has(f);
@@ -91,11 +95,35 @@
91
95
  };
92
96
  });
93
97
 
94
- function addLink() {
98
+ async function addLink() {
99
+ if (onPromptLink) {
100
+ const previous = editor.isActive("link")
101
+ ? (editor.getAttributes("link").href as string) || ""
102
+ : "";
103
+ const url = await onPromptLink(previous);
104
+ if (url === null) return;
105
+ if (url === "") {
106
+ editor.chain().focus().extendMarkRange("link").unsetLink().run();
107
+ } else {
108
+ editor
109
+ .chain()
110
+ .focus()
111
+ .extendMarkRange("link")
112
+ .setLink({ href: url })
113
+ .run();
114
+ }
115
+ return;
116
+ }
95
117
  modalState = { type: "link" };
96
118
  }
97
119
 
98
- function addImage() {
120
+ async function addImage() {
121
+ if (onPromptImage) {
122
+ const url = await onPromptImage("");
123
+ if (!url) return;
124
+ editor.chain().focus().setImage({ src: url }).run();
125
+ return;
126
+ }
99
127
  modalState = { type: "image" };
100
128
  }
101
129
 
@@ -120,10 +148,11 @@
120
148
  </script>
121
149
 
122
150
  <div
123
- class="sticky z-30 flex flex-wrap items-center gap-0.5 px-3 py-2 border-b border-border bg-background rounded-t-xl"
151
+ class="hce-toolbar sticky z-30 flex flex-wrap items-center gap-1.5 px-3 py-2 border-b border-border bg-background rounded-t-xl"
124
152
  style="top: var(--header-height, 74px)"
125
153
  >
126
154
  <!-- Format -->
155
+ <div class="hce-toolbar-group">
127
156
  {#if has('bold')}
128
157
  <button
129
158
  type="button"
@@ -237,11 +266,11 @@
237
266
  </button>
238
267
  {/if}
239
268
 
240
- {#if has('align-left') || has('align-center') || has('align-right')}
241
- <div class="w-px h-6 bg-border mx-0.5"></div>
242
- {/if}
269
+ </div>
243
270
 
271
+ {#if has('align-left') || has('align-center') || has('align-right')}
244
272
  <!-- Alignment -->
273
+ <div class="hce-toolbar-group">
245
274
  {#if has('align-left')}
246
275
  <button
247
276
  type="button"
@@ -291,11 +320,12 @@
291
320
  </button>
292
321
  {/if}
293
322
 
294
- {#if has('h1') || has('h2') || has('h3')}
295
- <div class="w-px h-6 bg-border mx-0.5"></div>
323
+ </div>
296
324
  {/if}
297
325
 
326
+ {#if has('h1') || has('h2') || has('h3')}
298
327
  <!-- Headings -->
328
+ <div class="hce-toolbar-group">
299
329
  {#if has('h1')}
300
330
  <button
301
331
  type="button"
@@ -345,11 +375,12 @@
345
375
  </button>
346
376
  {/if}
347
377
 
348
- {#if has('bullet-list') || has('ordered-list') || has('checklist') || has('blockquote') || has('horizontal-rule') || has('toggle')}
349
- <div class="w-px h-6 bg-border mx-0.5"></div>
378
+ </div>
350
379
  {/if}
351
380
 
381
+ {#if has('bullet-list') || has('ordered-list') || has('checklist') || has('blockquote') || has('horizontal-rule') || has('toggle')}
352
382
  <!-- Lists & Blocks -->
383
+ <div class="hce-toolbar-group">
353
384
  {#if has('bullet-list')}
354
385
  <button
355
386
  type="button"
@@ -442,11 +473,12 @@
442
473
  </button>
443
474
  {/if}
444
475
 
445
- {#if has('link') || has('image') || has('pdf') || has('youtube') || has('video') || has('file')}
446
- <div class="w-px h-6 bg-border mx-0.5"></div>
476
+ </div>
447
477
  {/if}
448
478
 
449
- <!-- Media -->
479
+ {#if has('link') || has('image') || has('pdf') || has('youtube') || has('video') || has('file') || has('columns-2') || has('columns-3') || has('table') || has('code-block')}
480
+ <!-- Media & Layout -->
481
+ <div class="hce-toolbar-group">
450
482
  {#if has('link')}
451
483
  <button
452
484
  type="button"
@@ -607,7 +639,7 @@
607
639
  </button>
608
640
  {:else}
609
641
  <p
610
- class="px-3 py-1 text-[10px] font-medium uppercase tracking-wider text-muted-foreground"
642
+ class="px-3 py-1 text-xs font-semibold text-muted-foreground"
611
643
  >
612
644
  구조
613
645
  </p>
@@ -671,7 +703,7 @@
671
703
  </button>
672
704
  <div class="h-px bg-border my-1"></div>
673
705
  <p
674
- class="px-3 py-1 text-[10px] font-medium uppercase tracking-wider text-muted-foreground"
706
+ class="px-3 py-1 text-xs font-semibold text-muted-foreground"
675
707
  >
676
708
  헤더 / 셀
677
709
  </p>
@@ -745,7 +777,7 @@
745
777
  </button>
746
778
  <div class="h-px bg-border my-1"></div>
747
779
  <p
748
- class="px-3 py-1 text-[10px] font-medium uppercase tracking-wider text-muted-foreground"
780
+ class="px-3 py-1 text-xs font-semibold text-muted-foreground"
749
781
  >
750
782
  삭제
751
783
  </p>
@@ -797,70 +829,31 @@
797
829
  </div>
798
830
  {/if}
799
831
 
800
- <!-- Code block menu -->
832
+ <!-- Code block -->
801
833
  {#if has('code-block')}
802
- <div bind:this={codeMenuEl} class="relative">
803
- <button
804
- type="button"
805
- onclick={() => (codeMenuOpen = !codeMenuOpen)}
806
- data-tooltip="코드 블록"
807
- aria-label="코드 블록"
808
- class={cn(
809
- "flex items-center gap-0.5 p-1.5 rounded-md transition-colors",
810
- editor.isActive("codeBlock")
811
- ? "bg-primary/10 text-primary"
812
- : "text-muted-foreground hover:bg-muted hover:text-foreground",
813
- )}
814
- >
815
- <Code2 size={iconSize} />
816
- <ChevronDown size={12} />
817
- </button>
818
- {#if codeMenuOpen}
819
- <div
820
- class="absolute top-full left-0 mt-1 bg-popover border border-border rounded-lg shadow-lg z-50 py-1"
821
- style="min-width: 130px"
822
- >
823
- <button
824
- type="button"
825
- class="w-full text-left px-3 py-2 text-sm hover:bg-muted transition-colors"
826
- onclick={() => {
827
- editor.chain().focus().setCodeBlock({ language: "cpp" }).run();
828
- codeMenuOpen = false;
829
- }}
830
- >
831
- C++
832
- </button>
833
- <button
834
- type="button"
835
- class="w-full text-left px-3 py-2 text-sm hover:bg-muted transition-colors"
836
- onclick={() => {
837
- editor.chain().focus().setCodeBlock({ language: "python" }).run();
838
- codeMenuOpen = false;
839
- }}
840
- >
841
- Python
842
- </button>
843
- <div class="h-px bg-border my-1"></div>
844
- <button
845
- type="button"
846
- class="w-full text-left px-3 py-2 text-sm hover:bg-muted transition-colors"
847
- onclick={() => {
848
- editor.chain().focus().setCodeBlock({ language: "" }).run();
849
- codeMenuOpen = false;
850
- }}
851
- >
852
- 일반 코드
853
- </button>
854
- </div>
855
- {/if}
856
- </div>
834
+ <button
835
+ type="button"
836
+ onclick={() => editor.chain().focus().setCodeBlock({ language: 'cpp' }).run()}
837
+ data-tooltip="코드 블록"
838
+ aria-label="코드 블록"
839
+ class={cn(
840
+ "p-1.5 rounded-md transition-colors",
841
+ editor.isActive("codeBlock")
842
+ ? "bg-primary/10 text-primary"
843
+ : "text-muted-foreground hover:bg-muted hover:text-foreground",
844
+ )}
845
+ >
846
+ <Code2 size={iconSize} />
847
+ </button>
857
848
  {/if}
858
849
 
859
- {#if has('undo') || has('redo')}
860
- <div class="w-px h-6 bg-border mx-0.5"></div>
850
+ </div>
861
851
  {/if}
862
852
 
853
+ {#if has('undo') || has('redo')}
863
854
  <!-- Undo / Redo -->
855
+ <div class="hce-toolbar-group">
856
+
864
857
  {#if has('undo')}
865
858
  <button
866
859
  type="button"
@@ -892,10 +885,13 @@
892
885
  </button>
893
886
  {/if}
894
887
 
888
+ </div>
889
+ {/if}
890
+
895
891
  <!-- Modals -->
896
892
  {#if modalState?.type === "link"}
897
893
  <InputModal
898
- data-tooltip="링크 URL 입력"
894
+ title="링크 URL 입력"
899
895
  placeholder="https://example.com"
900
896
  defaultValue={editor.isActive("link") ? editor.getAttributes("link").href || "" : ""}
901
897
  onConfirm={(url) => {
@@ -912,7 +908,7 @@
912
908
  {/if}
913
909
  {#if modalState?.type === "image"}
914
910
  <InputModal
915
- data-tooltip="이미지 URL 입력"
911
+ title="이미지 URL 입력"
916
912
  placeholder="https://example.com/image.png"
917
913
  onConfirm={(url) => {
918
914
  editor.chain().focus().setImage({ src: url }).run();
@@ -963,3 +959,30 @@
963
959
  </div>
964
960
  {/if}
965
961
  </div>
962
+
963
+ <style>
964
+ /* 툴바 버튼 기본 리셋 (Tailwind preflight 비활성화 상태라 UA 기본값 제거) */
965
+ .hce-toolbar :global(button) {
966
+ background: transparent;
967
+ border: 0;
968
+ color: inherit;
969
+ font: inherit;
970
+ cursor: pointer;
971
+ }
972
+ .hce-toolbar :global(button:disabled) {
973
+ cursor: default;
974
+ }
975
+
976
+ .hce-toolbar-group {
977
+ display: flex;
978
+ align-items: center;
979
+ gap: 2px;
980
+ padding: 3px;
981
+ border-radius: var(--radius-xl, 8px);
982
+ background: color-mix(in srgb, var(--muted, #f6f7f9) 55%, transparent);
983
+ }
984
+
985
+ .hce-toolbar-group :global(button) {
986
+ flex-shrink: 0;
987
+ }
988
+ </style>
@@ -1,11 +1,13 @@
1
1
  import type { Editor } from "@tiptap/core";
2
- import type { ToolbarFeature } from "../types";
2
+ import type { ToolbarFeature, PromptHandler } from "../types";
3
3
  type $$ComponentProps = {
4
4
  editor: Editor;
5
5
  features: Set<ToolbarFeature>;
6
6
  onPdfClick: () => void;
7
7
  onFileClick?: () => void;
8
8
  onVideoClick?: () => void;
9
+ onPromptLink?: PromptHandler;
10
+ onPromptImage?: PromptHandler;
9
11
  };
10
12
  declare const FixedToolbar: import("svelte").Component<$$ComponentProps, {}, "">;
11
13
  type FixedToolbar = ReturnType<typeof FixedToolbar>;
@@ -1 +1 @@
1
- {"version":3,"file":"FixedToolbar.svelte.d.ts","sourceRoot":"","sources":["../../src/components/FixedToolbar.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AA4C3C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAE9C,KAAK,gBAAgB,GAAI;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,GAAG,CAAC,cAAc,CAAC,CAAC;IAC9B,UAAU,EAAE,MAAM,IAAI,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,IAAI,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;CAC3B,CAAC;AAymBJ,QAAA,MAAM,YAAY,sDAAwC,CAAC;AAC3D,KAAK,YAAY,GAAG,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC;AACpD,eAAe,YAAY,CAAC"}
1
+ {"version":3,"file":"FixedToolbar.svelte.d.ts","sourceRoot":"","sources":["../../src/components/FixedToolbar.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AA4C3C,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAE7D,KAAK,gBAAgB,GAAI;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,GAAG,CAAC,cAAc,CAAC,CAAC;IAC9B,UAAU,EAAE,MAAM,IAAI,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,IAAI,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IAC1B,YAAY,CAAC,EAAE,aAAa,CAAC;IAC7B,aAAa,CAAC,EAAE,aAAa,CAAC;CAC/B,CAAC;AAonBJ,QAAA,MAAM,YAAY,sDAAwC,CAAC;AAC3D,KAAK,YAAY,GAAG,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC;AACpD,eAAe,YAAY,CAAC"}
@@ -55,44 +55,47 @@
55
55
  <!-- svelte-ignore a11y_click_events_have_key_events -->
56
56
  <!-- svelte-ignore a11y_no_static_element_interactions -->
57
57
  <div
58
- class="fixed inset-0 z-50 flex items-center justify-center bg-black/30"
58
+ class="fixed inset-0 z-50 flex items-center justify-center bg-black/40"
59
59
  onclick={handleBackdropClick}
60
60
  >
61
61
  <!-- svelte-ignore a11y_click_events_have_key_events -->
62
62
  <!-- svelte-ignore a11y_no_static_element_interactions -->
63
63
  <div
64
- class="bg-background border border-border rounded-lg shadow-lg p-4 w-80"
64
+ class="bg-background border border-border rounded-xl shadow-xl p-5 w-[360px] max-w-[90vw]"
65
65
  onclick={(e) => e.stopPropagation()}
66
66
  >
67
- <div class="flex items-center justify-between mb-3">
68
- <span class="text-sm font-medium">{title}</span>
69
- <button
70
- type="button"
71
- class="p-0.5 rounded hover:bg-muted text-muted-foreground"
72
- onclick={onCancel}
73
- >
74
- <X size={14} />
75
- </button>
76
- </div>
67
+ {#if title}
68
+ <div class="flex items-center justify-between mb-3">
69
+ <span class="text-sm font-semibold">{title}</span>
70
+ <button
71
+ type="button"
72
+ class="p-1 rounded-md hover:bg-muted text-muted-foreground"
73
+ onclick={onCancel}
74
+ aria-label="닫기"
75
+ >
76
+ <X size={14} />
77
+ </button>
78
+ </div>
79
+ {/if}
77
80
  <input
78
81
  bind:this={inputEl}
79
82
  type="text"
80
- class="w-full border border-border rounded px-3 py-1.5 text-sm bg-background focus:outline-none focus:ring-1 focus:ring-primary"
83
+ class="w-full border border-border rounded-md px-3 py-2 text-sm bg-background focus:outline-none focus:ring-2 focus:ring-primary/40 focus:border-primary"
81
84
  {placeholder}
82
85
  bind:value
83
86
  onkeydown={handleKeydown}
84
87
  />
85
- <div class="flex justify-end gap-2 mt-3">
88
+ <div class="flex justify-end gap-2 mt-4">
86
89
  <button
87
90
  type="button"
88
- class="px-3 py-1 text-sm rounded border border-border hover:bg-muted"
91
+ class="px-3.5 py-1.5 text-sm rounded-md border border-border hover:bg-muted transition-colors"
89
92
  onclick={onCancel}
90
93
  >
91
94
  취소
92
95
  </button>
93
96
  <button
94
97
  type="button"
95
- class="px-3 py-1 text-sm rounded bg-primary text-primary-foreground hover:opacity-90"
98
+ class="px-3.5 py-1.5 text-sm rounded-md bg-primary text-primary-foreground hover:opacity-90 transition-opacity"
96
99
  onclick={handleSubmit}
97
100
  >
98
101
  확인
@@ -1 +1 @@
1
- {"version":3,"file":"InputModal.svelte.d.ts","sourceRoot":"","sources":["../../src/components/InputModal.svelte.ts"],"names":[],"mappings":"AAKC,KAAK,gBAAgB,GAAI;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC,QAAQ,EAAE,MAAM,IAAI,CAAC;CACtB,CAAC;AA2EJ,QAAA,MAAM,UAAU,sDAAwC,CAAC;AACzD,KAAK,UAAU,GAAG,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC;AAChD,eAAe,UAAU,CAAC"}
1
+ {"version":3,"file":"InputModal.svelte.d.ts","sourceRoot":"","sources":["../../src/components/InputModal.svelte.ts"],"names":[],"mappings":"AAKC,KAAK,gBAAgB,GAAI;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC,QAAQ,EAAE,MAAM,IAAI,CAAC;CACtB,CAAC;AA6EJ,QAAA,MAAM,UAAU,sDAAwC,CAAC;AACzD,KAAK,UAAU,GAAG,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC;AAChD,eAAe,UAAU,CAAC"}