@hwp-editor/react 1.3.0 → 1.4.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 CHANGED
@@ -178,31 +178,6 @@ function bandsOnPage(envelope, segment, page, pageIndex, pageCount) {
178
178
  });
179
179
  }
180
180
 
181
- // src/sanitize.ts
182
- function sanitizeSvg(svgText) {
183
- const doc = new DOMParser().parseFromString(svgText, "image/svg+xml");
184
- const root = doc.documentElement;
185
- if (root === null || root.nodeName === "parsererror" || doc.getElementsByTagName("parsererror").length > 0) {
186
- return "";
187
- }
188
- const dangerous = root.querySelectorAll("script, foreignObject");
189
- for (const node of Array.from(dangerous)) node.remove();
190
- scrubElement(root);
191
- return root.outerHTML;
192
- }
193
- function scrubElement(el) {
194
- for (const attr of Array.from(el.attributes)) {
195
- const name = attr.name.toLowerCase();
196
- const value = attr.value.trim().toLowerCase();
197
- if (name.startsWith("on")) {
198
- el.removeAttribute(attr.name);
199
- } else if ((name === "href" || name === "xlink:href") && value.startsWith("javascript:")) {
200
- el.removeAttribute(attr.name);
201
- }
202
- }
203
- for (const child of Array.from(el.children)) scrubElement(child);
204
- }
205
-
206
181
  // src/text.ts
