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