@aeriz/wysiwyg 1.0.0 → 1.0.2

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.
@@ -2388,10 +2388,124 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
2388
2388
  function setLocale(locale) {
2389
2389
  }
2390
2390
  const STRIP_TAGS = ["script", "noscript", "meta", "link", "style", "base"];
2391
+ const INHERITED_PROPERTIES = /* @__PURE__ */ new Set([
2392
+ // CSS 2.1
2393
+ "border-collapse",
2394
+ "border-spacing",
2395
+ "caption-side",
2396
+ "color",
2397
+ "cursor",
2398
+ "direction",
2399
+ "empty-cells",
2400
+ "font-family",
2401
+ "font-size",
2402
+ "font-style",
2403
+ "font-variant",
2404
+ "font-weight",
2405
+ "letter-spacing",
2406
+ "line-height",
2407
+ "list-style",
2408
+ "list-style-image",
2409
+ "list-style-position",
2410
+ "list-style-type",
2411
+ "orphans",
2412
+ "quotes",
2413
+ "text-align",
2414
+ "text-indent",
2415
+ "text-transform",
2416
+ "visibility",
2417
+ "white-space",
2418
+ "widows",
2419
+ "word-spacing",
2420
+ // CSS 3+
2421
+ "caret-color",
2422
+ "font",
2423
+ "font-feature-settings",
2424
+ "font-kerning",
2425
+ "font-language-override",
2426
+ "font-optical-sizing",
2427
+ "font-palette",
2428
+ "font-size-adjust",
2429
+ "font-stretch",
2430
+ "font-synthesis",
2431
+ "font-synthesis-weight",
2432
+ "font-synthesis-style",
2433
+ "font-synthesis-small-caps",
2434
+ "font-variant-alternates",
2435
+ "font-variant-caps",
2436
+ "font-variant-east-asian",
2437
+ "font-variant-emoji",
2438
+ "font-variant-ligatures",
2439
+ "font-variant-numeric",
2440
+ "font-variant-position",
2441
+ "font-variation-settings",
2442
+ "hanging-punctuation",
2443
+ "hyphens",
2444
+ "image-rendering",
2445
+ "line-break",
2446
+ "overflow-wrap",
2447
+ "pointer-events",
2448
+ "ruby-align",
2449
+ "ruby-position",
2450
+ "tab-size",
2451
+ "text-align-last",
2452
+ "text-combine-upright",
2453
+ "text-decoration-color",
2454
+ "text-emphasis",
2455
+ "text-emphasis-color",
2456
+ "text-emphasis-position",
2457
+ "text-emphasis-style",
2458
+ "text-justify",
2459
+ "text-orientation",
2460
+ "text-rendering",
2461
+ "text-shadow",
2462
+ "text-size-adjust",
2463
+ "text-underline-offset",
2464
+ "text-underline-position",
2465
+ "white-space-collapse",
2466
+ "word-break",
2467
+ "word-wrap",
2468
+ "writing-mode",
2469
+ "color-scheme",
2470
+ "print-color-adjust",
2471
+ // Vendor
2472
+ "-webkit-font-smoothing",
2473
+ "-moz-osx-font-smoothing",
2474
+ "-webkit-locale",
2475
+ "-webkit-text-fill-color",
2476
+ "-webkit-text-stroke",
2477
+ "-webkit-text-stroke-color",
2478
+ "-webkit-text-stroke-width",
2479
+ "-webkit-print-color-adjust",
2480
+ // SVG
2481
+ "clip-rule",
2482
+ "color-interpolation",
2483
+ "color-interpolation-filters",
2484
+ "color-rendering",
2485
+ "fill",
2486
+ "fill-opacity",
2487
+ "fill-rule",
2488
+ "marker",
2489
+ "marker-end",
2490
+ "marker-mid",
2491
+ "marker-start",
2492
+ "paint-order",
2493
+ "shape-rendering",
2494
+ "stroke",
2495
+ "stroke-dasharray",
2496
+ "stroke-dashoffset",
2497
+ "stroke-linecap",
2498
+ "stroke-linejoin",
2499
+ "stroke-miterlimit",
2500
+ "stroke-opacity",
2501
+ "stroke-width",
2502
+ "text-anchor"
2503
+ ]);
2391
2504
  async function importHtmlWithComputedStyles(html, options = {}) {
2392
2505
  const settleMs = options.settleMs ?? 1e3;
2393
2506
  const timeoutMs = options.timeoutMs ?? 3e4;
2394
2507
  const width = options.width ?? 1024;
2508
+ const mountTarget = options.mountTarget ?? null;
2395
2509
  return new Promise((resolve, reject) => {
2396
2510
  const iframe = document.createElement("iframe");
2397
2511
  iframe.setAttribute("aria-hidden", "true");
@@ -2444,7 +2558,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
2444
2558
  fail(new Error("iframe document 접근 실패"));
2445
2559
  return;
2446
2560
  }
2447
- const baked = bakeBodyToImportable(doc.body, w);
2561
+ const baked = bakeBodyToImportable(doc.body, w, mountTarget);
2448
2562
  window.clearTimeout(timer);
2449
2563
  finish(baked);
2450
2564
  } catch (err) {
@@ -2461,23 +2575,30 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
2461
2575
  document.body.appendChild(iframe);
2462
2576
  });