207
182
  function plainSegmentText(markdownSlice) {
208
183
  return markdownSlice.split("\n").map(stripLine).join("\n").trim();
@@ -299,13 +274,8 @@ function PageView(props) {
299
274
  const { page, onClick, onActivate, bands } = props;
300
275
  const { t } = useHwpEditorContext();
301
276
  const label = t("page.label", { page: page.page });
302
- const svgHtml = (0, import_react2.useMemo)(
303
- () => page.format === "svg" ? sanitizeSvg(new TextDecoder().decode(page.data)) : null,
304
- [page]
305
- );
306
277
  const imgSrc = (0, import_react2.useMemo)(() => {
307
- if (page.format === "svg") return null;
308
- const mime = page.format === "jpeg" ? "image/jpeg" : `image/${page.format}`;
278
+ const mime = page.format === "jpeg" ? "image/jpeg" : page.format === "svg" ? "image/svg+xml" : `image/${page.format}`;
309
279
  return `data:${mime};base64,${import_core2.base64.encode(page.data)}`;
310
280
  }, [page]);
311
281
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
@@ -324,17 +294,11 @@ function PageView(props) {
324
294
  }
325
295
  },
326
296
  children: [
327
- svgHtml !== null ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
328
- "div",
329
- {
330
- className: "hwped-page-svg",
331
- dangerouslySetInnerHTML: { __html: svgHtml }
332
- }
333
- ) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
297
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
334
298
  "img",
335
299
  {
336
300
  className: "hwped-page-img",
337
- src: imgSrc ?? "",
301
+ src: imgSrc,
338
302
  alt: label,
339
303
  width: page.width,
340
304
  height: page.height
@@ -403,6 +367,14 @@ var SHAPE_KEYS = [
403
367
  { value: "top", key: "segment.shape.top" },
404
368
  { value: "bottom", key: "segment.shape.bottom" }
405
369
  ];
370
+ var CLICK_REFUSAL_MESSAGE = {
371
+ cell: "segment.clickRefusedCell",
372
+ table: "segment.clickRefusedTable",
373
+ container: "segment.clickRefusedContainer",
374
+ unaddressable: "segment.clickRefusedUnaddressable",
375
+ unaddressableRuns: "segment.clickRefusedUnaddressableRuns",
376
+ noRuns: "segment.clickRefusedNoRuns"
377
+ };
406
378
  function SegmentInspector() {
407
379
  const { state, store, envelope, editable, capabilities, styles, stylesStatus, t, insertedHidden } = useHwpEditorContext();
408
380
  const { selection } = state;
@@ -427,7 +399,7 @@ function SegmentInspector() {
427
399
  })();
428
400
  const directStyleWhy = directStyleRow !== void 0 ? null : stylesStatus === "loading" ? "segment.directStyleLoading" : stylesStatus === "failed" ? "segment.directStyleFailed" : stylesStatus === "unsupported" ? "styles.unavailableHost" : capabilities === null ? "styles.checkingHost" : capabilities.styles !== "v1" ? (0, import_core3.stylesVersionSupported)(capabilities.version) ? "styles.unavailableHost" : "segment.directStyleUnavailable" : "segment.directStyleUnknown";
429
401
  const refusalText = (reason) => reason === null || reason === void 0 ? null : reason === "cell" ? t("segment.cellSelectionRefused") : reason === "table" ? t("segment.tableSelectionRefused") : reason === "container" ? t("segment.containerSelectionRefused") : t("segment.noParagraphSelection");
430
- const clickRefusedText = (reason) => reason === null || reason === void 0 ? null : reason === "cell" ? t("segment.clickRefusedCell") : reason === "table" ? t("segment.clickRefusedTable") : reason === "container" ? t("segment.clickRefusedContainer") : t("segment.clickRefusedUnaddressable");
402
+ const clickRefusedText = (reason) => reason === null || reason === void 0 ? null : t(CLICK_REFUSAL_MESSAGE[reason]);
431
403
  const refusalReason2 = refusalText(targets?.refusal);
432
404
  const targetLabel = segment === void 0 ? "" : segment.kind === "cell" ? t("segment.selectedCell", { path: (0, import_core3.segmentPath)(segment), count: paragraphTargets.length }) : singleParagraph === void 0 ? "" : t("segment.selectedPara", { path: (0, import_core3.segmentPath)(singleParagraph) });
433
405
  const currentText = envelope !== null && paragraphTargets.length > 0 ? plainSegmentText(paragraphTargets.map((candidate) => (0, import_core3.segmentText)(envelope, candidate)).join("\n")) : "";
@@ -606,7 +578,10 @@ function SegmentInspector() {
606
578
  type: "selectMany",
607
579
  selections: refs,
608
580
  primary,
609
- ...state.selectionAnchor === null || state.selectionAnchor === void 0 ? { anchor: primary } : { anchor: state.selectionAnchor }
581
+ // A matching-character-format selection is a new run selection, so its
582
+ // anchor is its own primary (#71 rule 7), never a run left over from an
583
+ // earlier one. The paragraph level keeps an existing anchor as before.
584
+ ...level === "char" || state.selectionAnchor === null || state.selectionAnchor === void 0 ? { anchor: primary } : { anchor: state.selectionAnchor }
610
585
  });
611
586
  };
612
587
  const moveParagraph = (direction) => {
@@ -720,9 +695,9 @@ function SegmentInspector() {
720
695
  // state holds and the targets the format op will address.
721
696
  children: hasRunSelection ? t("segment.selectedRunCount", { count: explicitSegments.length }) : t("segment.selectedCount", { count: selected.length })
722
697
  }),
723
- // A run-level selection formats runs, and a modifier click from it
724
- // starts a paragraph selection over instead of joining the runs.
725
- hasRunSelection && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "hwped-hint", role: "status", children: t("segment.runSelectionTerminal") }),
698
+ // A run-level selection formats runs, and a modifier click keeps it at
699
+ // run level: Ctrl toggles a unit's runs, Shift extends by run (#71).
700
+ hasRunSelection && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "hwped-hint", role: "status", children: t("segment.runSelectionGestures") }),
726
701
  // A modifier click that had no paragraph to add left the selection
727
702
  // alone, and reports the click here whatever the selection's own
728
703
  // reason is: the reader's action is what was refused, and a click
