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