@aeriz/wysiwyg 1.0.4 → 1.0.5

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.
@@ -2425,8 +2425,9 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
2425
2425
  for (const l of LANGUAGES) m.set(l.id, l);
2426
2426
  return m;
2427
2427
  })();
2428
+ const PLAIN_TEXT_ID = "plaintext";
2428
2429
  function highlightCode(code, langId) {
2429
- if (!langId) return escapeHtml$2(code);
2430
+ if (!langId || langId === PLAIN_TEXT_ID) return escapeHtml$2(code);
2430
2431
  const lang = LANG_BY_ID.get(langId);
2431
2432
  if (!lang) return escapeHtml$2(code);
2432
2433
  if (lang.custom) return lang.custom(code);
@@ -3485,6 +3486,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
3485
3486
  const pre = document.createElement("pre");
3486
3487
  const code = document.createElement("code");
3487
3488
  if (attrs == null ? void 0 : attrs.language) code.setAttribute("data-language", attrs.language);
3489
+ fillEmpty(code);
3488
3490
  pre.appendChild(code);
3489
3491
  return pre;
3490
3492
  },
@@ -3990,9 +3992,15 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
3990
3992
  fig.appendChild(makeResizeHandle());
3991
3993
  }
3992
3994
  }
3995
+ ensureCodeCarets(root);
3993
3996
  dropEmptyCaptions(root);
3994
3997
  if (options.writableTail !== false) ensureWritableTail(root);
3995
3998
  }
3999
+ function ensureCodeCarets(root) {
4000
+ for (const code of Array.from(root.querySelectorAll("pre > code"))) {
4001
+ if (code.childNodes.length === 0) code.appendChild(document.createElement("br"));
4002
+ }
4003
+ }
3996
4004
  function dropEmptyCaptions(root) {
3997
4005
  var _a;
3998
4006
  const captions = root.querySelectorAll("figure > figcaption");
@@ -5619,7 +5627,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
5619
5627
  };
5620
5628
  const DRAG_MIME = "application/x-aeriz-block";
5621
5629
  const MIN_BLOCK_WIDTH = 80;