@@ -3870,7 +3845,7 @@ var en = {
3870
3845
  "segment.rangeTextEditHint": "A character range formats text but cannot rewrite it. Use whole paragraph to edit its text.",
3871
3846
  "segment.runTextEditHint": "A run selection formats runs only, so the text controls are off. Select a paragraph to edit its text.",
3872
3847
  "segment.runSelectionParagraphs": (p) => p.count === 1 ? "Alignment and spacing apply to the 1 paragraph containing a selected run." : `Alignment and spacing apply to all ${p.count} paragraphs containing a selected run.`,
3873
- "segment.runSelectionTerminal": "Starts a new paragraph selection",
3848
+ "segment.runSelectionGestures": "Ctrl adds or removes a paragraph's runs; Shift extends by run.",
3874
3849
  "segment.noParagraphSelection": "This selection carries no paragraph the engine can address, so paragraph edits are off. Pick a paragraph from the list above.",
3875
3850
  "segment.cellSelectionRefused": "This selection is a table cell. Alignment and spacing apply to every paragraph in the cell, but replace, insert and delete address exactly one paragraph, and the engine refuses to delete a cell's last one. Pick a paragraph from the list above.",
3876
3851
  "segment.tableSelectionRefused": "This selection is a table, not a paragraph. The paragraph that holds the table also holds its own text, so editing or deleting it would take the table with it. Pick a paragraph from the list above.",
@@ -3886,6 +3861,8 @@ var en = {
3886
3861
  "segment.clickRefusedTable": "The click added nothing: a table has no paragraph of its own, so there is none to add. Click a paragraph or a cell.",
3887
3862
  "segment.clickRefusedContainer": "The click added nothing: an image, a field or a bookmark has no text of its own, so there is no paragraph to add. Click a paragraph or a cell.",
3888
3863
  "segment.clickRefusedUnaddressable": "That unit does not name a paragraph the engine can address, so the click selected nothing. Click a paragraph or a cell.",
3864
+ "segment.clickRefusedUnaddressableRuns": "The clicked paragraph or cell has runs, but the engine published no address for them, so the click selected nothing. Click without Ctrl or Shift to select the whole paragraph, or click a paragraph or cell whose runs have addresses.",
3865
+ "segment.clickRefusedNoRuns": "The clicked paragraph or cell has no runs to add. Click a paragraph or cell that has text.",
3889
3866
  "segment.selectSimilar": "Select content with matching direct formatting",
3890
3867
  "segment.similarChar": "Matching character format",
3891
3868
  "segment.similarPara": "Matching paragraph format",
@@ -4012,6 +3989,7 @@ var en = {
4012
3989
  "error.staleCompose": "The open document changed while the new document was being composed; compose again.",
4013
3990
  "error.stalePreview": "The document changed while this preview was being made; preview again on the open document.",
4014
3991
  "error.unmatchedReplace": "The engine found no text to replace in that paragraph, so nothing was applied. Select the text again and retry.",
3992
+ "error.unresolvableAddress": "The engine could not address the edit's paragraph or run. Runs inside some nested paragraphs cannot be addressed. Format text in a top-level paragraph or a table cell, or edit this target in a word processor.",
4015
3993
  "error.runRangeConflict": "A run format change was refused because an earlier pending change in that paragraph (text, a link, a field, a bookmark, an image or a cell) may change its text, so the run might no longer name the same characters. Nothing was applied. Remove the format change from the pending list, apply the rest, then format the run again; or queue the format change before the others.",
4016
3994
  "error.rangeTagRefused": "This HWP document holds a range mark the engine cannot move, so a pending edit was refused and nothing was applied. Edits that move text around the mark are refused: changing a paragraph's text length, changing a field's value to one of a different length, inserting or deleting a link, field, bookmark or image, or deleting a table. A table cell that holds a mark refuses any change to its content. The engine does not say which edit. Remove those pending edits from the pending list (a same-length text replacement in a paragraph, or a same-length field value, is accepted) and apply the rest. To make those edits, convert the document to HWPX, which drops the range marks, or remove the mark in Hangul, then reopen it; reopening drops the pending list.",
4017
3995
  "error.stoppedWithoutDiagnostic": import_core18.STOPPED_WITHOUT_DIAGNOSTIC,
@@ -4341,7 +4319,7 @@ var ko = {
4341
4319
  "segment.rangeTextEditHint": "\uAE00\uC790 \uBC94\uC704 \uC120\uD0DD\uC740 \uC11C\uC2DD\uB9CC \uBC14\uAFC0 \uC218 \uC788\uACE0 \uD14D\uC2A4\uD2B8\uB294 \uBC14\uAFC0 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uD14D\uC2A4\uD2B8\uB97C \uD3B8\uC9D1\uD558\uB824\uBA74 \uBB38\uB2E8 \uC804\uCCB4\uB97C \uC0AC\uC6A9\uD558\uC138\uC694.",
4342
4320
  "segment.runTextEditHint": "run \uC120\uD0DD\uC740 \uC11C\uC2DD\uB9CC \uBC14\uAFC0 \uC218 \uC788\uC5B4 \uD14D\uC2A4\uD2B8 \uC785\uB825\uB780\uC744 \uC4F8 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uD14D\uC2A4\uD2B8\uB97C \uD3B8\uC9D1\uD558\uB824\uBA74 \uBB38\uB2E8\uC744 \uC120\uD0DD\uD558\uC138\uC694.",
4343
4321
  "segment.runSelectionParagraphs": (p) => `\uBB38\uB2E8 \uC815\uB82C\uACFC \uAC04\uACA9\uC740 \uC120\uD0DD\uD55C run\uC774 \uC788\uB294 \uBB38\uB2E8 ${p.count}\uAC1C \uC804\uCCB4\uC5D0 \uC801\uC6A9\uB429\uB2C8\uB2E4.`,
4344
- "segment.runSelectionTerminal": "\uC0C8 \uBB38\uB2E8 \uC120\uD0DD\uC774 \uC2DC\uC791\uB429\uB2C8\uB2E4",
4322
+ "segment.runSelectionGestures": "Ctrl\uB85C \uBB38\uB2E8\uC758 run\uC744 \uB354\uD558\uAC70\uB098 \uBE7C\uACE0, Shift\uB85C run \uB2E8\uC704\uB85C \uB113\uD799\uB2C8\uB2E4.",
4345
4323
  "segment.noParagraphSelection": "\uC774 \uC120\uD0DD\uC5D0\uB294 \uC5D4\uC9C4\uC774 \uC8FC\uC18C\uB85C \uC9C0\uC815\uD560 \uC218 \uC788\uB294 \uBB38\uB2E8\uC774 \uC5C6\uC5B4 \uBB38\uB2E8 \uD3B8\uC9D1\uC744 \uC0AC\uC6A9\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uC704 \uBAA9\uB85D\uC5D0\uC11C \uBB38\uB2E8\uC744 \uC120\uD0DD\uD558\uC138\uC694.",
4346
4324
  "segment.cellSelectionRefused": "\uC774 \uC120\uD0DD\uC740 \uD45C\uC758 \uC140\uC785\uB2C8\uB2E4. \uC815\uB82C\uACFC \uBB38\uB2E8 \uBAA8\uC591\uC740 \uC140 \uC548\uC758 \uBAA8\uB4E0 \uBB38\uB2E8\uC5D0 \uC801\uC6A9\uB418\uC9C0\uB9CC, \uAD50\uCCB4, \uC0BD\uC785, \uC0AD\uC81C\uB294 \uBB38\uB2E8 \uD558\uB098\uB9CC \uB300\uC0C1\uC73C\uB85C \uD558\uBA70, \uC5D4\uC9C4\uC740 \uC140\uC758 \uB9C8\uC9C0\uB9C9 \uBB38\uB2E8 \uC0AD\uC81C\uB3C4 \uAC70\uBD80\uD569\uB2C8\uB2E4. \uC704 \uBAA9\uB85D\uC5D0\uC11C \uBB38\uB2E8\uC744 \uC120\uD0DD\uD558\uC138\uC694.",
4347
4325
  "segment.tableSelectionRefused": "\uC774 \uC120\uD0DD\uC740 \uBB38\uB2E8\uC774 \uC544\uB2C8\uB77C \uD45C\uC785\uB2C8\uB2E4. \uD45C\uB97C \uB2F4\uACE0 \uC788\uB294 \uBB38\uB2E8\uC740 \uC790\uAE30 \uD14D\uC2A4\uD2B8\uB3C4 \uD568\uAED8 \uB2F4\uACE0 \uC788\uC5B4, \uADF8 \uBB38\uB2E8\uC744 \uD3B8\uC9D1\uD558\uAC70\uB098 \uC0AD\uC81C\uD558\uBA74 \uD45C\uAC00 \uC0AC\uB77C\uC9D1\uB2C8\uB2E4. \uC704 \uBAA9\uB85D\uC5D0\uC11C \uBB38\uB2E8\uC744 \uC120\uD0DD\uD558\uC138\uC694.",
@@ -4350,6 +4328,8 @@ var ko = {
4350
4328
  "segment.clickRefusedTable": "\uD074\uB9AD\uC73C\uB85C \uCD94\uAC00\uB41C \uBB38\uB2E8\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. \uD45C\uB294 \uC790\uCCB4 \uBB38\uB2E8\uC774 \uC5C6\uC5B4 \uCD94\uAC00\uD560 \uB300\uC0C1\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. \uBB38\uB2E8\uC774\uB098 \uC140\uC744 \uD074\uB9AD\uD558\uC138\uC694.",
4351
4329
  "segment.clickRefusedContainer": "\uD074\uB9AD\uC73C\uB85C \uCD94\uAC00\uB41C \uBB38\uB2E8\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. \uADF8\uB9BC, \uD544\uB4DC \uB610\uB294 \uCC45\uAC08\uD53C\uB294 \uC790\uCCB4 \uD14D\uC2A4\uD2B8\uAC00 \uC5C6\uC5B4 \uCD94\uAC00\uD560 \uBB38\uB2E8\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. \uBB38\uB2E8\uC774\uB098 \uC140\uC744 \uD074\uB9AD\uD558\uC138\uC694.",
4352
4330
  "segment.clickRefusedUnaddressable": "\uADF8 \uC694\uC18C\uB294 \uC5D4\uC9C4\uC774 \uC8FC\uC18C\uB85C \uC9C0\uC815\uD560 \uC218 \uC788\uB294 \uBB38\uB2E8\uC744 \uAC00\uB9AC\uD0A4\uC9C0 \uC54A\uC73C\uBBC0\uB85C \uD074\uB9AD\uC73C\uB85C \uC120\uD0DD\uB41C \uAC83\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. \uBB38\uB2E8\uC774\uB098 \uC140\uC744 \uD074\uB9AD\uD558\uC138\uC694.",
4331
+ "segment.clickRefusedUnaddressableRuns": "\uD074\uB9AD\uD55C \uBB38\uB2E8\uC774\uB098 \uC140\uC5D0 run\uC740 \uC788\uC9C0\uB9CC \uC5D4\uC9C4\uC774 \uADF8 run\uC758 \uC8FC\uC18C\uB97C \uC81C\uACF5\uD558\uC9C0 \uC54A\uC544 \uC120\uD0DD\uB41C \uAC83\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. Ctrl\uC774\uB098 Shift \uC5C6\uC774 \uD074\uB9AD\uD574 \uBB38\uB2E8 \uC804\uCCB4\uB97C \uC120\uD0DD\uD558\uAC70\uB098, \uC8FC\uC18C\uAC00 \uC788\uB294 run\uC744 \uAC00\uC9C4 \uBB38\uB2E8\uC774\uB098 \uC140\uC744 \uD074\uB9AD\uD558\uC138\uC694.",
4332
+ "segment.clickRefusedNoRuns": "\uD074\uB9AD\uD55C \uBB38\uB2E8\uC774\uB098 \uC140\uC5D0 \uCD94\uAC00\uD560 run\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. \uD14D\uC2A4\uD2B8\uAC00 \uC788\uB294 \uBB38\uB2E8\uC774\uB098 \uC140\uC744 \uD074\uB9AD\uD558\uC138\uC694.",
4353
4333
  "segment.selectSimilar": "\uC9C1\uC811 \uC11C\uC2DD\uC774 \uAC19\uC740 \uB0B4\uC6A9 \uC120\uD0DD",
4354
4334
  "segment.similarChar": "\uAC19\uC740 \uAE00\uC790 \uC11C\uC2DD",
4355
4335
  "segment.similarPara": "\uAC19\uC740 \uBB38\uB2E8 \uC11C\uC2DD",
@@ -4467,6 +4447,7 @@ var ko = {
4467
4447
  "error.staleCompose": "\uC0C8 \uBB38\uC11C\uB97C \uC791\uC131\uD558\uB294 \uB3D9\uC548 \uC5F4\uB9B0 \uBB38\uC11C\uAC00 \uBC14\uB00C\uC5C8\uC2B5\uB2C8\uB2E4. \uB2E4\uC2DC \uC791\uC131\uD558\uC138\uC694.",
4468
4448
  "error.stalePreview": "\uBBF8\uB9AC\uBCF4\uAE30\uB97C \uB9CC\uB4DC\uB294 \uB3D9\uC548 \uBB38\uC11C\uAC00 \uBC14\uB00C\uC5C8\uC2B5\uB2C8\uB2E4. \uC5F4\uB9B0 \uBB38\uC11C\uC5D0\uC11C \uB2E4\uC2DC \uBBF8\uB9AC\uBCF4\uAE30\uD558\uC138\uC694.",
4469
4449
  "error.unmatchedReplace": "\uD574\uB2F9 \uBB38\uB2E8\uC5D0\uC11C \uBC14\uAFC0 \uD14D\uC2A4\uD2B8\uB97C \uC5D4\uC9C4\uC774 \uCC3E\uC9C0 \uBABB\uD574 \uC544\uBB34\uAC83\uB3C4 \uC801\uC6A9\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. \uD14D\uC2A4\uD2B8\uB97C \uB2E4\uC2DC \uC120\uD0DD\uD55C \uB4A4 \uC7AC\uC2DC\uB3C4\uD558\uC138\uC694.",
4450
+ "error.unresolvableAddress": "\uC5D4\uC9C4\uC774 \uD3B8\uC9D1 \uB300\uC0C1 \uBB38\uB2E8\uC774\uB098 run\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uC77C\uBD80 \uC911\uCCA9 \uBB38\uB2E8 \uC548\uC758 run\uC740 \uC5D4\uC9C4\uC774 \uC8FC\uC18C\uB85C \uC9C0\uC815\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uCD5C\uC0C1\uC704 \uBB38\uB2E8\uC774\uB098 \uD45C \uC140\uC758 \uD14D\uC2A4\uD2B8\uC5D0 \uC11C\uC2DD\uC744 \uC9C0\uC815\uD558\uAC70\uB098, \uC774 \uB300\uC0C1\uC740 \uC6CC\uB4DC\uD504\uB85C\uC138\uC11C\uC5D0\uC11C \uD3B8\uC9D1\uD558\uC138\uC694.",
4470
4451
  "error.runRangeConflict": "\uAC19\uC740 \uBB38\uB2E8\uC5D0 \uB300\uAE30 \uC911\uC778 \uC55E\uC120 \uBCC0\uACBD(\uD14D\uC2A4\uD2B8, \uB9C1\uD06C, \uD544\uB4DC, \uCC45\uAC08\uD53C, \uC774\uBBF8\uC9C0, \uC140)\uC774 \uBB38\uB2E8 \uD14D\uC2A4\uD2B8\uB97C \uBC14\uAFC0 \uC218 \uC788\uC5B4 run\uC774 \uB354 \uC774\uC0C1 \uAC19\uC740 \uAE00\uC790\uB97C \uAC00\uB9AC\uD0A4\uC9C0 \uC54A\uC744 \uC218 \uC788\uC73C\uBBC0\uB85C run \uC11C\uC2DD \uBCC0\uACBD\uC774 \uAC70\uBD80\uB418\uC5C8\uC2B5\uB2C8\uB2E4. \uC544\uBB34\uAC83\uB3C4 \uC801\uC6A9\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. \uB300\uAE30 \uBAA9\uB85D\uC5D0\uC11C \uC11C\uC2DD \uBCC0\uACBD\uC744 \uC81C\uAC70\uD558\uACE0 \uB098\uBA38\uC9C0\uB97C \uC801\uC6A9\uD55C \uB4A4 run \uC11C\uC2DD\uC744 \uB2E4\uC2DC \uC9C0\uC815\uD558\uAC70\uB098, \uC11C\uC2DD \uBCC0\uACBD\uC744 \uB2E4\uB978 \uBCC0\uACBD\uBCF4\uB2E4 \uBA3C\uC800 \uB300\uAE30\uC5F4\uC5D0 \uB123\uC73C\uC138\uC694.",
4471
4452
  "error.rangeTagRefused": "\uC774 HWP \uBB38\uC11C\uC5D0\uB294 \uC5D4\uC9C4\uC774 \uC62E\uAE38 \uC218 \uC5C6\uB294 \uAD6C\uAC04 \uD45C\uC2DC\uAC00 \uC788\uC5B4 \uB300\uAE30 \uC911\uC778 \uD3B8\uC9D1\uC774 \uAC70\uBD80\uB418\uC5C8\uACE0, \uC544\uBB34\uAC83\uB3C4 \uC801\uC6A9\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. \uAD6C\uAC04 \uD45C\uC2DC \uC8FC\uBCC0\uC758 \uAE00\uC790\uB97C \uC6C0\uC9C1\uC774\uB294 \uD3B8\uC9D1\uC740 \uAC70\uBD80\uB429\uB2C8\uB2E4: \uBB38\uB2E8 \uD14D\uC2A4\uD2B8 \uAE38\uC774 \uBCC0\uACBD, \uAE38\uC774\uAC00 \uB2E4\uB978 \uD544\uB4DC \uAC12\uC73C\uB85C\uC758 \uBCC0\uACBD, \uB9C1\uD06C\xB7\uD544\uB4DC\xB7\uCC45\uAC08\uD53C\xB7\uC774\uBBF8\uC9C0\uC758 \uC0BD\uC785\uC774\uB098 \uC0AD\uC81C, \uD45C \uC0AD\uC81C. \uAD6C\uAC04 \uD45C\uC2DC\uAC00 \uC788\uB294 \uD45C \uC140\uC740 \uB0B4\uC6A9\uC744 \uC5B4\uB5BB\uAC8C \uBC14\uAFB8\uB4E0 \uAC70\uBD80\uD569\uB2C8\uB2E4. \uC5D4\uC9C4\uC740 \uC5B4\uB290 \uD3B8\uC9D1\uC778\uC9C0 \uC54C\uB824 \uC8FC\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. \uB300\uAE30 \uBAA9\uB85D\uC5D0\uC11C \uD574\uB2F9 \uD3B8\uC9D1\uC744 \uC81C\uAC70\uD558\uACE0(\uBB38\uB2E8 \uC548\uC758 \uAC19\uC740 \uAE38\uC774 \uD14D\uC2A4\uD2B8 \uCE58\uD658\uACFC \uAC19\uC740 \uAE38\uC774\uC758 \uD544\uB4DC \uAC12\uC740 \uD5C8\uC6A9\uB429\uB2C8\uB2E4) \uB098\uBA38\uC9C0\uB97C \uC801\uC6A9\uD558\uC138\uC694. \uADF8\uB7F0 \uD3B8\uC9D1\uC774 \uD544\uC694\uD558\uBA74 \uBB38\uC11C\uB97C HWPX\uB85C \uBCC0\uD658\uD558\uAC70\uB098(\uBCC0\uD658\uD558\uBA74 \uAD6C\uAC04 \uD45C\uC2DC\uAC00 \uC0AC\uB77C\uC9D1\uB2C8\uB2E4) \uD55C\uAE00\uC5D0\uC11C \uAD6C\uAC04 \uD45C\uC2DC\uB97C \uC81C\uAC70\uD55C \uB4A4 \uB2E4\uC2DC \uC5EC\uC138\uC694. \uB2E4\uC2DC \uC5F4\uBA74 \uB300\uAE30 \uBAA9\uB85D\uC740 \uC0AC\uB77C\uC9D1\uB2C8\uB2E4.",
4472
4453
  "error.noEngineCause": "\uC5D4\uC9C4\uC774 \uC6D0\uC778\uC744 \uBC1D\uD788\uC9C0 \uC54A\uACE0 \uC2E4\uD328\uD588\uC73C\uBBC0\uB85C(\uB300\uAC1C \uBE44\uC815\uC0C1 \uC885\uB8CC\uB41C \uACBD\uC6B0\uC785\uB2C8\uB2E4) \uC544\uBB34\uAC83\uB3C4 \uC801\uC6A9\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. \uB2E4\uC2DC \uC801\uC6A9\uD558\uACE0, \uAC19\uC740 \uBC29\uC2DD\uC73C\uB85C \uC2E4\uD328\uD558\uBA74 \uD638\uC2A4\uD2B8 \uB85C\uADF8\uC640 \uD568\uAED8 \uC54C\uB824 \uC8FC\uC138\uC694.",
@@ -4644,7 +4625,8 @@ var RENDER_SVG = { format: "svg" };
4644
4625
  var REFUSAL_MESSAGE = {
4645
4626
  unmatched_replace: "error.unmatchedReplace",
4646
4627
  run_range_conflict: "error.runRangeConflict",
4647
- range_tag: "error.rangeTagRefused"
4628
+ range_tag: "error.rangeTagRefused",
4629
+ unresolvable_address: "error.unresolvableAddress"
4648
4630
  };
4649
4631
  var HwpEditor = (0, import_react13.forwardRef)(
4650
4632
  function HwpEditor2(props, ref) {
@@ -5513,6 +5495,31 @@ var HwpEditor = (0, import_react13.forwardRef)(
5513
5495
  ) });
5514
5496
  }
5515
5497
  );
5498
+
5499
+ // src/sanitize.ts
5500
+ function sanitizeSvg(svgText) {
5501
+ const doc = new DOMParser().parseFromString(svgText, "image/svg+xml");
5502
+ const root = doc.documentElement;
5503
+ if (root === null || root.nodeName === "parsererror" || doc.getElementsByTagName("parsererror").length > 0) {
5504
+ return "";
5505
+ }
5506
+ const dangerous = root.querySelectorAll("script, foreignObject");
5507
+ for (const node of Array.from(dangerous)) node.remove();
5508
+ scrubElement(root);
5509
+ return root.outerHTML;
5510
+ }
5511
+ function scrubElement(el) {
5512
+ for (const attr of Array.from(el.attributes)) {
5513
+ const name = attr.name.toLowerCase();
5514
+ const value = attr.value.trim().toLowerCase();
5515
+ if (name.startsWith("on")) {
5516
+ el.removeAttribute(attr.name);
5517
+ } else if ((name === "href" || name === "xlink:href") && value.startsWith("javascript:")) {
5518
+ el.removeAttribute(attr.name);
5519
+ }
5520
+ }
5521
+ for (const child of Array.from(el.children)) scrubElement(child);
5522
+ }
5516
5523
  // Annotate the CommonJS export names for ESM import in node:
5517
5524
  0 && (module.exports = {
5518
5525
  COMPOSE_PRESETS,