2463
2577
  }
2464
- function bakeBodyToImportable(body, win) {
2578
+ function bakeBodyToImportable(body, win, mountTarget) {
2465
2579
  var _a;
2580
+ const doc = body.ownerDocument;
2581
+ if (!doc) throw new Error("body has no ownerDocument");
2466
2582
  const wrapper = document.createElement("div");
2467
- for (let i = 0; i < body.attributes.length; i++) {
2468
- const attr = body.attributes.item(i);
2469
- if (!attr) continue;
2470
- if (attr.name === "style") continue;
2471
- wrapper.setAttribute(attr.name, attr.value);
2472
- }
2473
- const bodyStyle = computedStyleToInline(body, win);
2474
- if (bodyStyle.length > 0) {
2475
- wrapper.setAttribute("style", bodyStyle);
2476
- }
2477
- for (const child of Array.from(body.children)) {
2478
- const imported = document.importNode(child, true);
2479
- applyComputedStylesParallel(child, imported, win);
2480
- wrapper.appendChild(imported);
2583
+ const baseline = createBaselineProbe(doc, win, mountTarget);
2584
+ try {
2585
+ for (let i = 0; i < body.attributes.length; i++) {
2586
+ const attr = body.attributes.item(i);
2587
+ if (!attr) continue;
2588
+ if (attr.name === "style") continue;
2589
+ wrapper.setAttribute(attr.name, attr.value);
2590
+ }
2591
+ const wrapperStyle = computeMinimalInline(body, null, "div", baseline.get, win);
2592
+ if (wrapperStyle.length > 0) {
2593
+ wrapper.setAttribute("style", wrapperStyle);
2594
+ }
2595
+ for (const child of Array.from(body.children)) {
2596
+ const imported = document.importNode(child, true);
2597
+ applyComputedStylesParallel(child, imported, body, baseline.get, win);
2598
+ wrapper.appendChild(imported);
2599
+ }
2600
+ } finally {
2601
+ baseline.cleanup();
2481
2602
  }
2482
2603
  for (const tag of STRIP_TAGS) {
2483
2604
  for (const el of Array.from(wrapper.querySelectorAll(tag))) {
@@ -2491,24 +2612,83 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
2491
2612
  wrapper.setAttribute("data-imported-html", "true");
2492
2613
  return [wrapper];
2493
2614
  }
2494
- function computedStyleToInline(el, win) {
2615
+ function createBaselineProbe(iframeDoc, iframeWin, mountTarget) {
2616
+ var _a;
2617
+ let probeDoc;
2618
+ let probeWin;
2619
+ let probeHost;
2620
+ const targetWin = ((_a = mountTarget == null ? void 0 : mountTarget.ownerDocument) == null ? void 0 : _a.defaultView) ?? null;
2621
+ if (mountTarget && targetWin) {
2622
+ probeDoc = mountTarget.ownerDocument;
2623
+ probeWin = targetWin;
2624
+ const host = probeDoc.createElement("div");
2625
+ host.setAttribute("data-imported-html", "true");
2626
+ host.setAttribute("aria-hidden", "true");
2627
+ host.style.cssText = "position:absolute;left:-99999px;top:0;visibility:hidden;pointer-events:none;";
2628
+ mountTarget.appendChild(host);
2629
+ probeHost = host;
2630
+ } else {
2631
+ probeDoc = iframeDoc;
2632
+ probeWin = iframeWin;
2633
+ const host = probeDoc.createElement("div");
2634
+ host.setAttribute("aria-hidden", "true");
2635
+ host.style.cssText = "position:absolute;left:-99999px;top:0;width:0;height:0;overflow:hidden;visibility:hidden;";
2636
+ probeDoc.body.appendChild(host);
2637
+ probeHost = host;
2638
+ }
2639
+ const cache = /* @__PURE__ */ new Map();
2640
+ const get = (tagName) => {
2641
+ const cached = cache.get(tagName);
2642
+ if (cached) return cached;
2643
+ const probe = probeDoc.createElement(tagName);
2644
+ probeHost.appendChild(probe);
2645
+ const cs = probeWin.getComputedStyle(probe);
2646
+ const map = /* @__PURE__ */ new Map();
2647
+ for (let i = 0; i < cs.length; i++) {
2648
+ const prop = cs.item(i);
2649
+ if (prop) map.set(prop, cs.getPropertyValue(prop));
2650
+ }
2651
+ probeHost.removeChild(probe);
2652
+ cache.set(tagName, map);
2653
+ return map;
2654
+ };
2655
+ const cleanup = () => {
2656
+ if (probeHost.parentNode) probeHost.parentNode.removeChild(probeHost);
2657
+ };
2658
+ return { get, cleanup };
2659
+ }
2660
+ function computeMinimalInline(el, parent, baselineTagName, getBaseline, win) {
2495
2661
  const cs = win.getComputedStyle(el);
2662
+ const parentCs = parent ? win.getComputedStyle(parent) : null;
2663
+ const baseline = getBaseline(baselineTagName);
2496
2664
  let cssText = "";
2497
2665
  for (let i = 0; i < cs.length; i++) {
2498
2666
  const prop = cs.item(i);
2499
2667
  if (!prop) continue;
2500
2668
  const val = cs.getPropertyValue(prop);
2501
- if (val && val.length > 0) {
2502
- cssText += `${prop}:${val};`;
2669
+ if (!val) continue;
2670
+ if (INHERITED_PROPERTIES.has(prop)) {
2671
+ if (parentCs && parentCs.getPropertyValue(prop) === val) continue;
2672
+ } else {
2673
+ if (baseline.get(prop) === val) continue;
2503
2674
  }
2675
+ cssText += `${prop}:${val};`;
2504
2676
  }
2505
2677
  return cssText;
2506
2678
  }
2507
- function applyComputedStylesParallel(orig, copy, win) {
2679
+ function applyComputedStylesParallel(orig, copy, origParent, getBaseline, win) {
2508
2680
  if (orig.nodeType === 1 && copy.nodeType === 1) {
2509
- const cssText = computedStyleToInline(orig, win);
2681
+ const cssText = computeMinimalInline(
2682
+ orig,
2683
+ origParent,
2684
+ orig.tagName.toLowerCase(),
2685
+ getBaseline,
2686
+ win
2687
+ );
2510
2688
  if (cssText.length > 0) {
2511
2689
  copy.setAttribute("style", cssText);
2690
+ } else if (copy.hasAttribute("style")) {
2691
+ copy.removeAttribute("style");
2512
2692
  }
2513
2693
  }
2514
2694
  const origChildren = orig.children;
@@ -2517,7 +2697,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
2517
2697
  for (let i = 0; i < n; i++) {
2518
2698
  const oc = origChildren[i];
2519
2699
  const cc = copyChildren[i];
2520
- if (oc && cc) applyComputedStylesParallel(oc, cc, win);
2700
+ if (oc && cc) applyComputedStylesParallel(oc, cc, orig, getBaseline, win);
2521
2701
  }
2522
2702
  }
2523
2703
  function markdownToBlocks(md) {
@@ -2734,6 +2914,8 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
2734
2914
  label: "단락",
2735
2915
  allowsInlineFormat: true,
2736
2916
  allowedMarks: ALL_INLINE_MARKS,
2917
+ allowsColor: true,
2918
+ allowsFontSize: true,
2737
2919
  matches: (el) => el.tagName === "P" ? { kind: "paragraph", attrs: {} } : null,
2738
2920
  create: () => fillEmpty(document.createElement("p")),
2739
2921
  isEmpty: (el) => isElementBlank(el)
@@ -2745,6 +2927,10 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
2745
2927
  // 사용자 지정 — 제목은 italic 만 허용. (bold 는 heading 자체가 굵으므로
2746
2928
  // 중복, underline / strike 는 가독성/시맨틱 면에서 부적합.)
2747
2929
  allowedMarks: ["italic"],
2930
+ // 글자색/배경색은 시맨틱 헤딩 톤 보존을 위해 X.
2931
+ allowsColor: false,
2932
+ // 글자 크기는 heading level (h1~h6) 로 결정되므로 X.
2933
+ allowsFontSize: false,
2748
2934
  matches: (el) => {
2749
2935
  const m = /^H([1-6])$/.exec(el.tagName);
2750
2936
  if (!m) return null;
@@ -2761,6 +2947,8 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
2761
2947
  label: "글머리 목록",
2762
2948
  allowsInlineFormat: true,
2763
2949
  allowedMarks: ALL_INLINE_MARKS,
2950
+ allowsColor: true,
2951
+ allowsFontSize: true,
2764
2952
  matches: (el) => el.tagName === "UL" ? { kind: "bulletList", attrs: {} } : null,
2765
2953
  create: () => createListElement("ul"),
2766
2954
  isEmpty: (el) => isListEmpty(el)
@@ -2770,6 +2958,8 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
2770
2958
  label: "번호 매기기 목록",
2771
2959
  allowsInlineFormat: true,
2772
2960
  allowedMarks: ALL_INLINE_MARKS,
2961
+ allowsColor: true,
2962
+ allowsFontSize: true,
2773
2963
  matches: (el) => el.tagName === "OL" ? { kind: "orderedList", attrs: {} } : null,
2774
2964
  create: () => createListElement("ol"),
2775
2965
  isEmpty: (el) => isListEmpty(el)
@@ -2781,6 +2971,9 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
2781
2971
  // 사용자 지정 — 인용구는 italic 제외(인용 자체가 이미 italic 으로 렌더되어
2782
2972
  // 중복). bold / underline / strike 만 허용.
2783
2973
  allowedMarks: ["bold", "underline", "strike"],
2974
+ // 인용구 자체의 톤(이탤릭/주변 색조) 을 보존.
2975
+ allowsColor: false,
2976
+ allowsFontSize: true,
2784
2977
  matches: (el) => el.tagName === "BLOCKQUOTE" ? { kind: "blockquote", attrs: {} } : null,
2785
2978
  create: () => {
2786
2979
  const bq = document.createElement("blockquote");
@@ -2794,6 +2987,10 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
2794
2987
  label: "코드 블록",
2795
2988
  allowsInlineFormat: false,
2796
2989
  allowedMarks: [],
2990
+ // 코드 블록은 인라인 서식은 막혀 있지만 글자 크기 조정은 (가독성) 의미가
2991
+ // 있어 예외적으로 허용. 색은 syntax highlight 와 충돌하므로 X.
2992
+ allowsColor: false,
2993
+ allowsFontSize: true,
2797
2994
  matches: (el) => {
2798
2995
  if (el.tagName !== "PRE") return null;
2799
2996
  const code = el.querySelector(":scope > code");
@@ -2814,6 +3011,8 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
2814
3011
  label: "이미지",
2815
3012
  allowsInlineFormat: false,
2816
3013
  allowedMarks: [],
3014
+ allowsColor: false,
3015
+ allowsFontSize: false,
2817
3016
  matches: (el) => {
2818
3017
  if (el.tagName === "FIGURE") {
2819
3018
  const img = el.querySelector(":scope > img");
@@ -2877,6 +3076,8 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
2877
3076
  label: "표",
2878
3077
  allowsInlineFormat: true,
2879
3078
  allowedMarks: ALL_INLINE_MARKS,
3079
+ allowsColor: true,
3080
+ allowsFontSize: true,
2880
3081
  matches: (el) => {
2881
3082
  var _a;
2882
3083
  if (el.tagName !== "TABLE") return null;
@@ -2909,6 +3110,8 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
2909
3110
  label: "구분선",
2910
3111
  allowsInlineFormat: false,
2911
3112
  allowedMarks: [],
3113
+ allowsColor: false,
3114
+ allowsFontSize: false,
2912
3115
  matches: (el) => el.tagName === "HR" ? { kind: "hr", attrs: {} } : null,
2913
3116
  create: () => {
2914
3117
  const hr = document.createElement("hr");
@@ -2922,6 +3125,8 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
2922
3125
  label: "유튜브",
2923
3126
  allowsInlineFormat: false,
2924
3127
  allowedMarks: [],
3128
+ allowsColor: false,
3129
+ allowsFontSize: false,
2925
3130
  matches: (el) => {
2926
3131
  if (el.tagName !== "DIV") return null;
2927
3132
  if (!el.classList.contains("aw-youtube-embed")) return null;
@@ -2962,6 +3167,8 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
2962
3167
  label: "링크",
2963
3168
  allowsInlineFormat: false,
2964
3169
  allowedMarks: [],
3170
+ allowsColor: false,
3171
+ allowsFontSize: false,
2965
3172
  matches: (el) => {
2966
3173
  var _a, _b, _c, _d, _e;
2967
3174
  if (el.tagName !== "A") return null;
@@ -3049,6 +3256,8 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
3049
3256
  label: "파일",
3050
3257
  allowsInlineFormat: false,
3051
3258
  allowedMarks: [],
3259
+ allowsColor: false,
3260
+ allowsFontSize: false,
3052
3261
  matches: (el) => {
3053
3262
  if (el.tagName !== "A") return null;
3054
3263
  if (!el.classList.contains("aw-file-attachment")) return null;
@@ -3132,6 +3341,8 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
3132
3341
  label: "HTML",
3133
3342
  allowsInlineFormat: false,
3134
3343
  allowedMarks: [],
3344
+ allowsColor: false,
3345
+ allowsFontSize: false,
3135
3346
  matches: (el) => {
3136
3347
  if (el.tagName !== "DIV") return null;
3137
3348
  if (el.getAttribute("data-imported-html") !== "true") return null;
@@ -3156,6 +3367,8 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
3156
3367
  label: "알 수 없음",
3157
3368
  allowsInlineFormat: false,
3158
3369
  allowedMarks: [],
3370
+ allowsColor: false,
3371
+ allowsFontSize: false,
3159
3372
  matches: () => null,
3160
3373
  create: () => fillEmpty(document.createElement("p")),
3161
3374
  isEmpty: (el) => isElementBlank(el)
@@ -3430,6 +3643,72 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
3430
3643
  if (!target) return;
3431
3644
  while (target.firstChild) target.removeChild(target.firstChild);
3432
3645
  for (const node of content) target.appendChild(node);
3646
+ const type = BLOCK_TYPE_BY_KIND[kind];
3647
+ if (type) sanitizeInlineFormat(target, type);
3648
+ }
3649
+ function sanitizeInlineFormat(container, type) {
3650
+ const allowed = new Set(type.allowedMarks);
3651
+ for (const el of Array.from(container.querySelectorAll("*"))) {
3652
+ if (!(el instanceof HTMLElement)) continue;
3653
+ if (!el.parentNode) continue;
3654
+ const tag = el.tagName.toLowerCase();
3655
+ if ((tag === "b" || tag === "strong") && !allowed.has("bold") || (tag === "i" || tag === "em") && !allowed.has("italic") || tag === "u" && !allowed.has("underline") || (tag === "s" || tag === "strike" || tag === "del") && !allowed.has("strike")) {
3656
+ unwrapElement(el);
3657
+ continue;
3658
+ }
3659
+ if (tag === "span" || tag === "font") {
3660
+ const s = el.style;
3661
+ if (!allowed.has("bold")) {
3662
+ const fw = s.fontWeight;
3663
+ if (fw === "bold" || fw === "bolder" || /^[7-9]\d{2}$|^1000$/.test(fw)) {
3664
+ s.removeProperty("font-weight");
3665
+ }
3666
+ }
3667
+ if (!allowed.has("italic") && s.fontStyle === "italic") {
3668
+ s.removeProperty("font-style");
3669
+ }
3670
+ const td = s.textDecorationLine || s.textDecoration || "";
3671
+ if (td) {
3672
+ const parts = td.split(/\s+/);
3673
+ const kept = parts.filter((p) => {
3674
+ if (p === "underline" && !allowed.has("underline")) return false;
3675
+ if (p === "line-through" && !allowed.has("strike")) return false;
3676
+ return true;
3677
+ });
3678
+ if (kept.length !== parts.length) {
3679
+ s.removeProperty("text-decoration");
3680
+ s.removeProperty("text-decoration-line");
3681
+ if (kept.length > 0) s.textDecoration = kept.join(" ");
3682
+ }
3683
+ }
3684
+ if (!type.allowsColor) {
3685
+ s.removeProperty("color");
3686
+ s.removeProperty("background-color");
3687
+ if (tag === "font") el.removeAttribute("color");
3688
+ }
3689
+ if (!type.allowsFontSize) {
3690
+ s.removeProperty("font-size");
3691
+ if (tag === "font") el.removeAttribute("size");
3692
+ }
3693
+ if (!type.allowsInlineFormat) {
3694
+ s.removeProperty("font-family");
3695
+ if (tag === "font") el.removeAttribute("face");
3696
+ }
3697
+ const hasStyle = s.cssText.trim().length > 0;
3698
+ const hasOtherAttrs = Array.from(el.attributes).some(
3699
+ (a) => a.name !== "style"
3700
+ );
3701
+ if (!hasStyle && !hasOtherAttrs) {
3702
+ unwrapElement(el);
3703
+ }
3704
+ }
3705
+ }
3706
+ }
3707
+ function unwrapElement(el) {
3708
+ const parent = el.parentNode;
3709
+ if (!parent) return;
3710
+ while (el.firstChild) parent.insertBefore(el.firstChild, el);
3711
+ parent.removeChild(el);
3433
3712
  }
3434
3713
  function resolveTheme(theme = {}) {
3435
3714
  const fg = theme.fg || "currentColor";
@@ -3582,12 +3861,32 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
3582
3861
  function mergeInlineStyle(el, portable) {
3583
3862
  const existing = (el.getAttribute("style") ?? "").trim();
3584
3863
  if (!existing) {
3585
- el.setAttribute("style", portable);
3864
+ const trimmed = portable.replace(/;\s*$/, "").trim();
3865
+ if (trimmed) el.setAttribute("style", trimmed);
3586
3866
  return;
3587
3867
  }
3588
- const p = portable.replace(/;\s*$/, "");
3589
- const u = existing.replace(/;\s*$/, "");
3590
- el.setAttribute("style", `${p};${u}`);
3868
+ const userDecls = parseStyleDeclarations(existing);
3869
+ const userProps = new Set(userDecls.map((d) => d.prop));
3870
+ const portableDecls = parseStyleDeclarations(portable).filter(
3871
+ (d) => !userProps.has(d.prop)
3872
+ );
3873
+ const merged = [...portableDecls, ...userDecls].map((d) => `${d.prop}:${d.value}`).join(";");
3874
+ if (merged) el.setAttribute("style", merged);
3875
+ else el.removeAttribute("style");
3876
+ }
3877
+ function parseStyleDeclarations(s) {
3878
+ const out = [];
3879
+ for (const part of s.split(";")) {
3880
+ const trimmed = part.trim();
3881
+ if (!trimmed) continue;
3882
+ const idx = trimmed.indexOf(":");
3883
+ if (idx <= 0) continue;
3884
+ const prop = trimmed.slice(0, idx).trim().toLowerCase();
3885
+ const value = trimmed.slice(idx + 1).trim();
3886
+ if (!prop || !value) continue;
3887
+ out.push({ prop, value });
3888
+ }
3889
+ return out;
3591
3890
  }
3592
3891
  function stripEditorOnlyAttrs(root) {
3593
3892
  for (const el of Array.from(root.querySelectorAll("[data-block-kind]"))) {
@@ -4752,7 +5051,9 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
4752
5051
  }
4753
5052
  let imported;
4754
5053
  try {
4755
- imported = await importHtmlWithComputedStyles(result.html);
5054
+ imported = await importHtmlWithComputedStyles(result.html, {
5055
+ mountTarget: this.content
5056
+ });
4756
5057
  } catch (err) {
4757
5058
  console.error("HTML 가져오기 실패:", err);
4758
5059
  const msg = err instanceof Error ? err.message : String(err);
@@ -5269,27 +5570,15 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
5269
5570
  const type = BLOCK_TYPE_BY_KIND[active.kind] ?? BLOCK_TYPE_BY_KIND.unknown;
5270
5571
  return type.allowsInlineFormat;
5271
5572
  }
5272
- const FONT_SIZE_ALLOWED_KINDS = /* @__PURE__ */ new Set([
5273
- "paragraph",
5274
- "bulletList",
5275
- "orderedList",
5276
- "blockquote",
5277
- "codeBlock",
5278
- "table"
5279
- ]);
5280
5573
  function isFontSizeAllowed(active) {
5281
5574
  if (!active) return false;
5282
- return FONT_SIZE_ALLOWED_KINDS.has(active.kind);
5575
+ const type = BLOCK_TYPE_BY_KIND[active.kind] ?? BLOCK_TYPE_BY_KIND.unknown;
5576
+ return type.allowsFontSize;
5283
5577
  }
5284
- const COLOR_ALLOWED_KINDS = /* @__PURE__ */ new Set([
5285
- "paragraph",
5286
- "bulletList",
5287
- "orderedList",
5288
- "table"
5289
- ]);
5290
5578
  function isColorAllowed(active) {
5291
5579
  if (!active) return false;
5292
- return COLOR_ALLOWED_KINDS.has(active.kind);
5580
+ const type = BLOCK_TYPE_BY_KIND[active.kind] ?? BLOCK_TYPE_BY_KIND.unknown;
5581
+ return type.allowsColor;
5293
5582
  }
5294
5583
  const ALIGN_ALLOWED_KINDS = /* @__PURE__ */ new Set([
5295
5584
  "paragraph",