5622
- class AerizEditor {
5630
+ const _AerizEditor = class _AerizEditor {
5623
5631
  constructor(options) {
5624
5632
  __publicField(this, "root");
5625
5633
  __publicField(this, "content");
@@ -5657,6 +5665,11 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
5657
5665
  __publicField(this, "hoverRaf", null);
5658
5666
  /** 라벨 pill 의 등장 애니메이션을 블록이 **바뀔 때만** 다시 재생하기 위한 표식. */
5659
5667
  __publicField(this, "pillBlockId", null);
5668
+ /**
5669
+ * IME 조합이 진행 중인지. 한글 · 일본어 · 중국어 입력은 글자 하나가 여러
5670
+ * 입력에 걸쳐 만들어지는데, 그 사이에 DOM 을 다시 쓰면 조합이 끊긴다.
5671
+ */
5672
+ __publicField(this, "composing", false);
5660
5673
  /**
5661
5674
  * 편집 이력. 브라우저 기본 undo 는 우리가 직접 한 DOM 조작(블록 이동 · 표
5662
5675
  * 편집 · 유형 변경) 을 모르기 때문에 자체 스택을 둔다.
@@ -5820,6 +5833,13 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
5820
5833
  document.addEventListener("mouseup", this.handleDocumentMouseUp);
5821
5834
  this.content.addEventListener("input", () => this.scheduleCodeHighlight());
5822
5835
  this.content.addEventListener("input", (e) => this.handleInputRule(e));
5836
+ this.content.addEventListener("compositionstart", () => {
5837
+ this.composing = true;
5838
+ });
5839
+ this.content.addEventListener("compositionend", () => {
5840
+ this.composing = false;
5841
+ this.scheduleCodeHighlight();
5842
+ });
5823
5843
  this.content.addEventListener("dragstart", (e) => {
5824
5844
  const t2 = e.target;
5825
5845
  if (!t2) return;
@@ -6486,6 +6506,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
6486
6506
  }
6487
6507
  this.codeHighlightTimer = window.setTimeout(() => {
6488
6508
  this.codeHighlightTimer = null;
6509
+ if (this.composing) return;
6489
6510
  const active = this.getActiveBlock();
6490
6511
  if ((active == null ? void 0 : active.kind) !== "codeBlock") return;
6491
6512
  const code = active.element.querySelector(":scope > code");
@@ -6515,7 +6536,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
6515
6536
  * code 안에 있다면 textContent offset 으로 caret 위치 보존.
6516
6537
  */
6517
6538
  rehighlightCode(code, langId) {
6518
- const text = code.textContent ?? "";
6539
+ const text = codeTextOf(code);
6519
6540
  const sel = saveCodeSelection(code);
6520
6541
  code.innerHTML = highlightCode(text, langId);
6521
6542
  if (sel) restoreCodeSelection(code, sel);
@@ -7229,6 +7250,11 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
7229
7250
  if (e.key === "Backspace" && !e.isComposing) {
7230
7251
  if (this.handleBackspaceKey(e)) return;
7231
7252
  }
7253
+ if (e.key === "Enter" && e.shiftKey && !e.isComposing) {
7254
+ const active2 = this.getActiveBlock();
7255
+ if ((active2 == null ? void 0 : active2.kind) === "codeBlock") this.insertCodeLineBreak(e, active2);
7256
+ return;
7257
+ }
7232
7258
  if (e.key !== "Enter" || e.shiftKey || e.isComposing) return;
7233
7259
  const sel = document.getSelection();
7234
7260
  if (!sel || sel.rangeCount === 0 || !sel.isCollapsed) return;
@@ -7405,8 +7431,72 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
7405
7431
  this.queueSync();
7406
7432
  return true;
7407
7433
  }
7434
+ if (active.kind === "codeBlock") {
7435
+ return this.indentCode(e, active, e.shiftKey);
7436
+ }
7408
7437
  return false;
7409
7438
  }
7439
+ /**
7440
+ * 코드 블록 들여쓰기 / 내어쓰기 (Tab · Shift+Tab).
7441
+ *
7442
+ * 코드를 쓰는 자리에서 Tab 이 포커스를 툴바로 옮겨 버리면 들여쓰기를 할
7443
+ * 방법이 없다. 다른 블록에서는 그 탈출구가 필요하지만(§7.5) 코드 블록은
7444
+ * 예외다.
7445
+ *
7446
+ * 선택 영역이 여러 줄에 걸치면 **걸친 줄 전체**를 옮긴다. 한 줄 안에 캐럿만
7447
+ * 있으면 그 자리에 들여쓰기를 넣는다 — 편집기들이 공통으로 하는 동작이다.
7448
+ *
7449
+ * 텍스트를 통째로 다시 쓰고 캐럿은 문자 오프셋으로 되돌린다. 하이라이트가
7450
+ * 이미 같은 방식으로 동작하므로 경로가 하나로 모인다.
7451
+ */
7452
+ indentCode(e, active, outdent) {
7453
+ const code = active.element.querySelector(":scope > code");
7454
+ if (!code) return false;
7455
+ const offsets = captureSelectionOffsets(code) ?? { start: 0, end: 0 };
7456
+ const text = codeTextOf(code);
7457
+ const indent = _AerizEditor.CODE_INDENT;
7458
+ let next;
7459
+ let start;
7460
+ let end;
7461
+ if (!outdent && offsets.start === offsets.end) {
7462
+ next = text.slice(0, offsets.start) + indent + text.slice(offsets.start);
7463
+ start = end = offsets.start + indent.length;
7464
+ } else {
7465
+ const from = text.lastIndexOf("\n", offsets.start - 1) + 1;
7466
+ const toRaw = text.indexOf("\n", offsets.end);
7467
+ const to = toRaw === -1 ? text.length : toRaw;
7468
+ let firstDelta = 0;
7469
+ let totalDelta = 0;
7470
+ const moved = text.slice(from, to).split("\n").map((line, i) => {
7471
+ if (outdent) {
7472
+ const m = /^(\t| {1,2})/.exec(line);
7473
+ const removed = (m == null ? void 0 : m[0].length) ?? 0;
7474
+ if (i === 0) firstDelta = -removed;
7475
+ totalDelta -= removed;
7476
+ return line.slice(removed);
7477
+ }
7478
+ if (line === "") return line;
7479
+ if (i === 0) firstDelta = indent.length;
7480
+ totalDelta += indent.length;
7481
+ return indent + line;
7482
+ }).join("\n");
7483
+ if (totalDelta === 0) {
7484
+ e.preventDefault();
7485
+ return true;
7486
+ }
7487
+ next = text.slice(0, from) + moved + text.slice(to);
7488
+ start = Math.max(from, offsets.start + firstDelta);
7489
+ end = Math.max(start, offsets.end + totalDelta);
7490
+ }
7491
+ e.preventDefault();
7492
+ this.markStructuralChange();
7493
+ const langId = code.getAttribute("data-language") ?? void 0;
7494
+ code.innerHTML = highlightCode(next, langId);
7495
+ this.content.focus();
7496
+ restoreSelectionOffsets(code, { start, end });
7497
+ this.flush();
7498
+ return true;
7499
+ }
7410
7500
  /**
7411
7501
  * Backspace 처리. 처리했으면 `true`.
7412
7502
  *
@@ -7432,6 +7522,43 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
7432
7522
  placeCaretAtStart(p);
7433
7523
  return true;
7434
7524
  }
7525
+ /**
7526
+ * 코드 블록 안에 줄바꿈을 넣는다 (Shift+Enter).
7527
+ *
7528
+ * `<pre>` 는 개행 문자를 그대로 보존하므로 줄바꿈은 **문자 하나**여야 한다.
7529
+ * `<br>` 을 쓰면 `textContent` 가 그 자리를 빈 것으로 읽어, 하이라이트가
7530
+ * 다시 그려지는 순간(타이핑 멈춘 뒤 300ms) 줄이 사라진다.
7531
+ *
7532
+ * 맨 끝에서는 개행을 **하나 더** 넣는다. `<pre>` 는 마지막 개행 하나를
7533
+ * 접어 버려서, 하나만 넣으면 새 줄이 화면에 나타나지 않고 캐럿도 앞 줄에
7534
+ * 머문 것처럼 보인다. 뒤쪽 개행은 자리를 만드는 용도이고, 이어서 글자를
7535
+ * 치면 자연스럽게 그 줄의 내용이 된다.
7536
+ */
7537
+ insertCodeLineBreak(e, active) {
7538
+ const code = active.element.querySelector(":scope > code");
7539
+ if (!code) return;
7540
+ const sel = document.getSelection();
7541
+ if (!sel || sel.rangeCount === 0) return;
7542
+ e.preventDefault();
7543
+ if (!code.contains(sel.getRangeAt(0).commonAncestorContainer)) {
7544
+ placeCaretAtStart(active.element);
7545
+ }
7546
+ const range = sel.getRangeAt(0);
7547
+ if (!code.contains(range.commonAncestorContainer)) return;
7548
+ range.deleteContents();
7549
+ const after = document.createRange();
7550
+ after.selectNodeContents(code);
7551
+ after.setStart(range.endContainer, range.endOffset);
7552
+ const atEnd = after.toString() === "";
7553
+ const inserted = document.createTextNode(atEnd ? "\n\n" : "\n");
7554
+ range.insertNode(inserted);
7555
+ const caret = document.createRange();
7556
+ caret.setStart(inserted, 1);
7557
+ caret.collapse(true);
7558
+ sel.removeAllRanges();
7559
+ sel.addRange(caret);
7560
+ this.flush();
7561
+ }
7435
7562
  /**
7436
7563
  * 활성 블록 뒤에 새 빈 **본문 블록**을 만들고 캐럿을 그 안에 둔다.
7437
7564
  * 인용구 / 코드 블록의 Enter 탈출, 제목 끝 Enter 에 사용.
@@ -7775,6 +7902,29 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
7775
7902
  this.toolbar.setVisible("codeLang", (active == null ? void 0 : active.kind) === "codeBlock");
7776
7903
  this.toolbar.syncGroupVisibility();
7777
7904
  }
7905
+ };
7906
+ /** 코드 블록의 들여쓰기 한 단계. 공백 두 칸 — 이 저장소의 코드 스타일과 같다. */
7907
+ __publicField(_AerizEditor, "CODE_INDENT", " ");
7908
+ let AerizEditor = _AerizEditor;
7909
+ function codeTextOf(code) {
7910
+ var _a;
7911
+ if (code.childNodes.length === 1 && ((_a = code.firstChild) == null ? void 0 : _a.nodeName) === "BR") {
7912
+ return "";
7913
+ }
7914
+ let out = "";
7915
+ const walk = (node) => {
7916
+ for (const child of Array.from(node.childNodes)) {
7917
+ if (child.nodeType === Node.TEXT_NODE) {
7918
+ out += child.textContent ?? "";
7919
+ } else if (child.nodeName === "BR") {
7920
+ out += "\n";
7921
+ } else {
7922
+ walk(child);
7923
+ }
7924
+ }
7925
+ };
7926
+ walk(code);
7927
+ return out;
7778
7928
  }
7779
7929
  function parseYouTubeId(input) {
7780
7930
  const trimmed = input.trim();