@ubermensch1218/hwpxeditor 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.
package/dist/index.cjs ADDED
@@ -0,0 +1,2626 @@
1
+ "use client";
2
+ "use strict";
3
+ var __defProp = Object.defineProperty;
4
+ var __defProps = Object.defineProperties;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
7
+ var __getOwnPropNames = Object.getOwnPropertyNames;
8
+ var __getOwnPropSymbols = Object.getOwnPropertySymbols;
9
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
10
+ var __propIsEnum = Object.prototype.propertyIsEnumerable;
11
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
12
+ var __spreadValues = (a, b) => {
13
+ for (var prop in b || (b = {}))
14
+ if (__hasOwnProp.call(b, prop))
15
+ __defNormalProp(a, prop, b[prop]);
16
+ if (__getOwnPropSymbols)
17
+ for (var prop of __getOwnPropSymbols(b)) {
18
+ if (__propIsEnum.call(b, prop))
19
+ __defNormalProp(a, prop, b[prop]);
20
+ }
21
+ return a;
22
+ };
23
+ var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
24
+ var __export = (target, all) => {
25
+ for (var name in all)
26
+ __defProp(target, name, { get: all[name], enumerable: true });
27
+ };
28
+ var __copyProps = (to, from, except, desc) => {
29
+ if (from && typeof from === "object" || typeof from === "function") {
30
+ for (let key of __getOwnPropNames(from))
31
+ if (!__hasOwnProp.call(to, key) && key !== except)
32
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
33
+ }
34
+ return to;
35
+ };
36
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
37
+
38
+ // src/index.ts
39
+ var index_exports = {};
40
+ __export(index_exports, {
41
+ ALIGNMENT_OPTIONS: () => ALIGNMENT_OPTIONS,
42
+ AlignmentButtons: () => AlignmentButtons,
43
+ BackgroundSettings: () => BackgroundSettings,
44
+ BorderSettings: () => BorderSettings,
45
+ COLOR_PRESETS: () => COLOR_PRESETS,
46
+ CharFormatButtons: () => CharFormatButtons,
47
+ CharFormatPanel: () => CharFormatPanel,
48
+ ClipboardGroup: () => ClipboardGroup,
49
+ ColorPicker: () => ColorPicker,
50
+ Editor: () => Editor,
51
+ FONT_FAMILIES: () => FONT_FAMILIES,
52
+ FONT_SIZES: () => FONT_SIZES,
53
+ FileUpload: () => FileUpload,
54
+ FontSelector: () => FontSelector,
55
+ FontSizeInput: () => FontSizeInput,
56
+ FormatSidebar: () => FormatSidebar,
57
+ HIGHLIGHT_COLORS: () => HIGHLIGHT_COLORS,
58
+ HorizontalRuler: () => HorizontalRuler,
59
+ ImageBlock: () => ImageBlock,
60
+ InsertGroup: () => InsertGroup,
61
+ LINE_SPACING_OPTIONS: () => LINE_SPACING_OPTIONS,
62
+ LineSpacingControl: () => LineSpacingControl,
63
+ NewDocumentButton: () => NewDocumentButton,
64
+ Page: () => Page,
65
+ PageView: () => PageView,
66
+ ParaFormatPanel: () => ParaFormatPanel,
67
+ ParagraphBlock: () => ParagraphBlock,
68
+ RibbonGroup: () => RibbonGroup,
69
+ RibbonToolbar: () => RibbonToolbar,
70
+ RunSpan: () => RunSpan,
71
+ STYLE_PRESETS: () => STYLE_PRESETS,
72
+ SecondaryToolbar: () => SecondaryToolbar,
73
+ SidebarField: () => SidebarField,
74
+ SidebarSection: () => SidebarSection,
75
+ StyleSelector: () => StyleSelector,
76
+ TableBlock: () => TableBlock,
77
+ TableCell: () => TableCell,
78
+ ToolbarButton: () => ToolbarButton,
79
+ ToolbarDivider: () => ToolbarDivider,
80
+ ToolbarDropdown: () => ToolbarDropdown,
81
+ UNDERLINE_TYPES: () => UNDERLINE_TYPES,
82
+ buildViewModel: () => buildViewModel,
83
+ createNewDocument: () => createNewDocument,
84
+ ensureSkeletonLoaded: () => ensureSkeletonLoaded,
85
+ extractImages: () => extractImages,
86
+ getDocumentStyles: () => getDocumentStyles,
87
+ hwpToMm: () => hwpToMm,
88
+ hwpToPx: () => hwpToPx,
89
+ mmToHwp: () => mmToHwp,
90
+ pxToHwp: () => pxToHwp,
91
+ readCharFormat: () => readCharFormat,
92
+ readFormatFromSelection: () => readFormatFromSelection,
93
+ readParaFormat: () => readParaFormat,
94
+ readStyleInfo: () => readStyleInfo,
95
+ useEditorStore: () => useEditorStore
96
+ });
97
+ module.exports = __toCommonJS(index_exports);
98
+
99
+ // src/components/editor/Editor.tsx
100
+ var import_react10 = require("react");
101
+
102
+ // src/lib/store.ts
103
+ var import_zustand = require("zustand");
104
+
105
+ // src/lib/view-model.ts
106
+ var import_hwpxcore = require("@ubermensch1218/hwpxcore");
107
+
108
+ // src/lib/hwp-units.ts
109
+ var HWP_UNITS_PER_INCH = 7200;
110
+ var PX_PER_INCH = 96;
111
+ var MM_PER_INCH = 25.4;
112
+ function hwpToPx(hwpUnit) {
113
+ return hwpUnit * PX_PER_INCH / HWP_UNITS_PER_INCH;
114
+ }
115
+ function pxToHwp(px) {
116
+ return Math.round(px * HWP_UNITS_PER_INCH / PX_PER_INCH);
117
+ }
118
+ function hwpToMm(hwpUnit) {
119
+ return hwpUnit * MM_PER_INCH / HWP_UNITS_PER_INCH;
120
+ }
121
+ function mmToHwp(mm) {
122
+ return Math.round(mm * HWP_UNITS_PER_INCH / MM_PER_INCH);
123
+ }
124
+
125
+ // src/lib/image-extractor.ts
126
+ function extractImages(pkg) {
127
+ var _a, _b;
128
+ const images = /* @__PURE__ */ new Map();
129
+ const partNames = pkg.partNames();
130
+ for (const name of partNames) {
131
+ if (!name.startsWith("BinData/")) continue;
132
+ const ext = (_b = (_a = name.split(".").pop()) == null ? void 0 : _a.toLowerCase()) != null ? _b : "";
133
+ let mediaType = "application/octet-stream";
134
+ if (ext === "png") mediaType = "image/png";
135
+ else if (ext === "jpg" || ext === "jpeg") mediaType = "image/jpeg";
136
+ else if (ext === "gif") mediaType = "image/gif";
137
+ else if (ext === "bmp") mediaType = "image/bmp";
138
+ else if (ext === "svg") mediaType = "image/svg+xml";
139
+ else if (ext === "webp") mediaType = "image/webp";
140
+ else if (ext === "tif" || ext === "tiff") mediaType = "image/tiff";
141
+ else if (ext === "emf") mediaType = "image/x-emf";
142
+ else if (ext === "wmf") mediaType = "image/x-wmf";
143
+ try {
144
+ const data = pkg.getPart(name);
145
+ const base64 = uint8ToBase64(data);
146
+ const dataUrl = `data:${mediaType};base64,${base64}`;
147
+ const fileName = name.replace("BinData/", "");
148
+ images.set(fileName, dataUrl);
149
+ } catch (e) {
150
+ }
151
+ }
152
+ return images;
153
+ }
154
+ function uint8ToBase64(bytes) {
155
+ let binary = "";
156
+ for (let i = 0; i < bytes.length; i++) {
157
+ binary += String.fromCharCode(bytes[i]);
158
+ }
159
+ return btoa(binary);
160
+ }
161
+
162
+ // src/lib/view-model.ts
163
+ function extractRunStyle(style) {
164
+ var _a, _b, _c, _d;
165
+ if (!style) {
166
+ return {
167
+ bold: false,
168
+ italic: false,
169
+ underline: false,
170
+ strikethrough: false,
171
+ color: null,
172
+ fontFamily: null,
173
+ fontSize: null,
174
+ highlightColor: null,
175
+ letterSpacing: null
176
+ };
177
+ }
178
+ const bold = style.attributes["bold"] === "1";
179
+ const italic = style.attributes["italic"] === "1";
180
+ const underlineChild = style.childAttributes["underline"];
181
+ const underline = underlineChild != null && underlineChild["type"] != null && underlineChild["type"] !== "NONE";
182
+ const strikeChild = style.childAttributes["strikeout"];
183
+ const strikethrough = strikeChild != null && strikeChild["type"] != null && strikeChild["type"] !== "NONE";
184
+ const color = (_a = style.attributes["textColor"]) != null ? _a : null;
185
+ const highlightColor = (_b = style.attributes["shadeColor"]) != null ? _b : null;
186
+ let fontFamily = null;
187
+ const fontRef = style.childAttributes["fontRef"];
188
+ if (fontRef) {
189
+ fontFamily = (_d = (_c = fontRef["hangul"]) != null ? _c : fontRef["latin"]) != null ? _d : null;
190
+ }
191
+ let fontSize = null;
192
+ const sizeStr = style.attributes["height"];
193
+ if (sizeStr) {
194
+ const hwpVal = parseInt(sizeStr, 10);
195
+ if (!isNaN(hwpVal)) fontSize = hwpVal / 100;
196
+ }
197
+ let letterSpacing = null;
198
+ const spacingStr = style.attributes["spacing"];
199
+ if (spacingStr) {
200
+ const val = parseInt(spacingStr, 10);
201
+ if (!isNaN(val) && val !== 0) letterSpacing = val;
202
+ }
203
+ return { bold, italic, underline, strikethrough, color, fontFamily, fontSize, highlightColor, letterSpacing };
204
+ }
205
+ function findPictureRefs(runElement) {
206
+ var _a, _b;
207
+ const results = [];
208
+ const children = runElement.childNodes;
209
+ for (let i = 0; i < children.length; i++) {
210
+ const child = children.item(i);
211
+ if (!child || child.nodeType !== 1) continue;
212
+ const el = child;
213
+ const localName = el.localName || el.nodeName.split(":").pop() || "";
214
+ if (localName !== "pic") continue;
215
+ let width = 0;
216
+ let height = 0;
217
+ const picChildren = el.childNodes;
218
+ for (let j = 0; j < picChildren.length; j++) {
219
+ const pc = picChildren.item(j);
220
+ if (!pc || pc.nodeType !== 1) continue;
221
+ const pel = pc;
222
+ const pName = pel.localName || pel.nodeName.split(":").pop() || "";
223
+ if (pName === "curSz") {
224
+ width = parseInt((_a = pel.getAttribute("width")) != null ? _a : "0", 10);
225
+ height = parseInt((_b = pel.getAttribute("height")) != null ? _b : "0", 10);
226
+ }
227
+ if (pName === "img") {
228
+ const ref = pel.getAttribute("binaryItemIDRef");
229
+ if (ref) results.push({ binaryItemIdRef: ref, width, height });
230
+ }
231
+ }
232
+ const allDescendants = el.getElementsByTagName("*");
233
+ for (let k = 0; k < allDescendants.length; k++) {
234
+ const desc = allDescendants.item(k);
235
+ if (!desc) continue;
236
+ const dName = desc.localName || desc.nodeName.split(":").pop() || "";
237
+ if (dName === "img") {
238
+ const ref = desc.getAttribute("binaryItemIDRef");
239
+ if (ref && !results.some((r) => r.binaryItemIdRef === ref)) {
240
+ results.push({ binaryItemIdRef: ref, width, height });
241
+ }
242
+ }
243
+ }
244
+ }
245
+ return results;
246
+ }
247
+ function buildParaPrLookup(doc) {
248
+ var _a;
249
+ const lookup = /* @__PURE__ */ new Map();
250
+ try {
251
+ const headers = doc.headers;
252
+ if (headers.length === 0) return lookup;
253
+ const headerEl = headers[0].element;
254
+ const xml = (0, import_hwpxcore.serializeXml)(headerEl);
255
+ const parsed = (0, import_hwpxcore.parseHeaderXml)(xml);
256
+ if (!((_a = parsed.refList) == null ? void 0 : _a.paraProperties)) return lookup;
257
+ for (const prop of parsed.refList.paraProperties.properties) {
258
+ if (prop.rawId) lookup.set(prop.rawId, prop);
259
+ if (prop.id != null) lookup.set(String(prop.id), prop);
260
+ }
261
+ } catch (e) {
262
+ }
263
+ return lookup;
264
+ }
265
+ function buildViewModel(doc) {
266
+ var _a, _b, _c;
267
+ const imageMap = extractImages(doc.package);
268
+ const paraPrLookup = buildParaPrLookup(doc);
269
+ const sections = [];
270
+ let globalParaIndex = 0;
271
+ for (let sIdx = 0; sIdx < doc.sections.length; sIdx++) {
272
+ const section = doc.sections[sIdx];
273
+ const props = section.properties;
274
+ const pageSize = props.pageSize;
275
+ const pageMargins = props.pageMargins;
276
+ const sectionVM = {
277
+ pageWidthPx: hwpToPx(pageSize.width),
278
+ pageHeightPx: hwpToPx(pageSize.height),
279
+ marginTopPx: hwpToPx(pageMargins.top),
280
+ marginBottomPx: hwpToPx(pageMargins.bottom),
281
+ marginLeftPx: hwpToPx(pageMargins.left),
282
+ marginRightPx: hwpToPx(pageMargins.right),
283
+ paragraphs: [],
284
+ sectionIndex: sIdx
285
+ };
286
+ const paragraphs = section.paragraphs;
287
+ for (const para of paragraphs) {
288
+ const runs = [];
289
+ const images = [];
290
+ for (const run of para.runs) {
291
+ const style = run.style;
292
+ const {
293
+ bold,
294
+ italic,
295
+ underline,
296
+ strikethrough,
297
+ color,
298
+ fontFamily,
299
+ fontSize,
300
+ highlightColor,
301
+ letterSpacing
302
+ } = extractRunStyle(style);
303
+ const text = run.text;
304
+ if (text) {
305
+ runs.push({
306
+ text,
307
+ bold,
308
+ italic,
309
+ underline,
310
+ strikethrough,
311
+ color,
312
+ fontFamily,
313
+ fontSize,
314
+ highlightColor,
315
+ letterSpacing,
316
+ charPrIdRef: run.charPrIdRef
317
+ });
318
+ }
319
+ const picRefs = findPictureRefs(run.element);
320
+ for (const ref of picRefs) {
321
+ const dataUrl = imageMap.get(ref.binaryItemIdRef);
322
+ if (dataUrl) {
323
+ images.push({
324
+ dataUrl,
325
+ widthPx: hwpToPx(ref.width),
326
+ heightPx: hwpToPx(ref.height),
327
+ binaryItemIdRef: ref.binaryItemIdRef
328
+ });
329
+ }
330
+ }
331
+ }
332
+ const tables = [];
333
+ const paraTables = para.tables;
334
+ for (let tIdx = 0; tIdx < paraTables.length; tIdx++) {
335
+ const table = paraTables[tIdx];
336
+ const rowCount = table.rowCount;
337
+ const colCount = table.columnCount;
338
+ let cellGrid = [];
339
+ try {
340
+ cellGrid = table.getCellMap();
341
+ } catch (e) {
342
+ continue;
343
+ }
344
+ const cellsVM = [];
345
+ for (let r = 0; r < rowCount; r++) {
346
+ const row = [];
347
+ for (let c = 0; c < colCount; c++) {
348
+ const pos = (_a = cellGrid[r]) == null ? void 0 : _a[c];
349
+ if (!pos) {
350
+ row.push({
351
+ row: r,
352
+ col: c,
353
+ rowSpan: 1,
354
+ colSpan: 1,
355
+ widthPx: 0,
356
+ heightPx: 0,
357
+ text: "",
358
+ isAnchor: false
359
+ });
360
+ continue;
361
+ }
362
+ const isAnchor = pos.anchor[0] === r && pos.anchor[1] === c;
363
+ row.push({
364
+ row: r,
365
+ col: c,
366
+ rowSpan: pos.span[0],
367
+ colSpan: pos.span[1],
368
+ widthPx: hwpToPx(pos.cell.width),
369
+ heightPx: hwpToPx(pos.cell.height),
370
+ text: isAnchor ? pos.cell.text : "",
371
+ isAnchor
372
+ });
373
+ }
374
+ cellsVM.push(row);
375
+ }
376
+ tables.push({
377
+ rowCount,
378
+ colCount,
379
+ cells: cellsVM,
380
+ tableIndex: tIdx
381
+ });
382
+ }
383
+ let alignment = "LEFT";
384
+ let lineSpacing = 1.6;
385
+ let spacingBefore = 0;
386
+ let spacingAfter = 0;
387
+ let firstLineIndent = 0;
388
+ let marginLeftPx = 0;
389
+ let marginRightPx = 0;
390
+ const paraPrIdRef = para.paraPrIdRef;
391
+ if (paraPrIdRef) {
392
+ const paraPr = paraPrLookup.get(paraPrIdRef);
393
+ if (paraPr) {
394
+ if ((_b = paraPr.align) == null ? void 0 : _b.horizontal) {
395
+ alignment = paraPr.align.horizontal.toUpperCase();
396
+ }
397
+ if (((_c = paraPr.lineSpacing) == null ? void 0 : _c.value) != null) {
398
+ lineSpacing = paraPr.lineSpacing.value / 100;
399
+ }
400
+ if (paraPr.margin) {
401
+ if (paraPr.margin.left) {
402
+ marginLeftPx = hwpToPx(parseInt(paraPr.margin.left, 10) || 0);
403
+ }
404
+ if (paraPr.margin.right) {
405
+ marginRightPx = hwpToPx(parseInt(paraPr.margin.right, 10) || 0);
406
+ }
407
+ if (paraPr.margin.intent) {
408
+ firstLineIndent = hwpToPx(parseInt(paraPr.margin.intent, 10) || 0);
409
+ }
410
+ if (paraPr.margin.prev) {
411
+ spacingBefore = hwpToPx(parseInt(paraPr.margin.prev, 10) || 0);
412
+ }
413
+ if (paraPr.margin.next) {
414
+ spacingAfter = hwpToPx(parseInt(paraPr.margin.next, 10) || 0);
415
+ }
416
+ }
417
+ }
418
+ }
419
+ sectionVM.paragraphs.push({
420
+ runs,
421
+ tables,
422
+ images,
423
+ alignment,
424
+ lineSpacing,
425
+ spacingBefore,
426
+ spacingAfter,
427
+ firstLineIndent,
428
+ marginLeftPx,
429
+ marginRightPx,
430
+ paragraphIndex: globalParaIndex
431
+ });
432
+ globalParaIndex++;
433
+ }
434
+ sections.push(sectionVM);
435
+ }
436
+ return { sections };
437
+ }
438
+
439
+ // src/lib/format-bridge.ts
440
+ var import_hwpxcore2 = require("@ubermensch1218/hwpxcore");
441
+ var import_hwpxcore3 = require("@ubermensch1218/hwpxcore");
442
+ function getParsedHeader(doc) {
443
+ const headers = doc.headers;
444
+ if (headers.length === 0) return null;
445
+ const headerEl = headers[0].element;
446
+ try {
447
+ const xml = (0, import_hwpxcore3.serializeXml)(headerEl);
448
+ return (0, import_hwpxcore2.parseHeaderXml)(xml);
449
+ } catch (e) {
450
+ return null;
451
+ }
452
+ }
453
+ function readCharFormat(style) {
454
+ var _a, _b, _c, _d;
455
+ const result = {
456
+ bold: false,
457
+ italic: false,
458
+ underline: false,
459
+ strikethrough: false,
460
+ fontFamily: null,
461
+ fontSize: null,
462
+ textColor: null,
463
+ highlightColor: null,
464
+ letterSpacing: null
465
+ };
466
+ if (!style) return result;
467
+ result.bold = style.attributes["bold"] === "1";
468
+ result.italic = style.attributes["italic"] === "1";
469
+ const underlineChild = style.childAttributes["underline"];
470
+ result.underline = underlineChild != null && underlineChild["type"] != null && underlineChild["type"] !== "NONE";
471
+ const strikeChild = style.childAttributes["strikeout"];
472
+ result.strikethrough = strikeChild != null && strikeChild["type"] != null && strikeChild["type"] !== "NONE";
473
+ result.textColor = (_a = style.attributes["textColor"]) != null ? _a : null;
474
+ result.highlightColor = (_b = style.attributes["shadeColor"]) != null ? _b : null;
475
+ const spacing = style.attributes["spacing"];
476
+ if (spacing) {
477
+ const val = parseInt(spacing, 10);
478
+ if (!isNaN(val)) result.letterSpacing = val;
479
+ }
480
+ const fontRef = style.childAttributes["fontRef"];
481
+ if (fontRef) {
482
+ result.fontFamily = (_d = (_c = fontRef["hangul"]) != null ? _c : fontRef["latin"]) != null ? _d : null;
483
+ }
484
+ const sizeStr = style.attributes["height"];
485
+ if (sizeStr) {
486
+ const hwpVal = parseInt(sizeStr, 10);
487
+ if (!isNaN(hwpVal)) result.fontSize = hwpVal / 100;
488
+ }
489
+ return result;
490
+ }
491
+ function readParaFormat(doc, paragraph) {
492
+ var _a, _b, _c;
493
+ const result = {
494
+ alignment: "LEFT",
495
+ lineSpacing: 1.6,
496
+ spacingBefore: 0,
497
+ spacingAfter: 0,
498
+ indentLeft: 0,
499
+ indentRight: 0,
500
+ firstLineIndent: 0
501
+ };
502
+ const paraPrIdRef = paragraph.paraPrIdRef;
503
+ if (!paraPrIdRef) return result;
504
+ const header = getParsedHeader(doc);
505
+ if (!header) return result;
506
+ const paraPr = findParaPr(header, paraPrIdRef);
507
+ if (!paraPr) return result;
508
+ if ((_a = paraPr.align) == null ? void 0 : _a.horizontal) {
509
+ const h = paraPr.align.horizontal.toUpperCase();
510
+ if (h === "LEFT" || h === "CENTER" || h === "RIGHT" || h === "JUSTIFY" || h === "DISTRIBUTE") {
511
+ result.alignment = h;
512
+ }
513
+ }
514
+ if (((_b = paraPr.lineSpacing) == null ? void 0 : _b.value) != null) {
515
+ const spacingType = (_c = paraPr.lineSpacing.spacingType) == null ? void 0 : _c.toUpperCase();
516
+ if (spacingType === "PERCENT" || spacingType === "PROPORTIONAL") {
517
+ result.lineSpacing = paraPr.lineSpacing.value / 100;
518
+ } else {
519
+ result.lineSpacing = paraPr.lineSpacing.value / 100;
520
+ }
521
+ }
522
+ if (paraPr.margin) {
523
+ if (paraPr.margin.left) {
524
+ result.indentLeft = parseInt(paraPr.margin.left, 10) || 0;
525
+ }
526
+ if (paraPr.margin.right) {
527
+ result.indentRight = parseInt(paraPr.margin.right, 10) || 0;
528
+ }
529
+ if (paraPr.margin.intent) {
530
+ result.firstLineIndent = parseInt(paraPr.margin.intent, 10) || 0;
531
+ }
532
+ if (paraPr.margin.prev) {
533
+ result.spacingBefore = parseInt(paraPr.margin.prev, 10) || 0;
534
+ }
535
+ if (paraPr.margin.next) {
536
+ result.spacingAfter = parseInt(paraPr.margin.next, 10) || 0;
537
+ }
538
+ }
539
+ return result;
540
+ }
541
+ function findParaPr(header, paraPrIdRef) {
542
+ var _a;
543
+ if (!((_a = header.refList) == null ? void 0 : _a.paraProperties)) return null;
544
+ for (const prop of header.refList.paraProperties.properties) {
545
+ if (prop.rawId === paraPrIdRef) return prop;
546
+ if (prop.id != null && String(prop.id) === paraPrIdRef) return prop;
547
+ }
548
+ return null;
549
+ }
550
+ function readStyleInfo(doc, paragraph) {
551
+ var _a;
552
+ const styleIdRef = paragraph.styleIdRef;
553
+ if (!styleIdRef) return { styleName: null, styleId: null };
554
+ const header = getParsedHeader(doc);
555
+ if (!((_a = header == null ? void 0 : header.refList) == null ? void 0 : _a.styles)) return { styleName: null, styleId: null };
556
+ for (const style of header.refList.styles.styles) {
557
+ if (style.rawId === styleIdRef || style.id != null && String(style.id) === styleIdRef) {
558
+ return { styleName: style.name, styleId: styleIdRef };
559
+ }
560
+ }
561
+ return { styleName: null, styleId: styleIdRef };
562
+ }
563
+ function getDocumentStyles(doc) {
564
+ var _a;
565
+ const header = getParsedHeader(doc);
566
+ if (!((_a = header == null ? void 0 : header.refList) == null ? void 0 : _a.styles)) return [];
567
+ return header.refList.styles.styles;
568
+ }
569
+ function readFormatFromSelection(doc, sectionIndex, paragraphIndex) {
570
+ const section = doc.sections[sectionIndex];
571
+ if (!section) {
572
+ return {
573
+ char: readCharFormat(null),
574
+ para: {
575
+ alignment: "LEFT",
576
+ lineSpacing: 1.6,
577
+ spacingBefore: 0,
578
+ spacingAfter: 0,
579
+ indentLeft: 0,
580
+ indentRight: 0,
581
+ firstLineIndent: 0
582
+ }
583
+ };
584
+ }
585
+ const paras = section.paragraphs;
586
+ const para = paras[paragraphIndex];
587
+ if (!para) {
588
+ return {
589
+ char: readCharFormat(null),
590
+ para: {
591
+ alignment: "LEFT",
592
+ lineSpacing: 1.6,
593
+ spacingBefore: 0,
594
+ spacingAfter: 0,
595
+ indentLeft: 0,
596
+ indentRight: 0,
597
+ firstLineIndent: 0
598
+ }
599
+ };
600
+ }
601
+ const runs = para.runs;
602
+ let charStyle = null;
603
+ if (runs.length > 0) {
604
+ charStyle = runs[0].style;
605
+ } else {
606
+ const charPrIdRef = para.charPrIdRef;
607
+ if (charPrIdRef) {
608
+ charStyle = doc.charProperty(charPrIdRef);
609
+ }
610
+ }
611
+ return {
612
+ char: readCharFormat(charStyle),
613
+ para: readParaFormat(doc, para)
614
+ };
615
+ }
616
+
617
+ // src/lib/store.ts
618
+ var defaultCharFormat = {
619
+ bold: false,
620
+ italic: false,
621
+ underline: false,
622
+ strikethrough: false,
623
+ fontFamily: null,
624
+ fontSize: null,
625
+ textColor: null,
626
+ highlightColor: null,
627
+ letterSpacing: null
628
+ };
629
+ var defaultParaFormat = {
630
+ alignment: "LEFT",
631
+ lineSpacing: 1.6,
632
+ spacingBefore: 0,
633
+ spacingAfter: 0,
634
+ indentLeft: 0,
635
+ indentRight: 0,
636
+ firstLineIndent: 0
637
+ };
638
+ var useEditorStore = (0, import_zustand.create)((set, get) => ({
639
+ doc: null,
640
+ viewModel: null,
641
+ revision: 0,
642
+ selection: null,
643
+ activeFormat: { bold: false, italic: false, underline: false },
644
+ extendedFormat: { char: defaultCharFormat, para: defaultParaFormat },
645
+ uiState: { sidebarOpen: true, sidebarTab: "char" },
646
+ loading: false,
647
+ error: null,
648
+ setDocument: (doc) => {
649
+ const viewModel = buildViewModel(doc);
650
+ set({ doc, viewModel, revision: get().revision + 1, error: null });
651
+ },
652
+ rebuild: () => {
653
+ const { doc } = get();
654
+ if (!doc) return;
655
+ const viewModel = buildViewModel(doc);
656
+ set({ viewModel, revision: get().revision + 1 });
657
+ },
658
+ setSelection: (selection) => {
659
+ set({ selection });
660
+ if (selection) {
661
+ setTimeout(() => get().refreshExtendedFormat(), 0);
662
+ }
663
+ },
664
+ setActiveFormat: (fmt) => set((s) => ({ activeFormat: __spreadValues(__spreadValues({}, s.activeFormat), fmt) })),
665
+ refreshExtendedFormat: () => {
666
+ const { doc, selection } = get();
667
+ if (!doc || !selection) return;
668
+ try {
669
+ const fmt = readFormatFromSelection(
670
+ doc,
671
+ selection.sectionIndex,
672
+ selection.paragraphIndex
673
+ );
674
+ set({
675
+ extendedFormat: fmt,
676
+ activeFormat: {
677
+ bold: fmt.char.bold,
678
+ italic: fmt.char.italic,
679
+ underline: fmt.char.underline
680
+ }
681
+ });
682
+ } catch (e) {
683
+ console.error("refreshExtendedFormat failed:", e);
684
+ }
685
+ },
686
+ // UI actions
687
+ toggleSidebar: () => set((s) => ({
688
+ uiState: __spreadProps(__spreadValues({}, s.uiState), { sidebarOpen: !s.uiState.sidebarOpen })
689
+ })),
690
+ setSidebarTab: (tab) => set((s) => ({
691
+ uiState: __spreadProps(__spreadValues({}, s.uiState), { sidebarTab: tab })
692
+ })),
693
+ setLoading: (loading) => set({ loading }),
694
+ setError: (error) => set({ error }),
695
+ updateParagraphText: (sectionIndex, paragraphIndex, text) => {
696
+ const { doc } = get();
697
+ if (!doc) return;
698
+ try {
699
+ const section = doc.sections[sectionIndex];
700
+ if (!section) return;
701
+ const paras = section.paragraphs;
702
+ const para = paras[paragraphIndex];
703
+ if (!para) return;
704
+ para.text = text;
705
+ get().rebuild();
706
+ } catch (e) {
707
+ console.error("updateParagraphText failed:", e);
708
+ }
709
+ },
710
+ updateCellText: (sectionIndex, paragraphIndex, tableIndex, row, col, text) => {
711
+ const { doc } = get();
712
+ if (!doc) return;
713
+ try {
714
+ const section = doc.sections[sectionIndex];
715
+ if (!section) return;
716
+ const paras = section.paragraphs;
717
+ const para = paras[paragraphIndex];
718
+ if (!para) return;
719
+ const table = para.tables[tableIndex];
720
+ if (!table) return;
721
+ table.setCellText(row, col, text);
722
+ get().rebuild();
723
+ } catch (e) {
724
+ console.error("updateCellText failed:", e);
725
+ }
726
+ },
727
+ toggleBold: () => {
728
+ const { doc, activeFormat, selection } = get();
729
+ if (!doc || !selection) return;
730
+ const newBold = !activeFormat.bold;
731
+ try {
732
+ const charPrIdRef = doc.ensureRunStyle({
733
+ bold: newBold,
734
+ italic: activeFormat.italic,
735
+ underline: activeFormat.underline
736
+ });
737
+ const section = doc.sections[selection.sectionIndex];
738
+ if (!section) return;
739
+ const para = section.paragraphs[selection.paragraphIndex];
740
+ if (!para) return;
741
+ para.charPrIdRef = charPrIdRef;
742
+ set({ activeFormat: __spreadProps(__spreadValues({}, activeFormat), { bold: newBold }) });
743
+ get().rebuild();
744
+ } catch (e) {
745
+ console.error("toggleBold failed:", e);
746
+ }
747
+ },
748
+ toggleItalic: () => {
749
+ const { doc, activeFormat, selection } = get();
750
+ if (!doc || !selection) return;
751
+ const newItalic = !activeFormat.italic;
752
+ try {
753
+ const charPrIdRef = doc.ensureRunStyle({
754
+ bold: activeFormat.bold,
755
+ italic: newItalic,
756
+ underline: activeFormat.underline
757
+ });
758
+ const section = doc.sections[selection.sectionIndex];
759
+ if (!section) return;
760
+ const para = section.paragraphs[selection.paragraphIndex];
761
+ if (!para) return;
762
+ para.charPrIdRef = charPrIdRef;
763
+ set({ activeFormat: __spreadProps(__spreadValues({}, activeFormat), { italic: newItalic }) });
764
+ get().rebuild();
765
+ } catch (e) {
766
+ console.error("toggleItalic failed:", e);
767
+ }
768
+ },
769
+ toggleUnderline: () => {
770
+ const { doc, activeFormat, selection } = get();
771
+ if (!doc || !selection) return;
772
+ const newUnderline = !activeFormat.underline;
773
+ try {
774
+ const charPrIdRef = doc.ensureRunStyle({
775
+ bold: activeFormat.bold,
776
+ italic: activeFormat.italic,
777
+ underline: newUnderline
778
+ });
779
+ const section = doc.sections[selection.sectionIndex];
780
+ if (!section) return;
781
+ const para = section.paragraphs[selection.paragraphIndex];
782
+ if (!para) return;
783
+ para.charPrIdRef = charPrIdRef;
784
+ set({ activeFormat: __spreadProps(__spreadValues({}, activeFormat), { underline: newUnderline }) });
785
+ get().rebuild();
786
+ } catch (e) {
787
+ console.error("toggleUnderline failed:", e);
788
+ }
789
+ },
790
+ addParagraph: (text = "") => {
791
+ const { doc } = get();
792
+ if (!doc) return;
793
+ try {
794
+ doc.addParagraph(text);
795
+ get().rebuild();
796
+ } catch (e) {
797
+ console.error("addParagraph failed:", e);
798
+ }
799
+ },
800
+ addTable: (sectionIndex, paragraphIndex, rows, cols) => {
801
+ const { doc } = get();
802
+ if (!doc) return;
803
+ try {
804
+ const section = doc.sections[sectionIndex];
805
+ if (!section) return;
806
+ const para = section.paragraphs[paragraphIndex];
807
+ if (!para) return;
808
+ para.addTable(rows, cols);
809
+ get().rebuild();
810
+ } catch (e) {
811
+ console.error("addTable failed:", e);
812
+ }
813
+ },
814
+ insertImage: (data, mediaType, widthMm, heightMm) => {
815
+ const { doc } = get();
816
+ if (!doc) return;
817
+ try {
818
+ doc.addImage(data, { mediaType, widthMm, heightMm });
819
+ get().rebuild();
820
+ } catch (e) {
821
+ console.error("insertImage failed:", e);
822
+ }
823
+ },
824
+ saveDocument: async () => {
825
+ const { doc } = get();
826
+ if (!doc) return;
827
+ try {
828
+ set({ loading: true });
829
+ const bytes = await doc.save();
830
+ const blob = new Blob([bytes], {
831
+ type: "application/vnd.hancom.hwpx"
832
+ });
833
+ const url = URL.createObjectURL(blob);
834
+ const a = document.createElement("a");
835
+ a.href = url;
836
+ a.download = "document.hwpx";
837
+ a.click();
838
+ URL.revokeObjectURL(url);
839
+ } catch (e) {
840
+ console.error("save failed:", e);
841
+ set({ error: "\uBB38\uC11C \uC800\uC7A5\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4." });
842
+ } finally {
843
+ set({ loading: false });
844
+ }
845
+ }
846
+ }));
847
+
848
+ // src/lib/skeleton-loader.ts
849
+ var import_hwpxcore4 = require("@ubermensch1218/hwpxcore");
850
+ var loaded = false;
851
+ async function ensureSkeletonLoaded() {
852
+ if (loaded) return;
853
+ const res = await fetch("/Skeleton.hwpx");
854
+ if (!res.ok) throw new Error(`Failed to fetch Skeleton.hwpx: ${res.status}`);
855
+ const buf = await res.arrayBuffer();
856
+ (0, import_hwpxcore4.setSkeletonHwpx)(new Uint8Array(buf));
857
+ loaded = true;
858
+ }
859
+ async function createNewDocument() {
860
+ await ensureSkeletonLoaded();
861
+ const res = await fetch("/Skeleton.hwpx");
862
+ const buf = await res.arrayBuffer();
863
+ return import_hwpxcore4.HwpxDocument.open(buf);
864
+ }
865
+
866
+ // src/components/toolbar/ClipboardGroup.tsx
867
+ var import_lucide_react = require("lucide-react");
868
+
869
+ // src/components/toolbar/ToolbarButton.tsx
870
+ var import_jsx_runtime = require("react/jsx-runtime");
871
+ function ToolbarButton({
872
+ icon,
873
+ label,
874
+ active,
875
+ disabled,
876
+ onClick,
877
+ title,
878
+ size = "sm",
879
+ className = ""
880
+ }) {
881
+ const sizeClass = size === "md" ? "p-2" : "p-1.5";
882
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
883
+ "button",
884
+ {
885
+ disabled,
886
+ onClick,
887
+ title,
888
+ className: `${sizeClass} rounded transition-colors flex items-center gap-1 ${active ? "bg-blue-100 text-blue-700" : "text-gray-600 hover:bg-gray-100"} disabled:opacity-40 disabled:cursor-not-allowed ${className}`,
889
+ children: [
890
+ icon,
891
+ label && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "text-xs", children: label })
892
+ ]
893
+ }
894
+ );
895
+ }
896
+
897
+ // src/components/toolbar/RibbonGroup.tsx
898
+ var import_jsx_runtime2 = require("react/jsx-runtime");
899
+ function RibbonGroup({ label, children }) {
900
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-col items-center border-r border-gray-200 px-2 last:border-r-0", children: [
901
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "flex items-center gap-0.5 py-1", children }),
902
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "text-[10px] text-gray-400 pb-0.5", children: label })
903
+ ] });
904
+ }
905
+
906
+ // src/components/toolbar/ClipboardGroup.tsx
907
+ var import_jsx_runtime3 = require("react/jsx-runtime");
908
+ function ClipboardGroup() {
909
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(RibbonGroup, { label: "\uD074\uB9BD\uBCF4\uB4DC", children: [
910
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
911
+ ToolbarButton,
912
+ {
913
+ icon: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_lucide_react.Clipboard, { className: "w-4 h-4" }),
914
+ title: "\uC624\uB824\uB450\uAE30 (Ctrl+X)",
915
+ onClick: () => document.execCommand("cut")
916
+ }
917
+ ),
918
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
919
+ ToolbarButton,
920
+ {
921
+ icon: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_lucide_react.Copy, { className: "w-4 h-4" }),
922
+ title: "\uBCF5\uC0AC (Ctrl+C)",
923
+ onClick: () => document.execCommand("copy")
924
+ }
925
+ ),
926
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
927
+ ToolbarButton,
928
+ {
929
+ icon: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_lucide_react.ClipboardPaste, { className: "w-4 h-4" }),
930
+ title: "\uBD99\uC774\uAE30 (Ctrl+V)",
931
+ onClick: () => document.execCommand("paste")
932
+ }
933
+ ),
934
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
935
+ ToolbarButton,
936
+ {
937
+ icon: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_lucide_react.Paintbrush, { className: "w-4 h-4" }),
938
+ title: "\uBAA8\uC591 \uBCF5\uC0AC",
939
+ disabled: true
940
+ }
941
+ )
942
+ ] });
943
+ }
944
+
945
+ // src/components/toolbar/InsertGroup.tsx
946
+ var import_react = require("react");
947
+ var import_lucide_react2 = require("lucide-react");
948
+ var import_jsx_runtime4 = require("react/jsx-runtime");
949
+ function InsertGroup() {
950
+ const doc = useEditorStore((s) => s.doc);
951
+ const selection = useEditorStore((s) => s.selection);
952
+ const addTable = useEditorStore((s) => s.addTable);
953
+ const insertImage = useEditorStore((s) => s.insertImage);
954
+ const saveDocument = useEditorStore((s) => s.saveDocument);
955
+ const loading = useEditorStore((s) => s.loading);
956
+ const [showTableDialog, setShowTableDialog] = (0, import_react.useState)(false);
957
+ const [tableRows, setTableRows] = (0, import_react.useState)(3);
958
+ const [tableCols, setTableCols] = (0, import_react.useState)(3);
959
+ const imageInputRef = (0, import_react.useRef)(null);
960
+ const disabled = !doc;
961
+ const handleAddTable = (0, import_react.useCallback)(() => {
962
+ if (!selection) return;
963
+ addTable(selection.sectionIndex, selection.paragraphIndex, tableRows, tableCols);
964
+ setShowTableDialog(false);
965
+ }, [selection, tableRows, tableCols, addTable]);
966
+ const handleImageSelect = (0, import_react.useCallback)(
967
+ async (e) => {
968
+ var _a;
969
+ const file = (_a = e.target.files) == null ? void 0 : _a[0];
970
+ if (!file) return;
971
+ const buffer = await file.arrayBuffer();
972
+ const data = new Uint8Array(buffer);
973
+ const img = new window.Image();
974
+ const url = URL.createObjectURL(file);
975
+ img.onload = () => {
976
+ const widthMm = img.naturalWidth / 96 * 25.4;
977
+ const heightMm = img.naturalHeight / 96 * 25.4;
978
+ insertImage(data, file.type, widthMm, heightMm);
979
+ URL.revokeObjectURL(url);
980
+ };
981
+ img.src = url;
982
+ e.target.value = "";
983
+ },
984
+ [insertImage]
985
+ );
986
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
987
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(RibbonGroup, { label: "\uC0BD\uC785", children: [
988
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
989
+ ToolbarButton,
990
+ {
991
+ icon: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_lucide_react2.Table, { className: "w-4 h-4" }),
992
+ title: "\uD45C \uC0BD\uC785",
993
+ disabled: disabled || !selection,
994
+ onClick: () => setShowTableDialog(true)
995
+ }
996
+ ),
997
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
998
+ ToolbarButton,
999
+ {
1000
+ icon: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_lucide_react2.BarChart3, { className: "w-4 h-4" }),
1001
+ title: "\uCC28\uD2B8 \uC0BD\uC785",
1002
+ disabled: true
1003
+ }
1004
+ ),
1005
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1006
+ ToolbarButton,
1007
+ {
1008
+ icon: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_lucide_react2.Shapes, { className: "w-4 h-4" }),
1009
+ title: "\uB3C4\uD615 \uC0BD\uC785",
1010
+ disabled: true
1011
+ }
1012
+ ),
1013
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1014
+ ToolbarButton,
1015
+ {
1016
+ icon: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_lucide_react2.ImageIcon, { className: "w-4 h-4" }),
1017
+ title: "\uADF8\uB9BC \uC0BD\uC785",
1018
+ disabled,
1019
+ onClick: () => {
1020
+ var _a;
1021
+ return (_a = imageInputRef.current) == null ? void 0 : _a.click();
1022
+ }
1023
+ }
1024
+ ),
1025
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1026
+ "input",
1027
+ {
1028
+ ref: imageInputRef,
1029
+ type: "file",
1030
+ accept: "image/*",
1031
+ className: "hidden",
1032
+ onChange: handleImageSelect
1033
+ }
1034
+ )
1035
+ ] }),
1036
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(RibbonGroup, { label: "\uD30C\uC77C", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1037
+ ToolbarButton,
1038
+ {
1039
+ icon: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_lucide_react2.Save, { className: "w-4 h-4" }),
1040
+ title: "\uC800\uC7A5 (Ctrl+S)",
1041
+ disabled: disabled || loading,
1042
+ onClick: () => saveDocument()
1043
+ }
1044
+ ) }),
1045
+ showTableDialog && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "fixed inset-0 z-50 flex items-center justify-center bg-black/30", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "bg-white rounded-lg shadow-xl p-6 min-w-[280px]", children: [
1046
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("h3", { className: "font-semibold mb-4", children: "\uD45C \uC0BD\uC785" }),
1047
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "flex gap-4 mb-4", children: [
1048
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { className: "flex flex-col gap-1", children: [
1049
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "text-sm text-gray-600", children: "\uD589" }),
1050
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1051
+ "input",
1052
+ {
1053
+ type: "number",
1054
+ min: 1,
1055
+ max: 50,
1056
+ value: tableRows,
1057
+ onChange: (e) => setTableRows(Number(e.target.value)),
1058
+ className: "border rounded px-2 py-1 w-20"
1059
+ }
1060
+ )
1061
+ ] }),
1062
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { className: "flex flex-col gap-1", children: [
1063
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "text-sm text-gray-600", children: "\uC5F4" }),
1064
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1065
+ "input",
1066
+ {
1067
+ type: "number",
1068
+ min: 1,
1069
+ max: 20,
1070
+ value: tableCols,
1071
+ onChange: (e) => setTableCols(Number(e.target.value)),
1072
+ className: "border rounded px-2 py-1 w-20"
1073
+ }
1074
+ )
1075
+ ] })
1076
+ ] }),
1077
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "flex justify-end gap-2", children: [
1078
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1079
+ "button",
1080
+ {
1081
+ onClick: () => setShowTableDialog(false),
1082
+ className: "px-4 py-2 text-sm text-gray-600 hover:bg-gray-100 rounded",
1083
+ children: "\uCDE8\uC18C"
1084
+ }
1085
+ ),
1086
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1087
+ "button",
1088
+ {
1089
+ onClick: handleAddTable,
1090
+ className: "px-4 py-2 text-sm bg-blue-500 text-white rounded hover:bg-blue-600",
1091
+ children: "\uC0BD\uC785"
1092
+ }
1093
+ )
1094
+ ] })
1095
+ ] }) })
1096
+ ] });
1097
+ }
1098
+
1099
+ // src/components/toolbar/RibbonToolbar.tsx
1100
+ var import_jsx_runtime5 = require("react/jsx-runtime");
1101
+ function RibbonToolbar() {
1102
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex items-stretch bg-white border-b border-gray-200 px-1 min-h-[52px]", children: [
1103
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ClipboardGroup, {}),
1104
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(InsertGroup, {})
1105
+ ] });
1106
+ }
1107
+
1108
+ // src/lib/constants.ts
1109
+ var FONT_FAMILIES = [
1110
+ "\uB9D1\uC740 \uACE0\uB515",
1111
+ "\uD568\uCD08\uB86C\uB3CB\uC6C0",
1112
+ "\uD568\uCD08\uB86C\uBC14\uD0D5",
1113
+ "\uB098\uB214\uACE0\uB515",
1114
+ "\uB098\uB214\uBA85\uC870",
1115
+ "\uB098\uB214\uBC14\uB978\uACE0\uB515",
1116
+ "\uBC14\uD0D5",
1117
+ "\uB3CB\uC6C0",
1118
+ "\uAD74\uB9BC",
1119
+ "\uAD81\uC11C",
1120
+ "Arial",
1121
+ "Times New Roman",
1122
+ "Courier New",
1123
+ "Verdana",
1124
+ "Georgia"
1125
+ ];
1126
+ var FONT_SIZES = [
1127
+ 8,
1128
+ 9,
1129
+ 10,
1130
+ 10.5,
1131
+ 11,
1132
+ 12,
1133
+ 14,
1134
+ 16,
1135
+ 18,
1136
+ 20,
1137
+ 22,
1138
+ 24,
1139
+ 28,
1140
+ 32,
1141
+ 36,
1142
+ 48,
1143
+ 72
1144
+ ];
1145
+ var STYLE_PRESETS = [
1146
+ { id: "0", name: "\uBC14\uD0D5\uAE00", engName: "Normal" },
1147
+ { id: "1", name: "\uBCF8\uBB38", engName: "Body" },
1148
+ { id: "2", name: "\uAC1C\uC694 1", engName: "Outline 1" },
1149
+ { id: "3", name: "\uAC1C\uC694 2", engName: "Outline 2" },
1150
+ { id: "4", name: "\uAC1C\uC694 3", engName: "Outline 3" },
1151
+ { id: "5", name: "\uCABD \uBC88\uD638", engName: "Page Number" },
1152
+ { id: "6", name: "\uBA38\uB9AC\uB9D0", engName: "Header" },
1153
+ { id: "7", name: "\uAF2C\uB9AC\uB9D0", engName: "Footer" },
1154
+ { id: "8", name: "\uAC01\uC8FC", engName: "Footnote" },
1155
+ { id: "9", name: "\uBBF8\uC8FC", engName: "Endnote" }
1156
+ ];
1157
+ var ALIGNMENT_OPTIONS = [
1158
+ { value: "LEFT", label: "\uC67C\uCABD \uC815\uB82C", shortcut: "Ctrl+L" },
1159
+ { value: "CENTER", label: "\uAC00\uC6B4\uB370 \uC815\uB82C", shortcut: "Ctrl+E" },
1160
+ { value: "RIGHT", label: "\uC624\uB978\uCABD \uC815\uB82C", shortcut: "Ctrl+R" },
1161
+ { value: "JUSTIFY", label: "\uC591\uCABD \uC815\uB82C", shortcut: "Ctrl+J" },
1162
+ { value: "DISTRIBUTE", label: "\uBC30\uBD84 \uC815\uB82C" }
1163
+ ];
1164
+ var LINE_SPACING_OPTIONS = [
1165
+ { value: 1, label: "1.0" },
1166
+ { value: 1.15, label: "1.15" },
1167
+ { value: 1.5, label: "1.5" },
1168
+ { value: 1.6, label: "1.6" },
1169
+ { value: 2, label: "2.0" },
1170
+ { value: 2.5, label: "2.5" },
1171
+ { value: 3, label: "3.0" }
1172
+ ];
1173
+ var UNDERLINE_TYPES = [
1174
+ "NONE",
1175
+ "SOLID",
1176
+ "DASH",
1177
+ "DOT",
1178
+ "DASH_DOT",
1179
+ "DASH_DOT_DOT",
1180
+ "LONG_DASH",
1181
+ "DOUBLE",
1182
+ "WAVE",
1183
+ "HEAVY_WAVE",
1184
+ "DOUBLE_WAVE"
1185
+ ];
1186
+ var COLOR_PRESETS = [
1187
+ // Row 1: Primary colors
1188
+ "#000000",
1189
+ "#333333",
1190
+ "#555555",
1191
+ "#777777",
1192
+ "#999999",
1193
+ "#BBBBBB",
1194
+ "#DDDDDD",
1195
+ "#FFFFFF",
1196
+ // Row 2: Vivid colors
1197
+ "#FF0000",
1198
+ "#FF6600",
1199
+ "#FFCC00",
1200
+ "#33CC33",
1201
+ "#0099FF",
1202
+ "#3366FF",
1203
+ "#6633CC",
1204
+ "#CC33CC",
1205
+ // Row 3: Pastel colors
1206
+ "#FF9999",
1207
+ "#FFCC99",
1208
+ "#FFFF99",
1209
+ "#CCFFCC",
1210
+ "#99CCFF",
1211
+ "#9999FF",
1212
+ "#CC99FF",
1213
+ "#FF99CC",
1214
+ // Row 4: Dark colors
1215
+ "#CC0000",
1216
+ "#CC6600",
1217
+ "#CC9900",
1218
+ "#009900",
1219
+ "#006699",
1220
+ "#003399",
1221
+ "#330099",
1222
+ "#990066"
1223
+ ];
1224
+ var HIGHLIGHT_COLORS = [
1225
+ { value: "#FFFF00", label: "\uB178\uB791" },
1226
+ { value: "#00FF00", label: "\uC5F0\uB450" },
1227
+ { value: "#00FFFF", label: "\uD558\uB298" },
1228
+ { value: "#FF00FF", label: "\uBD84\uD64D" },
1229
+ { value: "#0000FF", label: "\uD30C\uB791" },
1230
+ { value: "#FF0000", label: "\uBE68\uAC15" },
1231
+ { value: "#008000", label: "\uCD08\uB85D" },
1232
+ { value: "#800080", label: "\uBCF4\uB77C" },
1233
+ { value: "#808080", label: "\uD68C\uC0C9" },
1234
+ { value: "none", label: "\uC5C6\uC74C" }
1235
+ ];
1236
+
1237
+ // src/components/toolbar/ToolbarDropdown.tsx
1238
+ var import_react2 = require("react");
1239
+ var import_jsx_runtime6 = require("react/jsx-runtime");
1240
+ function ToolbarDropdown({
1241
+ value,
1242
+ options,
1243
+ onChange,
1244
+ disabled,
1245
+ title,
1246
+ className = "",
1247
+ width = "w-28",
1248
+ icon
1249
+ }) {
1250
+ var _a, _b;
1251
+ const [open, setOpen] = (0, import_react2.useState)(false);
1252
+ const ref = (0, import_react2.useRef)(null);
1253
+ (0, import_react2.useEffect)(() => {
1254
+ if (!open) return;
1255
+ const handler = (e) => {
1256
+ if (ref.current && !ref.current.contains(e.target)) {
1257
+ setOpen(false);
1258
+ }
1259
+ };
1260
+ document.addEventListener("mousedown", handler);
1261
+ return () => document.removeEventListener("mousedown", handler);
1262
+ }, [open]);
1263
+ const selectedLabel = (_b = (_a = options.find((o) => o.value === value)) == null ? void 0 : _a.label) != null ? _b : value;
1264
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { ref, className: `relative ${className}`, title, children: [
1265
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
1266
+ "button",
1267
+ {
1268
+ disabled,
1269
+ onClick: () => setOpen(!open),
1270
+ className: `flex items-center gap-1 ${width} h-7 px-2 text-xs border border-gray-300 rounded bg-white hover:bg-gray-50 text-left disabled:opacity-40 disabled:cursor-not-allowed`,
1271
+ children: [
1272
+ icon,
1273
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "flex-1 truncate", children: selectedLabel }),
1274
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("svg", { className: "w-3 h-3 text-gray-400 flex-shrink-0", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M19 9l-7 7-7-7" }) })
1275
+ ]
1276
+ }
1277
+ ),
1278
+ open && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "absolute top-full left-0 mt-1 z-50 bg-white border border-gray-200 rounded shadow-lg max-h-64 overflow-auto min-w-full", children: options.map((opt) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1279
+ "button",
1280
+ {
1281
+ onClick: () => {
1282
+ onChange(opt.value);
1283
+ setOpen(false);
1284
+ },
1285
+ className: `block w-full text-left px-3 py-1.5 text-xs hover:bg-blue-50 ${opt.value === value ? "bg-blue-50 text-blue-700 font-medium" : "text-gray-700"}`,
1286
+ children: opt.label
1287
+ },
1288
+ opt.value
1289
+ )) })
1290
+ ] });
1291
+ }
1292
+
1293
+ // src/components/toolbar/StyleSelector.tsx
1294
+ var import_jsx_runtime7 = require("react/jsx-runtime");
1295
+ function StyleSelector({ value, onChange, disabled }) {
1296
+ const options = STYLE_PRESETS.map((s) => ({
1297
+ value: s.id,
1298
+ label: s.name
1299
+ }));
1300
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1301
+ ToolbarDropdown,
1302
+ {
1303
+ value,
1304
+ options,
1305
+ onChange,
1306
+ disabled,
1307
+ title: "\uC2A4\uD0C0\uC77C",
1308
+ width: "w-24"
1309
+ }
1310
+ );
1311
+ }
1312
+
1313
+ // src/components/toolbar/FontSelector.tsx
1314
+ var import_jsx_runtime8 = require("react/jsx-runtime");
1315
+ function FontSelector({ value, onChange, disabled }) {
1316
+ const options = FONT_FAMILIES.map((f) => ({
1317
+ value: f,
1318
+ label: f
1319
+ }));
1320
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1321
+ ToolbarDropdown,
1322
+ {
1323
+ value,
1324
+ options,
1325
+ onChange,
1326
+ disabled,
1327
+ title: "\uAE00\uAF34",
1328
+ width: "w-32"
1329
+ }
1330
+ );
1331
+ }
1332
+
1333
+ // src/components/toolbar/FontSizeInput.tsx
1334
+ var import_react3 = require("react");
1335
+ var import_jsx_runtime9 = require("react/jsx-runtime");
1336
+ function FontSizeInput({ value, onChange, disabled }) {
1337
+ const [editing, setEditing] = (0, import_react3.useState)(false);
1338
+ const [inputValue, setInputValue] = (0, import_react3.useState)(String(value));
1339
+ const handleBlur = (0, import_react3.useCallback)(() => {
1340
+ setEditing(false);
1341
+ const num = parseFloat(inputValue);
1342
+ if (!isNaN(num) && num > 0 && num <= 200) {
1343
+ onChange(num);
1344
+ } else {
1345
+ setInputValue(String(value));
1346
+ }
1347
+ }, [inputValue, value, onChange]);
1348
+ const handleKeyDown = (0, import_react3.useCallback)(
1349
+ (e) => {
1350
+ if (e.key === "Enter") {
1351
+ e.target.blur();
1352
+ } else if (e.key === "Escape") {
1353
+ setInputValue(String(value));
1354
+ setEditing(false);
1355
+ }
1356
+ },
1357
+ [value]
1358
+ );
1359
+ if (editing) {
1360
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1361
+ "input",
1362
+ {
1363
+ type: "text",
1364
+ value: inputValue,
1365
+ onChange: (e) => setInputValue(e.target.value),
1366
+ onBlur: handleBlur,
1367
+ onKeyDown: handleKeyDown,
1368
+ autoFocus: true,
1369
+ className: "w-14 h-7 px-2 text-xs border border-blue-400 rounded text-center focus:outline-none"
1370
+ }
1371
+ );
1372
+ }
1373
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "relative", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
1374
+ "select",
1375
+ {
1376
+ value: String(value),
1377
+ onChange: (e) => {
1378
+ const num = parseFloat(e.target.value);
1379
+ if (!isNaN(num)) onChange(num);
1380
+ },
1381
+ disabled,
1382
+ className: "w-14 h-7 px-1 text-xs border border-gray-300 rounded bg-white text-center appearance-none cursor-pointer disabled:opacity-40",
1383
+ title: "\uAE00\uC790 \uD06C\uAE30",
1384
+ onDoubleClick: () => {
1385
+ setInputValue(String(value));
1386
+ setEditing(true);
1387
+ },
1388
+ children: [
1389
+ FONT_SIZES.map((s) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("option", { value: String(s), children: s }, s)),
1390
+ !FONT_SIZES.includes(value) && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("option", { value: String(value), children: value })
1391
+ ]
1392
+ }
1393
+ ) });
1394
+ }
1395
+
1396
+ // src/components/toolbar/CharFormatButtons.tsx
1397
+ var import_lucide_react3 = require("lucide-react");
1398
+
1399
+ // src/components/toolbar/ColorPicker.tsx
1400
+ var import_react4 = require("react");
1401
+ var import_jsx_runtime10 = require("react/jsx-runtime");
1402
+ function ColorPicker({
1403
+ color,
1404
+ onChange,
1405
+ icon,
1406
+ title,
1407
+ disabled
1408
+ }) {
1409
+ const [open, setOpen] = (0, import_react4.useState)(false);
1410
+ const ref = (0, import_react4.useRef)(null);
1411
+ (0, import_react4.useEffect)(() => {
1412
+ if (!open) return;
1413
+ const handler = (e) => {
1414
+ if (ref.current && !ref.current.contains(e.target)) {
1415
+ setOpen(false);
1416
+ }
1417
+ };
1418
+ document.addEventListener("mousedown", handler);
1419
+ return () => document.removeEventListener("mousedown", handler);
1420
+ }, [open]);
1421
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { ref, className: "relative", children: [
1422
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
1423
+ "button",
1424
+ {
1425
+ disabled,
1426
+ onClick: () => setOpen(!open),
1427
+ title,
1428
+ className: "p-1.5 rounded transition-colors text-gray-600 hover:bg-gray-100 disabled:opacity-40 disabled:cursor-not-allowed flex flex-col items-center",
1429
+ children: [
1430
+ icon,
1431
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1432
+ "div",
1433
+ {
1434
+ className: "w-4 h-0.5 mt-0.5 rounded-sm",
1435
+ style: { backgroundColor: color || "#000000" }
1436
+ }
1437
+ )
1438
+ ]
1439
+ }
1440
+ ),
1441
+ open && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "absolute top-full left-0 mt-1 z-50 bg-white border border-gray-200 rounded-lg shadow-lg p-2", children: [
1442
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "grid grid-cols-8 gap-1", children: COLOR_PRESETS.map((c) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1443
+ "button",
1444
+ {
1445
+ onClick: () => {
1446
+ onChange(c);
1447
+ setOpen(false);
1448
+ },
1449
+ className: `w-5 h-5 rounded border ${c === color ? "border-blue-500 ring-1 ring-blue-300" : "border-gray-300"} hover:scale-110 transition-transform`,
1450
+ style: { backgroundColor: c },
1451
+ title: c
1452
+ },
1453
+ c
1454
+ )) }),
1455
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "mt-2 pt-2 border-t border-gray-100 flex items-center gap-2", children: [
1456
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("label", { className: "text-xs text-gray-500", children: "\uCEE4\uC2A4\uD140:" }),
1457
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1458
+ "input",
1459
+ {
1460
+ type: "color",
1461
+ value: color || "#000000",
1462
+ onChange: (e) => {
1463
+ onChange(e.target.value);
1464
+ setOpen(false);
1465
+ },
1466
+ className: "w-6 h-6 cursor-pointer border-0"
1467
+ }
1468
+ )
1469
+ ] })
1470
+ ] })
1471
+ ] });
1472
+ }
1473
+
1474
+ // src/components/toolbar/CharFormatButtons.tsx
1475
+ var import_jsx_runtime11 = require("react/jsx-runtime");
1476
+ function CharFormatButtons() {
1477
+ const activeFormat = useEditorStore((s) => s.activeFormat);
1478
+ const extendedFormat = useEditorStore((s) => s.extendedFormat);
1479
+ const selection = useEditorStore((s) => s.selection);
1480
+ const doc = useEditorStore((s) => s.doc);
1481
+ const toggleBold = useEditorStore((s) => s.toggleBold);
1482
+ const toggleItalic = useEditorStore((s) => s.toggleItalic);
1483
+ const toggleUnderline = useEditorStore((s) => s.toggleUnderline);
1484
+ const disabled = !doc || !selection;
1485
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "flex items-center gap-0.5", children: [
1486
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1487
+ ColorPicker,
1488
+ {
1489
+ color: extendedFormat.char.textColor || "#000000",
1490
+ onChange: () => {
1491
+ },
1492
+ icon: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_lucide_react3.Type, { className: "w-3.5 h-3.5" }),
1493
+ title: "\uAE00\uC790\uC0C9",
1494
+ disabled
1495
+ }
1496
+ ),
1497
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1498
+ ColorPicker,
1499
+ {
1500
+ color: extendedFormat.char.highlightColor || "#FFFF00",
1501
+ onChange: () => {
1502
+ },
1503
+ icon: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_lucide_react3.Highlighter, { className: "w-3.5 h-3.5" }),
1504
+ title: "\uD615\uAD11\uD39C",
1505
+ disabled
1506
+ }
1507
+ ),
1508
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1509
+ ToolbarButton,
1510
+ {
1511
+ icon: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_lucide_react3.Bold, { className: "w-3.5 h-3.5" }),
1512
+ active: activeFormat.bold,
1513
+ disabled,
1514
+ onClick: toggleBold,
1515
+ title: "\uAD75\uAC8C (Ctrl+B)"
1516
+ }
1517
+ ),
1518
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1519
+ ToolbarButton,
1520
+ {
1521
+ icon: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_lucide_react3.Italic, { className: "w-3.5 h-3.5" }),
1522
+ active: activeFormat.italic,
1523
+ disabled,
1524
+ onClick: toggleItalic,
1525
+ title: "\uAE30\uC6B8\uC784 (Ctrl+I)"
1526
+ }
1527
+ ),
1528
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1529
+ ToolbarButton,
1530
+ {
1531
+ icon: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_lucide_react3.Underline, { className: "w-3.5 h-3.5" }),
1532
+ active: activeFormat.underline,
1533
+ disabled,
1534
+ onClick: toggleUnderline,
1535
+ title: "\uBC11\uC904 (Ctrl+U)"
1536
+ }
1537
+ ),
1538
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1539
+ ToolbarButton,
1540
+ {
1541
+ icon: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_lucide_react3.Strikethrough, { className: "w-3.5 h-3.5" }),
1542
+ active: extendedFormat.char.strikethrough,
1543
+ disabled,
1544
+ title: "\uCDE8\uC18C\uC120"
1545
+ }
1546
+ )
1547
+ ] });
1548
+ }
1549
+
1550
+ // src/components/toolbar/AlignmentButtons.tsx
1551
+ var import_lucide_react4 = require("lucide-react");
1552
+ var import_jsx_runtime12 = require("react/jsx-runtime");
1553
+ var ALIGNMENT_ICONS = {
1554
+ LEFT: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_lucide_react4.AlignLeft, { className: "w-3.5 h-3.5" }),
1555
+ CENTER: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_lucide_react4.AlignCenter, { className: "w-3.5 h-3.5" }),
1556
+ RIGHT: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_lucide_react4.AlignRight, { className: "w-3.5 h-3.5" }),
1557
+ JUSTIFY: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_lucide_react4.AlignJustify, { className: "w-3.5 h-3.5" }),
1558
+ DISTRIBUTE: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_lucide_react4.Columns2, { className: "w-3.5 h-3.5" })
1559
+ };
1560
+ var ALIGNMENT_LABELS = {
1561
+ LEFT: "\uC67C\uCABD \uC815\uB82C (Ctrl+L)",
1562
+ CENTER: "\uAC00\uC6B4\uB370 \uC815\uB82C (Ctrl+E)",
1563
+ RIGHT: "\uC624\uB978\uCABD \uC815\uB82C (Ctrl+R)",
1564
+ JUSTIFY: "\uC591\uCABD \uC815\uB82C (Ctrl+J)",
1565
+ DISTRIBUTE: "\uBC30\uBD84 \uC815\uB82C"
1566
+ };
1567
+ function AlignmentButtons() {
1568
+ const extendedFormat = useEditorStore((s) => s.extendedFormat);
1569
+ const doc = useEditorStore((s) => s.doc);
1570
+ const selection = useEditorStore((s) => s.selection);
1571
+ const disabled = !doc || !selection;
1572
+ const currentAlignment = extendedFormat.para.alignment;
1573
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "flex items-center gap-0.5", children: Object.keys(ALIGNMENT_ICONS).map((align) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1574
+ ToolbarButton,
1575
+ {
1576
+ icon: ALIGNMENT_ICONS[align],
1577
+ active: currentAlignment === align,
1578
+ disabled,
1579
+ title: ALIGNMENT_LABELS[align]
1580
+ },
1581
+ align
1582
+ )) });
1583
+ }
1584
+
1585
+ // src/components/toolbar/LineSpacingControl.tsx
1586
+ var import_jsx_runtime13 = require("react/jsx-runtime");
1587
+ function LineSpacingControl() {
1588
+ const extendedFormat = useEditorStore((s) => s.extendedFormat);
1589
+ const doc = useEditorStore((s) => s.doc);
1590
+ const selection = useEditorStore((s) => s.selection);
1591
+ const disabled = !doc || !selection;
1592
+ const currentSpacing = extendedFormat.para.lineSpacing;
1593
+ const closestValue = LINE_SPACING_OPTIONS.reduce(
1594
+ (prev, curr) => Math.abs(curr.value - currentSpacing) < Math.abs(prev.value - currentSpacing) ? curr : prev
1595
+ ).value;
1596
+ const options = LINE_SPACING_OPTIONS.map((o) => ({
1597
+ value: String(o.value),
1598
+ label: o.label
1599
+ }));
1600
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1601
+ ToolbarDropdown,
1602
+ {
1603
+ value: String(closestValue),
1604
+ options,
1605
+ onChange: () => {
1606
+ },
1607
+ disabled,
1608
+ title: "\uC904 \uAC04\uACA9",
1609
+ width: "w-14"
1610
+ }
1611
+ );
1612
+ }
1613
+
1614
+ // src/components/toolbar/ToolbarDivider.tsx
1615
+ var import_jsx_runtime14 = require("react/jsx-runtime");
1616
+ function ToolbarDivider() {
1617
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "h-6 w-px bg-gray-200 mx-1 self-center" });
1618
+ }
1619
+
1620
+ // src/components/toolbar/SecondaryToolbar.tsx
1621
+ var import_lucide_react5 = require("lucide-react");
1622
+ var import_jsx_runtime15 = require("react/jsx-runtime");
1623
+ function SecondaryToolbar() {
1624
+ const doc = useEditorStore((s) => s.doc);
1625
+ const extendedFormat = useEditorStore((s) => s.extendedFormat);
1626
+ const addParagraph = useEditorStore((s) => s.addParagraph);
1627
+ const disabled = !doc;
1628
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "flex items-center gap-1 bg-gray-50 border-b border-gray-200 px-3 py-1 flex-wrap min-h-[36px]", children: [
1629
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
1630
+ StyleSelector,
1631
+ {
1632
+ value: "0",
1633
+ onChange: () => {
1634
+ },
1635
+ disabled
1636
+ }
1637
+ ),
1638
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(ToolbarDivider, {}),
1639
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
1640
+ FontSelector,
1641
+ {
1642
+ value: extendedFormat.char.fontFamily || "\uB9D1\uC740 \uACE0\uB515",
1643
+ onChange: () => {
1644
+ },
1645
+ disabled
1646
+ }
1647
+ ),
1648
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
1649
+ FontSizeInput,
1650
+ {
1651
+ value: extendedFormat.char.fontSize || 10,
1652
+ onChange: () => {
1653
+ },
1654
+ disabled
1655
+ }
1656
+ ),
1657
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(ToolbarDivider, {}),
1658
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(CharFormatButtons, {}),
1659
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(ToolbarDivider, {}),
1660
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(AlignmentButtons, {}),
1661
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(ToolbarDivider, {}),
1662
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(LineSpacingControl, {}),
1663
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "flex-1" }),
1664
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
1665
+ "button",
1666
+ {
1667
+ disabled,
1668
+ onClick: () => addParagraph(""),
1669
+ className: "flex items-center gap-1 px-2.5 py-1 text-xs bg-blue-500 text-white rounded hover:bg-blue-600 disabled:opacity-40 disabled:cursor-not-allowed",
1670
+ children: [
1671
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_lucide_react5.Plus, { className: "w-3 h-3" }),
1672
+ "\uBB38\uB2E8 \uCD94\uAC00"
1673
+ ]
1674
+ }
1675
+ )
1676
+ ] });
1677
+ }
1678
+
1679
+ // src/components/ruler/HorizontalRuler.tsx
1680
+ var import_jsx_runtime16 = require("react/jsx-runtime");
1681
+ function HorizontalRuler() {
1682
+ const viewModel = useEditorStore((s) => s.viewModel);
1683
+ if (!viewModel || viewModel.sections.length === 0) return null;
1684
+ const section = viewModel.sections[0];
1685
+ const pageWidthPx = section.pageWidthPx;
1686
+ const marginLeftPx = section.marginLeftPx;
1687
+ const marginRightPx = section.marginRightPx;
1688
+ const contentWidthPx = pageWidthPx - marginLeftPx - marginRightPx;
1689
+ const cmPerPx = 25.4 / 96 / 10;
1690
+ const totalCm = contentWidthPx * cmPerPx;
1691
+ const ticks = [];
1692
+ for (let cm = 0; cm <= totalCm + 0.5; cm += 0.5) {
1693
+ const px = cm / cmPerPx;
1694
+ const major = cm % 1 === 0;
1695
+ ticks.push({
1696
+ position: px,
1697
+ label: major ? String(Math.round(cm)) : "",
1698
+ major
1699
+ });
1700
+ }
1701
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "bg-white border-b border-gray-200 overflow-hidden flex-shrink-0", children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "flex justify-center", children: /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
1702
+ "div",
1703
+ {
1704
+ className: "relative h-6",
1705
+ style: { width: pageWidthPx },
1706
+ children: [
1707
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
1708
+ "div",
1709
+ {
1710
+ className: "absolute top-0 left-0 h-full bg-gray-100",
1711
+ style: { width: marginLeftPx }
1712
+ }
1713
+ ),
1714
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
1715
+ "div",
1716
+ {
1717
+ className: "absolute top-0 right-0 h-full bg-gray-100",
1718
+ style: { width: marginRightPx }
1719
+ }
1720
+ ),
1721
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
1722
+ "div",
1723
+ {
1724
+ className: "absolute top-0 h-full",
1725
+ style: { left: marginLeftPx, width: contentWidthPx },
1726
+ children: ticks.map((tick, i) => /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
1727
+ "div",
1728
+ {
1729
+ className: "absolute bottom-0",
1730
+ style: { left: tick.position },
1731
+ children: [
1732
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
1733
+ "div",
1734
+ {
1735
+ className: `w-px ${tick.major ? "h-3 bg-gray-500" : "h-1.5 bg-gray-300"}`
1736
+ }
1737
+ ),
1738
+ tick.label && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "absolute -top-0.5 left-1/2 -translate-x-1/2 text-[8px] text-gray-400 leading-none", children: tick.label })
1739
+ ]
1740
+ },
1741
+ i
1742
+ ))
1743
+ }
1744
+ )
1745
+ ]
1746
+ }
1747
+ ) }) });
1748
+ }
1749
+
1750
+ // src/components/sidebar/SidebarSection.tsx
1751
+ var import_react5 = require("react");
1752
+ var import_lucide_react6 = require("lucide-react");
1753
+ var import_jsx_runtime17 = require("react/jsx-runtime");
1754
+ function SidebarSection({
1755
+ title,
1756
+ children,
1757
+ defaultOpen = true
1758
+ }) {
1759
+ const [open, setOpen] = (0, import_react5.useState)(defaultOpen);
1760
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "border-b border-gray-100", children: [
1761
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
1762
+ "button",
1763
+ {
1764
+ onClick: () => setOpen(!open),
1765
+ className: "flex items-center gap-1 w-full px-3 py-2 text-xs font-medium text-gray-700 hover:bg-gray-50",
1766
+ children: [
1767
+ open ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_lucide_react6.ChevronDown, { className: "w-3.5 h-3.5 text-gray-400" }) : /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_lucide_react6.ChevronRight, { className: "w-3.5 h-3.5 text-gray-400" }),
1768
+ title
1769
+ ]
1770
+ }
1771
+ ),
1772
+ open && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "px-3 pb-3", children })
1773
+ ] });
1774
+ }
1775
+
1776
+ // src/components/sidebar/SidebarField.tsx
1777
+ var import_jsx_runtime18 = require("react/jsx-runtime");
1778
+ function SidebarField({ label, children }) {
1779
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "flex items-center gap-2 mb-2", children: [
1780
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "text-[11px] text-gray-500 w-14 flex-shrink-0", children: label }),
1781
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "flex-1", children })
1782
+ ] });
1783
+ }
1784
+
1785
+ // src/components/sidebar/BorderSettings.tsx
1786
+ var import_jsx_runtime19 = require("react/jsx-runtime");
1787
+ function BorderSettings({ disabled }) {
1788
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "space-y-1", children: [
1789
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(SidebarField, { label: "\uC885\uB958", children: /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
1790
+ "select",
1791
+ {
1792
+ disabled,
1793
+ className: "w-full h-6 px-1 text-[11px] border border-gray-300 rounded bg-white disabled:opacity-40",
1794
+ children: [
1795
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("option", { value: "none", children: "\uC5C6\uC74C" }),
1796
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("option", { value: "solid", children: "\uC2E4\uC120" }),
1797
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("option", { value: "dashed", children: "\uC810\uC120" }),
1798
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("option", { value: "double", children: "\uC774\uC911\uC120" })
1799
+ ]
1800
+ }
1801
+ ) }),
1802
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(SidebarField, { label: "\uB450\uAED8", children: /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
1803
+ "select",
1804
+ {
1805
+ disabled,
1806
+ className: "w-full h-6 px-1 text-[11px] border border-gray-300 rounded bg-white disabled:opacity-40",
1807
+ children: [
1808
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("option", { value: "0.1mm", children: "0.1mm" }),
1809
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("option", { value: "0.12mm", children: "0.12mm" }),
1810
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("option", { value: "0.3mm", children: "0.3mm" }),
1811
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("option", { value: "0.4mm", children: "0.4mm" }),
1812
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("option", { value: "0.5mm", children: "0.5mm" }),
1813
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("option", { value: "1.0mm", children: "1.0mm" })
1814
+ ]
1815
+ }
1816
+ ) }),
1817
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(SidebarField, { label: "\uC0C9", children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
1818
+ "input",
1819
+ {
1820
+ type: "color",
1821
+ defaultValue: "#000000",
1822
+ disabled,
1823
+ className: "w-6 h-6 border border-gray-300 rounded cursor-pointer disabled:opacity-40"
1824
+ }
1825
+ ) })
1826
+ ] });
1827
+ }
1828
+
1829
+ // src/components/sidebar/BackgroundSettings.tsx
1830
+ var import_jsx_runtime20 = require("react/jsx-runtime");
1831
+ function BackgroundSettings({ disabled }) {
1832
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "space-y-1", children: [
1833
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(SidebarField, { label: "\uCC44\uC6B0\uAE30", children: /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
1834
+ "select",
1835
+ {
1836
+ disabled,
1837
+ className: "w-full h-6 px-1 text-[11px] border border-gray-300 rounded bg-white disabled:opacity-40",
1838
+ children: [
1839
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("option", { value: "none", children: "\uC5C6\uC74C" }),
1840
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("option", { value: "color", children: "\uC0C9 \uCC44\uC6B0\uAE30" }),
1841
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("option", { value: "gradient", children: "\uADF8\uB77C\uB370\uC774\uC158" }),
1842
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("option", { value: "pattern", children: "\uBB34\uB2AC" })
1843
+ ]
1844
+ }
1845
+ ) }),
1846
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(SidebarField, { label: "\uBC30\uACBD\uC0C9", children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
1847
+ "input",
1848
+ {
1849
+ type: "color",
1850
+ defaultValue: "#ffffff",
1851
+ disabled,
1852
+ className: "w-6 h-6 border border-gray-300 rounded cursor-pointer disabled:opacity-40"
1853
+ }
1854
+ ) })
1855
+ ] });
1856
+ }
1857
+
1858
+ // src/components/sidebar/CharFormatPanel.tsx
1859
+ var import_jsx_runtime21 = require("react/jsx-runtime");
1860
+ function CharFormatPanel() {
1861
+ const extendedFormat = useEditorStore((s) => s.extendedFormat);
1862
+ const activeFormat = useEditorStore((s) => s.activeFormat);
1863
+ const doc = useEditorStore((s) => s.doc);
1864
+ const selection = useEditorStore((s) => s.selection);
1865
+ const disabled = !doc || !selection;
1866
+ const cf = extendedFormat.char;
1867
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "text-xs", children: [
1868
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(SidebarSection, { title: "\uAE30\uBCF8", children: [
1869
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(SidebarField, { label: "\uC2A4\uD0C0\uC77C", children: /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
1870
+ "select",
1871
+ {
1872
+ disabled,
1873
+ className: "w-full h-6 px-1 text-[11px] border border-gray-300 rounded bg-white disabled:opacity-40",
1874
+ defaultValue: "0",
1875
+ children: [
1876
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("option", { value: "0", children: "\uBC14\uD0D5\uAE00" }),
1877
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("option", { value: "1", children: "\uBCF8\uBB38" }),
1878
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("option", { value: "2", children: "\uAC1C\uC694 1" }),
1879
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("option", { value: "3", children: "\uAC1C\uC694 2" })
1880
+ ]
1881
+ }
1882
+ ) }),
1883
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(SidebarField, { label: "\uAE00\uAF34", children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
1884
+ "select",
1885
+ {
1886
+ disabled,
1887
+ value: cf.fontFamily || "\uB9D1\uC740 \uACE0\uB515",
1888
+ onChange: () => {
1889
+ },
1890
+ className: "w-full h-6 px-1 text-[11px] border border-gray-300 rounded bg-white disabled:opacity-40",
1891
+ children: FONT_FAMILIES.map((f) => /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("option", { value: f, children: f }, f))
1892
+ }
1893
+ ) }),
1894
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(SidebarField, { label: "\uD06C\uAE30", children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
1895
+ "select",
1896
+ {
1897
+ disabled,
1898
+ value: String(cf.fontSize || 10),
1899
+ onChange: () => {
1900
+ },
1901
+ className: "w-full h-6 px-1 text-[11px] border border-gray-300 rounded bg-white disabled:opacity-40",
1902
+ children: FONT_SIZES.map((s) => /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("option", { value: String(s), children: [
1903
+ s,
1904
+ "pt"
1905
+ ] }, s))
1906
+ }
1907
+ ) }),
1908
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(SidebarField, { label: "\uC0C9\uC0C1", children: /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "flex items-center gap-2", children: [
1909
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
1910
+ "input",
1911
+ {
1912
+ type: "color",
1913
+ value: cf.textColor || "#000000",
1914
+ onChange: () => {
1915
+ },
1916
+ disabled,
1917
+ className: "w-6 h-6 border border-gray-300 rounded cursor-pointer disabled:opacity-40"
1918
+ }
1919
+ ),
1920
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "text-[10px] text-gray-400", children: cf.textColor || "#000000" })
1921
+ ] }) })
1922
+ ] }),
1923
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(SidebarSection, { title: "\uAFB8\uBC08", children: /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "flex flex-wrap gap-1 mb-2", children: [
1924
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(FormatTag, { label: "\uAD75\uAC8C", active: activeFormat.bold }),
1925
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(FormatTag, { label: "\uAE30\uC6B8\uC784", active: activeFormat.italic }),
1926
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(FormatTag, { label: "\uBC11\uC904", active: activeFormat.underline }),
1927
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(FormatTag, { label: "\uCDE8\uC18C\uC120", active: cf.strikethrough })
1928
+ ] }) }),
1929
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(SidebarSection, { title: "\uD615\uAD11\uD39C", children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(SidebarField, { label: "\uC0C9\uC0C1", children: /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "flex items-center gap-2", children: [
1930
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
1931
+ "input",
1932
+ {
1933
+ type: "color",
1934
+ value: cf.highlightColor && cf.highlightColor !== "none" ? cf.highlightColor : "#FFFF00",
1935
+ onChange: () => {
1936
+ },
1937
+ disabled,
1938
+ className: "w-6 h-6 border border-gray-300 rounded cursor-pointer disabled:opacity-40"
1939
+ }
1940
+ ),
1941
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "text-[10px] text-gray-400", children: cf.highlightColor || "\uC5C6\uC74C" })
1942
+ ] }) }) }),
1943
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(SidebarSection, { title: "\uD14C\uB450\uB9AC", defaultOpen: false, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(BorderSettings, { disabled }) }),
1944
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(SidebarSection, { title: "\uBC30\uACBD", defaultOpen: false, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(BackgroundSettings, { disabled }) })
1945
+ ] });
1946
+ }
1947
+ function FormatTag({ label, active }) {
1948
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
1949
+ "span",
1950
+ {
1951
+ className: `px-2 py-0.5 rounded text-[10px] border ${active ? "bg-blue-50 border-blue-300 text-blue-700" : "bg-gray-50 border-gray-200 text-gray-400"}`,
1952
+ children: label
1953
+ }
1954
+ );
1955
+ }
1956
+
1957
+ // src/components/sidebar/ParaFormatPanel.tsx
1958
+ var import_jsx_runtime22 = require("react/jsx-runtime");
1959
+ function ParaFormatPanel() {
1960
+ const extendedFormat = useEditorStore((s) => s.extendedFormat);
1961
+ const doc = useEditorStore((s) => s.doc);
1962
+ const selection = useEditorStore((s) => s.selection);
1963
+ const disabled = !doc || !selection;
1964
+ const pf = extendedFormat.para;
1965
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: "text-xs", children: [
1966
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(SidebarSection, { title: "\uC815\uB82C", children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { className: "flex gap-1 mb-2", children: ALIGNMENT_OPTIONS.map((opt) => /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
1967
+ "button",
1968
+ {
1969
+ disabled,
1970
+ className: `flex-1 py-1 rounded text-[10px] border transition-colors ${pf.alignment === opt.value ? "bg-blue-50 border-blue-300 text-blue-700" : "bg-white border-gray-200 text-gray-500 hover:bg-gray-50"} disabled:opacity-40 disabled:cursor-not-allowed`,
1971
+ title: opt.label,
1972
+ children: [
1973
+ opt.value === "LEFT" && "\uC67C",
1974
+ opt.value === "CENTER" && "\uC911",
1975
+ opt.value === "RIGHT" && "\uC624",
1976
+ opt.value === "JUSTIFY" && "\uC591",
1977
+ opt.value === "DISTRIBUTE" && "\uBC30"
1978
+ ]
1979
+ },
1980
+ opt.value
1981
+ )) }) }),
1982
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(SidebarSection, { title: "\uC5EC\uBC31", children: [
1983
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(SidebarField, { label: "\uC67C\uCABD", children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
1984
+ "input",
1985
+ {
1986
+ type: "number",
1987
+ value: pf.indentLeft,
1988
+ disabled,
1989
+ onChange: () => {
1990
+ },
1991
+ className: "w-full h-6 px-1 text-[11px] border border-gray-300 rounded bg-white disabled:opacity-40"
1992
+ }
1993
+ ) }),
1994
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(SidebarField, { label: "\uC624\uB978\uCABD", children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
1995
+ "input",
1996
+ {
1997
+ type: "number",
1998
+ value: pf.indentRight,
1999
+ disabled,
2000
+ onChange: () => {
2001
+ },
2002
+ className: "w-full h-6 px-1 text-[11px] border border-gray-300 rounded bg-white disabled:opacity-40"
2003
+ }
2004
+ ) }),
2005
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(SidebarField, { label: "\uCCAB\uC904", children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
2006
+ "input",
2007
+ {
2008
+ type: "number",
2009
+ value: pf.firstLineIndent,
2010
+ disabled,
2011
+ onChange: () => {
2012
+ },
2013
+ className: "w-full h-6 px-1 text-[11px] border border-gray-300 rounded bg-white disabled:opacity-40"
2014
+ }
2015
+ ) })
2016
+ ] }),
2017
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(SidebarSection, { title: "\uAC04\uACA9", children: [
2018
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(SidebarField, { label: "\uC904 \uAC04\uACA9", children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
2019
+ "select",
2020
+ {
2021
+ disabled,
2022
+ value: String(
2023
+ LINE_SPACING_OPTIONS.reduce(
2024
+ (prev, curr) => Math.abs(curr.value - pf.lineSpacing) < Math.abs(prev.value - pf.lineSpacing) ? curr : prev
2025
+ ).value
2026
+ ),
2027
+ onChange: () => {
2028
+ },
2029
+ className: "w-full h-6 px-1 text-[11px] border border-gray-300 rounded bg-white disabled:opacity-40",
2030
+ children: LINE_SPACING_OPTIONS.map((o) => /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("option", { value: String(o.value), children: o.label }, o.value))
2031
+ }
2032
+ ) }),
2033
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(SidebarField, { label: "\uBB38\uB2E8 \uC55E", children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
2034
+ "input",
2035
+ {
2036
+ type: "number",
2037
+ value: pf.spacingBefore,
2038
+ disabled,
2039
+ onChange: () => {
2040
+ },
2041
+ className: "w-full h-6 px-1 text-[11px] border border-gray-300 rounded bg-white disabled:opacity-40"
2042
+ }
2043
+ ) }),
2044
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(SidebarField, { label: "\uBB38\uB2E8 \uB4A4", children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
2045
+ "input",
2046
+ {
2047
+ type: "number",
2048
+ value: pf.spacingAfter,
2049
+ disabled,
2050
+ onChange: () => {
2051
+ },
2052
+ className: "w-full h-6 px-1 text-[11px] border border-gray-300 rounded bg-white disabled:opacity-40"
2053
+ }
2054
+ ) })
2055
+ ] }),
2056
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(SidebarSection, { title: "\uC904 \uB098\uB214", defaultOpen: false, children: /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: "space-y-1", children: [
2057
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("label", { className: "flex items-center gap-2 text-[11px] text-gray-600", children: [
2058
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("input", { type: "checkbox", disabled, className: "w-3 h-3" }),
2059
+ "\uD55C\uAE00 \uB2E8\uC5B4 \uC798\uB9BC \uD5C8\uC6A9"
2060
+ ] }),
2061
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("label", { className: "flex items-center gap-2 text-[11px] text-gray-600", children: [
2062
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("input", { type: "checkbox", disabled, className: "w-3 h-3" }),
2063
+ "\uC601\uC5B4 \uB2E8\uC5B4 \uC798\uB9BC \uD5C8\uC6A9"
2064
+ ] }),
2065
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("label", { className: "flex items-center gap-2 text-[11px] text-gray-600", children: [
2066
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("input", { type: "checkbox", disabled, className: "w-3 h-3" }),
2067
+ "\uC678\uD1A8\uC774\uC904 \uBC29\uC9C0"
2068
+ ] })
2069
+ ] }) }),
2070
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(SidebarSection, { title: "\uD14C\uB450\uB9AC", defaultOpen: false, children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(BorderSettings, { disabled }) }),
2071
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(SidebarSection, { title: "\uBC30\uACBD", defaultOpen: false, children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(BackgroundSettings, { disabled }) })
2072
+ ] });
2073
+ }
2074
+
2075
+ // src/components/sidebar/FormatSidebar.tsx
2076
+ var import_lucide_react7 = require("lucide-react");
2077
+ var import_jsx_runtime23 = require("react/jsx-runtime");
2078
+ function FormatSidebar() {
2079
+ const uiState = useEditorStore((s) => s.uiState);
2080
+ const setSidebarTab = useEditorStore((s) => s.setSidebarTab);
2081
+ const toggleSidebar = useEditorStore((s) => s.toggleSidebar);
2082
+ if (!uiState.sidebarOpen) return null;
2083
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: "w-72 border-l border-gray-200 bg-white flex flex-col overflow-hidden flex-shrink-0", children: [
2084
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: "flex items-center border-b border-gray-200", children: [
2085
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
2086
+ "button",
2087
+ {
2088
+ onClick: () => setSidebarTab("char"),
2089
+ className: `flex-1 py-2 text-xs font-medium text-center transition-colors ${uiState.sidebarTab === "char" ? "text-blue-600 border-b-2 border-blue-600 bg-blue-50/50" : "text-gray-500 hover:text-gray-700 hover:bg-gray-50"}`,
2090
+ children: "\uAE00\uC790 \uBAA8\uC591"
2091
+ }
2092
+ ),
2093
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
2094
+ "button",
2095
+ {
2096
+ onClick: () => setSidebarTab("para"),
2097
+ className: `flex-1 py-2 text-xs font-medium text-center transition-colors ${uiState.sidebarTab === "para" ? "text-blue-600 border-b-2 border-blue-600 bg-blue-50/50" : "text-gray-500 hover:text-gray-700 hover:bg-gray-50"}`,
2098
+ children: "\uBB38\uB2E8 \uBAA8\uC591"
2099
+ }
2100
+ ),
2101
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
2102
+ "button",
2103
+ {
2104
+ onClick: toggleSidebar,
2105
+ className: "p-1.5 mr-1 rounded text-gray-400 hover:text-gray-600 hover:bg-gray-100",
2106
+ title: "\uC0AC\uC774\uB4DC\uBC14 \uB2EB\uAE30",
2107
+ children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_lucide_react7.PanelRightClose, { className: "w-4 h-4" })
2108
+ }
2109
+ )
2110
+ ] }),
2111
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { className: "flex-1 overflow-auto", children: uiState.sidebarTab === "char" ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(CharFormatPanel, {}) : /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(ParaFormatPanel, {}) })
2112
+ ] });
2113
+ }
2114
+
2115
+ // src/components/editor/ParagraphBlock.tsx
2116
+ var import_react7 = require("react");
2117
+
2118
+ // src/components/editor/RunSpan.tsx
2119
+ var import_jsx_runtime24 = require("react/jsx-runtime");
2120
+ function RunSpan({ run }) {
2121
+ const style = {};
2122
+ if (run.bold) style.fontWeight = "bold";
2123
+ if (run.italic) style.fontStyle = "italic";
2124
+ const decorations = [];
2125
+ if (run.underline) decorations.push("underline");
2126
+ if (run.strikethrough) decorations.push("line-through");
2127
+ if (decorations.length > 0) style.textDecoration = decorations.join(" ");
2128
+ if (run.color && run.color !== "#000000") style.color = run.color;
2129
+ if (run.fontFamily) style.fontFamily = run.fontFamily;
2130
+ if (run.fontSize) style.fontSize = `${run.fontSize}pt`;
2131
+ if (run.highlightColor) style.backgroundColor = run.highlightColor;
2132
+ if (run.letterSpacing) {
2133
+ style.letterSpacing = `${run.letterSpacing / 100}px`;
2134
+ }
2135
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("span", { style, children: run.text });
2136
+ }
2137
+
2138
+ // src/components/editor/TableCell.tsx
2139
+ var import_react6 = require("react");
2140
+ var import_jsx_runtime25 = require("react/jsx-runtime");
2141
+ function TableCell({
2142
+ cell,
2143
+ sectionIndex,
2144
+ paragraphIndex,
2145
+ tableIndex
2146
+ }) {
2147
+ const updateCellText = useEditorStore((s) => s.updateCellText);
2148
+ const setSelection = useEditorStore((s) => s.setSelection);
2149
+ const ref = (0, import_react6.useRef)(null);
2150
+ const handleBlur = (0, import_react6.useCallback)(() => {
2151
+ var _a;
2152
+ if (!ref.current) return;
2153
+ const newText = (_a = ref.current.textContent) != null ? _a : "";
2154
+ if (newText !== cell.text) {
2155
+ updateCellText(
2156
+ sectionIndex,
2157
+ paragraphIndex,
2158
+ tableIndex,
2159
+ cell.row,
2160
+ cell.col,
2161
+ newText
2162
+ );
2163
+ }
2164
+ }, [cell, sectionIndex, paragraphIndex, tableIndex, updateCellText]);
2165
+ const handleFocus = (0, import_react6.useCallback)(() => {
2166
+ setSelection({
2167
+ sectionIndex,
2168
+ paragraphIndex,
2169
+ type: "cell",
2170
+ tableIndex,
2171
+ row: cell.row,
2172
+ col: cell.col
2173
+ });
2174
+ }, [sectionIndex, paragraphIndex, tableIndex, cell.row, cell.col, setSelection]);
2175
+ if (!cell.isAnchor) return null;
2176
+ return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
2177
+ "td",
2178
+ {
2179
+ ref,
2180
+ contentEditable: true,
2181
+ suppressContentEditableWarning: true,
2182
+ colSpan: cell.colSpan > 1 ? cell.colSpan : void 0,
2183
+ rowSpan: cell.rowSpan > 1 ? cell.rowSpan : void 0,
2184
+ onBlur: handleBlur,
2185
+ onFocus: handleFocus,
2186
+ className: "border border-gray-300 px-2 py-1 text-sm outline-none focus:bg-blue-50",
2187
+ style: {
2188
+ minWidth: cell.widthPx > 0 ? cell.widthPx : 40,
2189
+ minHeight: cell.heightPx > 0 ? cell.heightPx : 24
2190
+ },
2191
+ children: cell.text
2192
+ }
2193
+ );
2194
+ }
2195
+
2196
+ // src/components/editor/TableBlock.tsx
2197
+ var import_jsx_runtime26 = require("react/jsx-runtime");
2198
+ function TableBlock({ table, sectionIndex, paragraphIndex }) {
2199
+ return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("div", { className: "my-2 overflow-x-auto", children: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("table", { className: "border-collapse border border-gray-400", children: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("tbody", { children: table.cells.map((row, rIdx) => /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("tr", { children: row.map(
2200
+ (cell) => cell.isAnchor ? /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
2201
+ TableCell,
2202
+ {
2203
+ cell,
2204
+ sectionIndex,
2205
+ paragraphIndex,
2206
+ tableIndex: table.tableIndex
2207
+ },
2208
+ `${cell.row}-${cell.col}`
2209
+ ) : null
2210
+ ) }, rIdx)) }) }) });
2211
+ }
2212
+
2213
+ // src/components/editor/ImageBlock.tsx
2214
+ var import_jsx_runtime27 = require("react/jsx-runtime");
2215
+ function ImageBlock({ image }) {
2216
+ return /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("div", { className: "my-2 flex justify-center", children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
2217
+ "img",
2218
+ {
2219
+ src: image.dataUrl,
2220
+ alt: "",
2221
+ style: {
2222
+ width: image.widthPx > 0 ? image.widthPx : void 0,
2223
+ height: image.heightPx > 0 ? image.heightPx : void 0,
2224
+ maxWidth: "100%"
2225
+ }
2226
+ }
2227
+ ) });
2228
+ }
2229
+
2230
+ // src/components/editor/ParagraphBlock.tsx
2231
+ var import_jsx_runtime28 = require("react/jsx-runtime");
2232
+ function alignmentToCSS(alignment) {
2233
+ switch (alignment.toUpperCase()) {
2234
+ case "CENTER":
2235
+ return "center";
2236
+ case "RIGHT":
2237
+ return "right";
2238
+ case "JUSTIFY":
2239
+ case "DISTRIBUTE":
2240
+ return "justify";
2241
+ default:
2242
+ return "left";
2243
+ }
2244
+ }
2245
+ function ParagraphBlock({
2246
+ paragraph,
2247
+ sectionIndex,
2248
+ localIndex
2249
+ }) {
2250
+ const updateParagraphText = useEditorStore((s) => s.updateParagraphText);
2251
+ const setSelection = useEditorStore((s) => s.setSelection);
2252
+ const ref = (0, import_react7.useRef)(null);
2253
+ const hasTables = paragraph.tables.length > 0;
2254
+ const hasImages = paragraph.images.length > 0;
2255
+ const hasText = paragraph.runs.length > 0;
2256
+ const handleBlur = (0, import_react7.useCallback)(() => {
2257
+ var _a;
2258
+ if (!ref.current) return;
2259
+ const newText = (_a = ref.current.textContent) != null ? _a : "";
2260
+ if (!hasTables && !hasImages) {
2261
+ const oldText = paragraph.runs.map((r) => r.text).join("");
2262
+ if (newText !== oldText) {
2263
+ updateParagraphText(sectionIndex, localIndex, newText);
2264
+ }
2265
+ }
2266
+ }, [paragraph, sectionIndex, localIndex, hasTables, hasImages, updateParagraphText]);
2267
+ const handleFocus = (0, import_react7.useCallback)(() => {
2268
+ setSelection({
2269
+ sectionIndex,
2270
+ paragraphIndex: localIndex,
2271
+ type: "paragraph"
2272
+ });
2273
+ }, [sectionIndex, localIndex, setSelection]);
2274
+ const paraStyle = {
2275
+ textAlign: alignmentToCSS(paragraph.alignment),
2276
+ lineHeight: paragraph.lineSpacing,
2277
+ marginLeft: paragraph.marginLeftPx || void 0,
2278
+ marginRight: paragraph.marginRightPx || void 0,
2279
+ textIndent: paragraph.firstLineIndent || void 0,
2280
+ paddingTop: paragraph.spacingBefore || void 0,
2281
+ paddingBottom: paragraph.spacingAfter || void 0
2282
+ };
2283
+ if (hasTables) {
2284
+ return /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { className: "my-1", style: paraStyle, children: [
2285
+ hasText && /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
2286
+ "div",
2287
+ {
2288
+ ref,
2289
+ contentEditable: true,
2290
+ suppressContentEditableWarning: true,
2291
+ onBlur: handleBlur,
2292
+ onFocus: handleFocus,
2293
+ className: "outline-none leading-relaxed",
2294
+ children: paragraph.runs.map((run, i) => /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(RunSpan, { run }, i))
2295
+ }
2296
+ ),
2297
+ paragraph.tables.map((table) => /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
2298
+ TableBlock,
2299
+ {
2300
+ table,
2301
+ sectionIndex,
2302
+ paragraphIndex: localIndex
2303
+ },
2304
+ table.tableIndex
2305
+ ))
2306
+ ] });
2307
+ }
2308
+ if (hasImages) {
2309
+ return /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { className: "my-1", style: paraStyle, children: paragraph.images.map((img, i) => /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(ImageBlock, { image: img }, i)) });
2310
+ }
2311
+ return /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
2312
+ "div",
2313
+ {
2314
+ ref,
2315
+ contentEditable: true,
2316
+ suppressContentEditableWarning: true,
2317
+ onBlur: handleBlur,
2318
+ onFocus: handleFocus,
2319
+ className: "outline-none min-h-[1.5em]",
2320
+ style: paraStyle,
2321
+ children: paragraph.runs.length > 0 ? paragraph.runs.map((run, i) => /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(RunSpan, { run }, i)) : /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("br", {})
2322
+ }
2323
+ );
2324
+ }
2325
+
2326
+ // src/components/editor/Page.tsx
2327
+ var import_jsx_runtime29 = require("react/jsx-runtime");
2328
+ function Page({ section }) {
2329
+ const addParagraph = useEditorStore((s) => s.addParagraph);
2330
+ const revision = useEditorStore((s) => s.revision);
2331
+ const handlePageClick = (e) => {
2332
+ if (e.target !== e.currentTarget) return;
2333
+ if (section.paragraphs.length === 0) {
2334
+ addParagraph("");
2335
+ return;
2336
+ }
2337
+ const container = e.currentTarget;
2338
+ const editables = container.querySelectorAll("[contenteditable]");
2339
+ const last = editables[editables.length - 1];
2340
+ if (last) {
2341
+ last.focus();
2342
+ const sel = window.getSelection();
2343
+ if (sel) {
2344
+ sel.selectAllChildren(last);
2345
+ sel.collapseToEnd();
2346
+ }
2347
+ }
2348
+ };
2349
+ return /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
2350
+ "div",
2351
+ {
2352
+ className: "bg-white shadow-lg mx-auto mb-8 cursor-text",
2353
+ onClick: handlePageClick,
2354
+ style: {
2355
+ width: section.pageWidthPx,
2356
+ minHeight: section.pageHeightPx,
2357
+ paddingTop: section.marginTopPx,
2358
+ paddingBottom: section.marginBottomPx,
2359
+ paddingLeft: section.marginLeftPx,
2360
+ paddingRight: section.marginRightPx
2361
+ },
2362
+ children: section.paragraphs.map((para, idx) => /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
2363
+ ParagraphBlock,
2364
+ {
2365
+ paragraph: para,
2366
+ sectionIndex: section.sectionIndex,
2367
+ localIndex: idx
2368
+ },
2369
+ `${section.sectionIndex}-${idx}-r${revision}`
2370
+ ))
2371
+ }
2372
+ );
2373
+ }
2374
+
2375
+ // src/components/editor/PageView.tsx
2376
+ var import_jsx_runtime30 = require("react/jsx-runtime");
2377
+ function PageView() {
2378
+ const viewModel = useEditorStore((s) => s.viewModel);
2379
+ useEditorStore((s) => s.revision);
2380
+ if (!viewModel) {
2381
+ return /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("div", { className: "flex-1 flex items-center justify-center text-gray-400", children: "\uBB38\uC11C\uB97C \uC5F4\uAC70\uB098 \uC0C8 \uBB38\uC11C\uB97C \uB9CC\uB4DC\uC138\uC694." });
2382
+ }
2383
+ return /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("div", { className: "flex-1 overflow-auto bg-gray-200 py-8", children: viewModel.sections.map((section) => /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(Page, { section }, section.sectionIndex)) });
2384
+ }
2385
+
2386
+ // src/components/upload/FileUpload.tsx
2387
+ var import_react8 = require("react");
2388
+ var import_lucide_react8 = require("lucide-react");
2389
+ var import_hwpxcore5 = require("@ubermensch1218/hwpxcore");
2390
+ var import_jsx_runtime31 = require("react/jsx-runtime");
2391
+ function FileUpload() {
2392
+ const setDocument = useEditorStore((s) => s.setDocument);
2393
+ const setLoading = useEditorStore((s) => s.setLoading);
2394
+ const setError = useEditorStore((s) => s.setError);
2395
+ const [dragging, setDragging] = (0, import_react8.useState)(false);
2396
+ const inputRef = (0, import_react8.useRef)(null);
2397
+ const openFile = (0, import_react8.useCallback)(
2398
+ async (file) => {
2399
+ setLoading(true);
2400
+ setError(null);
2401
+ try {
2402
+ const buffer = await file.arrayBuffer();
2403
+ const doc = await import_hwpxcore5.HwpxDocument.open(buffer);
2404
+ setDocument(doc);
2405
+ } catch (e) {
2406
+ console.error("Failed to open file:", e);
2407
+ setError("\uD30C\uC77C\uC744 \uC5F4 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. HWPX \uD30C\uC77C\uC778\uC9C0 \uD655\uC778\uD558\uC138\uC694.");
2408
+ } finally {
2409
+ setLoading(false);
2410
+ }
2411
+ },
2412
+ [setDocument, setLoading, setError]
2413
+ );
2414
+ const handleDrop = (0, import_react8.useCallback)(
2415
+ (e) => {
2416
+ e.preventDefault();
2417
+ setDragging(false);
2418
+ const file = e.dataTransfer.files[0];
2419
+ if (file) openFile(file);
2420
+ },
2421
+ [openFile]
2422
+ );
2423
+ const handleDragOver = (0, import_react8.useCallback)((e) => {
2424
+ e.preventDefault();
2425
+ setDragging(true);
2426
+ }, []);
2427
+ const handleDragLeave = (0, import_react8.useCallback)(() => {
2428
+ setDragging(false);
2429
+ }, []);
2430
+ const handleChange = (0, import_react8.useCallback)(
2431
+ (e) => {
2432
+ var _a;
2433
+ const file = (_a = e.target.files) == null ? void 0 : _a[0];
2434
+ if (file) openFile(file);
2435
+ e.target.value = "";
2436
+ },
2437
+ [openFile]
2438
+ );
2439
+ return /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(
2440
+ "div",
2441
+ {
2442
+ onDrop: handleDrop,
2443
+ onDragOver: handleDragOver,
2444
+ onDragLeave: handleDragLeave,
2445
+ onClick: () => {
2446
+ var _a;
2447
+ return (_a = inputRef.current) == null ? void 0 : _a.click();
2448
+ },
2449
+ className: `cursor-pointer border-2 border-dashed rounded-lg p-8 text-center transition-colors ${dragging ? "border-blue-400 bg-blue-50" : "border-gray-300 hover:border-gray-400"}`,
2450
+ children: [
2451
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_lucide_react8.Upload, { className: "w-10 h-10 mx-auto mb-3 text-gray-400" }),
2452
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("p", { className: "text-gray-600 font-medium", children: "HWPX \uD30C\uC77C\uC744 \uB4DC\uB798\uADF8\uD558\uAC70\uB098 \uD074\uB9AD\uD558\uC5EC \uC120\uD0DD" }),
2453
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("p", { className: "text-sm text-gray-400 mt-1", children: ".hwpx \uD30C\uC77C\uB9CC \uC9C0\uC6D0" }),
2454
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
2455
+ "input",
2456
+ {
2457
+ ref: inputRef,
2458
+ type: "file",
2459
+ accept: ".hwpx",
2460
+ className: "hidden",
2461
+ onChange: handleChange
2462
+ }
2463
+ )
2464
+ ]
2465
+ }
2466
+ );
2467
+ }
2468
+
2469
+ // src/components/upload/NewDocumentButton.tsx
2470
+ var import_react9 = require("react");
2471
+ var import_lucide_react9 = require("lucide-react");
2472
+ var import_jsx_runtime32 = require("react/jsx-runtime");
2473
+ function NewDocumentButton() {
2474
+ const setDocument = useEditorStore((s) => s.setDocument);
2475
+ const setLoading = useEditorStore((s) => s.setLoading);
2476
+ const setError = useEditorStore((s) => s.setError);
2477
+ const handleClick = (0, import_react9.useCallback)(async () => {
2478
+ setLoading(true);
2479
+ setError(null);
2480
+ try {
2481
+ const doc = await createNewDocument();
2482
+ setDocument(doc);
2483
+ } catch (e) {
2484
+ console.error("Failed to create new document:", e);
2485
+ setError("\uC0C8 \uBB38\uC11C\uB97C \uB9CC\uB4E4 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
2486
+ } finally {
2487
+ setLoading(false);
2488
+ }
2489
+ }, [setDocument, setLoading, setError]);
2490
+ return /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(
2491
+ "button",
2492
+ {
2493
+ onClick: handleClick,
2494
+ className: "flex items-center gap-2 px-4 py-2 bg-green-500 text-white rounded-lg hover:bg-green-600 transition-colors",
2495
+ children: [
2496
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_lucide_react9.FilePlus, { className: "w-5 h-5" }),
2497
+ "\uC0C8 \uBB38\uC11C"
2498
+ ]
2499
+ }
2500
+ );
2501
+ }
2502
+
2503
+ // src/components/editor/Editor.tsx
2504
+ var import_lucide_react10 = require("lucide-react");
2505
+ var import_jsx_runtime33 = require("react/jsx-runtime");
2506
+ function Editor() {
2507
+ const doc = useEditorStore((s) => s.doc);
2508
+ const loading = useEditorStore((s) => s.loading);
2509
+ const error = useEditorStore((s) => s.error);
2510
+ const uiState = useEditorStore((s) => s.uiState);
2511
+ const toggleSidebar = useEditorStore((s) => s.toggleSidebar);
2512
+ (0, import_react10.useEffect)(() => {
2513
+ ensureSkeletonLoaded().catch(console.error);
2514
+ }, []);
2515
+ (0, import_react10.useEffect)(() => {
2516
+ const handler = (e) => {
2517
+ const store = useEditorStore.getState();
2518
+ if (!store.doc || !store.selection) return;
2519
+ if ((e.ctrlKey || e.metaKey) && e.key === "b") {
2520
+ e.preventDefault();
2521
+ store.toggleBold();
2522
+ } else if ((e.ctrlKey || e.metaKey) && e.key === "i") {
2523
+ e.preventDefault();
2524
+ store.toggleItalic();
2525
+ } else if ((e.ctrlKey || e.metaKey) && e.key === "u") {
2526
+ e.preventDefault();
2527
+ store.toggleUnderline();
2528
+ } else if ((e.ctrlKey || e.metaKey) && e.key === "s") {
2529
+ e.preventDefault();
2530
+ store.saveDocument();
2531
+ }
2532
+ };
2533
+ window.addEventListener("keydown", handler);
2534
+ return () => window.removeEventListener("keydown", handler);
2535
+ }, []);
2536
+ if (loading) {
2537
+ return /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { className: "flex h-screen items-center justify-center", children: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { className: "text-gray-500", children: "\uB85C\uB529 \uC911..." }) });
2538
+ }
2539
+ if (!doc) {
2540
+ return /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("div", { className: "flex h-screen flex-col items-center justify-center gap-6 bg-gray-100 p-8", children: [
2541
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("h1", { className: "text-2xl font-bold text-gray-800", children: "HWPX Editor" }),
2542
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("p", { className: "text-gray-500 max-w-md text-center", children: "HWPX \uBB38\uC11C\uB97C \uC5F4\uAC70\uB098 \uC0C8 \uBB38\uC11C\uB97C \uB9CC\uB4E4\uC5B4 \uD3B8\uC9D1\uD558\uC138\uC694." }),
2543
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { className: "w-full max-w-md", children: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(FileUpload, {}) }),
2544
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { className: "flex gap-3", children: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(NewDocumentButton, {}) }),
2545
+ error && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { className: "bg-red-50 text-red-600 px-4 py-2 rounded-lg text-sm", children: error })
2546
+ ] });
2547
+ }
2548
+ return /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("div", { className: "flex h-screen flex-col", children: [
2549
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(RibbonToolbar, {}),
2550
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(SecondaryToolbar, {}),
2551
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(HorizontalRuler, {}),
2552
+ error && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { className: "bg-red-50 text-red-600 px-4 py-2 text-sm border-b border-red-200", children: error }),
2553
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("div", { className: "flex flex-1 overflow-hidden", children: [
2554
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(PageView, {}),
2555
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(FormatSidebar, {}),
2556
+ !uiState.sidebarOpen && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
2557
+ "button",
2558
+ {
2559
+ onClick: toggleSidebar,
2560
+ className: "absolute right-2 top-[140px] z-10 p-1.5 bg-white border border-gray-200 rounded shadow-sm text-gray-400 hover:text-gray-600 hover:bg-gray-50",
2561
+ title: "\uC11C\uC2DD \uC0AC\uC774\uB4DC\uBC14 \uC5F4\uAE30",
2562
+ children: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(import_lucide_react10.PanelRight, { className: "w-4 h-4" })
2563
+ }
2564
+ )
2565
+ ] })
2566
+ ] });
2567
+ }
2568
+ // Annotate the CommonJS export names for ESM import in node:
2569
+ 0 && (module.exports = {
2570
+ ALIGNMENT_OPTIONS,
2571
+ AlignmentButtons,
2572
+ BackgroundSettings,
2573
+ BorderSettings,
2574
+ COLOR_PRESETS,
2575
+ CharFormatButtons,
2576
+ CharFormatPanel,
2577
+ ClipboardGroup,
2578
+ ColorPicker,
2579
+ Editor,
2580
+ FONT_FAMILIES,
2581
+ FONT_SIZES,
2582
+ FileUpload,
2583
+ FontSelector,
2584
+ FontSizeInput,
2585
+ FormatSidebar,
2586
+ HIGHLIGHT_COLORS,
2587
+ HorizontalRuler,
2588
+ ImageBlock,
2589
+ InsertGroup,
2590
+ LINE_SPACING_OPTIONS,
2591
+ LineSpacingControl,
2592
+ NewDocumentButton,
2593
+ Page,
2594
+ PageView,
2595
+ ParaFormatPanel,
2596
+ ParagraphBlock,
2597
+ RibbonGroup,
2598
+ RibbonToolbar,
2599
+ RunSpan,
2600
+ STYLE_PRESETS,
2601
+ SecondaryToolbar,
2602
+ SidebarField,
2603
+ SidebarSection,
2604
+ StyleSelector,
2605
+ TableBlock,
2606
+ TableCell,
2607
+ ToolbarButton,
2608
+ ToolbarDivider,
2609
+ ToolbarDropdown,
2610
+ UNDERLINE_TYPES,
2611
+ buildViewModel,
2612
+ createNewDocument,
2613
+ ensureSkeletonLoaded,
2614
+ extractImages,
2615
+ getDocumentStyles,
2616
+ hwpToMm,
2617
+ hwpToPx,
2618
+ mmToHwp,
2619
+ pxToHwp,
2620
+ readCharFormat,
2621
+ readFormatFromSelection,
2622
+ readParaFormat,
2623
+ readStyleInfo,
2624
+ useEditorStore
2625
+ });
2626
+ //# sourceMappingURL=index.cjs.map