@teriusu/rich-editor 0.10.8 → 0.11.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/components/BubbleToolbar.svelte +40 -36
- package/dist/components/BubbleToolbar.svelte.d.ts +3 -0
- package/dist/components/BubbleToolbar.svelte.d.ts.map +1 -1
- package/dist/components/EmojiSuggestionMenu.svelte +291 -0
- package/dist/components/EmojiSuggestionMenu.svelte.d.ts +9 -0
- package/dist/components/EmojiSuggestionMenu.svelte.d.ts.map +1 -0
- package/dist/components/FixedToolbar.svelte +53 -47
- package/dist/components/FixedToolbar.svelte.d.ts +3 -0
- package/dist/components/FixedToolbar.svelte.d.ts.map +1 -1
- package/dist/components/FloatingHelper.svelte +219 -0
- package/dist/components/FloatingHelper.svelte.d.ts +14 -0
- package/dist/components/FloatingHelper.svelte.d.ts.map +1 -0
- package/dist/components/InputModal.svelte +7 -3
- package/dist/components/InputModal.svelte.d.ts +3 -0
- package/dist/components/InputModal.svelte.d.ts.map +1 -1
- package/dist/components/MathModal.svelte +33 -29
- package/dist/components/MathModal.svelte.d.ts +3 -0
- package/dist/components/MathModal.svelte.d.ts.map +1 -1
- package/dist/components/MediaPickerModal.svelte +7 -3
- package/dist/components/MediaPickerModal.svelte.d.ts +3 -0
- package/dist/components/MediaPickerModal.svelte.d.ts.map +1 -1
- package/dist/components/SlashCommandMenu.svelte +88 -72
- package/dist/components/SlashCommandMenu.svelte.d.ts +3 -0
- package/dist/components/SlashCommandMenu.svelte.d.ts.map +1 -1
- package/dist/components/TableBubbleMenu.svelte +41 -33
- package/dist/components/TableBubbleMenu.svelte.d.ts +3 -0
- package/dist/components/TableBubbleMenu.svelte.d.ts.map +1 -1
- package/dist/components/TipTapEditor.svelte +239 -127
- package/dist/components/TipTapEditor.svelte.d.ts +7 -0
- package/dist/components/TipTapEditor.svelte.d.ts.map +1 -1
- package/dist/extensions/CardBlock.d.ts.map +1 -1
- package/dist/extensions/CardBlock.js +6 -4
- package/dist/extensions/FileAttachment.d.ts.map +1 -1
- package/dist/extensions/FileAttachment.js +2 -1
- package/dist/extensions/FixedDetails.d.ts.map +1 -1
- package/dist/extensions/FixedDetails.js +5 -3
- package/dist/extensions/LegacyBlock.d.ts.map +1 -1
- package/dist/extensions/LegacyBlock.js +8 -6
- package/dist/extensions/Math.d.ts.map +1 -1
- package/dist/extensions/Math.js +5 -2
- package/dist/extensions/MbusVideo.d.ts.map +1 -1
- package/dist/extensions/MbusVideo.js +73 -9
- package/dist/extensions/MediaToolbar.d.ts +41 -0
- package/dist/extensions/MediaToolbar.d.ts.map +1 -0
- package/dist/extensions/MediaToolbar.js +196 -0
- package/dist/extensions/MediaToolbar.test.d.ts +2 -0
- package/dist/extensions/MediaToolbar.test.d.ts.map +1 -0
- package/dist/extensions/MediaToolbar.test.js +231 -0
- package/dist/extensions/OrderedListMarker.d.ts +2 -0
- package/dist/extensions/OrderedListMarker.d.ts.map +1 -0
- package/dist/extensions/OrderedListMarker.js +114 -0
- package/dist/extensions/OrderedListMarker.test.d.ts +2 -0
- package/dist/extensions/OrderedListMarker.test.d.ts.map +1 -0
- package/dist/extensions/OrderedListMarker.test.js +176 -0
- package/dist/extensions/PdfBlock.d.ts.map +1 -1
- package/dist/extensions/PdfBlock.js +25 -23
- package/dist/extensions/ResizableImage.d.ts.map +1 -1
- package/dist/extensions/ResizableImage.js +20 -1
- package/dist/extensions/TableKeymap.d.ts +16 -0
- package/dist/extensions/TableKeymap.d.ts.map +1 -0
- package/dist/extensions/TableKeymap.js +97 -0
- package/dist/extensions/TableKeymap.test.d.ts +2 -0
- package/dist/extensions/TableKeymap.test.d.ts.map +1 -0
- package/dist/extensions/TableKeymap.test.js +147 -0
- package/dist/extensions/TabsBlock.d.ts.map +1 -1
- package/dist/extensions/TabsBlock.js +12 -9
- package/dist/extensions/TiptapMidibus.d.ts.map +1 -1
- package/dist/extensions/TiptapMidibus.js +60 -6
- package/dist/extensions/UploadSkeleton.d.ts +61 -0
- package/dist/extensions/UploadSkeleton.d.ts.map +1 -0
- package/dist/extensions/UploadSkeleton.js +233 -0
- package/dist/extensions/UploadSkeleton.test.d.ts +2 -0
- package/dist/extensions/UploadSkeleton.test.d.ts.map +1 -0
- package/dist/extensions/UploadSkeleton.test.js +128 -0
- package/dist/extensions/VideoEmbed.d.ts +8 -0
- package/dist/extensions/VideoEmbed.d.ts.map +1 -1
- package/dist/extensions/VideoEmbed.js +226 -45
- package/dist/extensions/VideoEmbed.test.js +131 -2
- package/dist/i18n/en.d.ts +3 -0
- package/dist/i18n/en.d.ts.map +1 -0
- package/dist/i18n/en.js +191 -0
- package/dist/i18n/es.d.ts +3 -0
- package/dist/i18n/es.d.ts.map +1 -0
- package/dist/i18n/es.js +191 -0
- package/dist/i18n/index.d.ts +43 -0
- package/dist/i18n/index.d.ts.map +1 -0
- package/dist/i18n/index.js +109 -0
- package/dist/i18n/index.test.d.ts +2 -0
- package/dist/i18n/index.test.d.ts.map +1 -0
- package/dist/i18n/index.test.js +173 -0
- package/dist/i18n/ja.d.ts +3 -0
- package/dist/i18n/ja.d.ts.map +1 -0
- package/dist/i18n/ja.js +191 -0
- package/dist/i18n/ko.d.ts +198 -0
- package/dist/i18n/ko.d.ts.map +1 -0
- package/dist/i18n/ko.js +215 -0
- package/dist/i18n/zh-hans.d.ts +3 -0
- package/dist/i18n/zh-hans.d.ts.map +1 -0
- package/dist/i18n/zh-hans.js +191 -0
- package/dist/i18n/zh-hant.d.ts +3 -0
- package/dist/i18n/zh-hant.d.ts.map +1 -0
- package/dist/i18n/zh-hant.js +192 -0
- package/dist/index.d.ts +7 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13 -1
- package/dist/static/highlight.d.ts +13 -0
- package/dist/static/highlight.d.ts.map +1 -0
- package/dist/static/highlight.js +99 -0
- package/dist/static/index.d.ts +33 -0
- package/dist/static/index.d.ts.map +1 -0
- package/dist/static/index.js +367 -0
- package/dist/static/index.test.d.ts +2 -0
- package/dist/static/index.test.d.ts.map +1 -0
- package/dist/static/index.test.js +194 -0
- package/dist/static/tokenizer.d.ts +52 -0
- package/dist/static/tokenizer.d.ts.map +1 -0
- package/dist/static/tokenizer.js +289 -0
- package/dist/styles/editor.css +1 -1
- package/dist/utils/emoji-data.d.ts +15 -0
- package/dist/utils/emoji-suggestion.d.ts +48 -0
- package/dist/utils/emoji-suggestion.d.ts.map +1 -0
- package/dist/utils/emoji-suggestion.js +51 -0
- package/dist/utils/emoji-suggestion.test.d.ts +2 -0
- package/dist/utils/emoji-suggestion.test.d.ts.map +1 -0
- package/dist/utils/emoji-suggestion.test.js +117 -0
- package/dist/utils/floating-helper.d.ts +60 -0
- package/dist/utils/floating-helper.d.ts.map +1 -0
- package/dist/utils/floating-helper.js +63 -0
- package/dist/utils/floating-helper.test.d.ts +2 -0
- package/dist/utils/floating-helper.test.d.ts.map +1 -0
- package/dist/utils/floating-helper.test.js +141 -0
- package/dist/utils/guard-vscode-paste.d.ts +19 -0
- package/dist/utils/guard-vscode-paste.d.ts.map +1 -0
- package/dist/utils/guard-vscode-paste.js +46 -0
- package/dist/utils/guard-vscode-paste.test.d.ts +2 -0
- package/dist/utils/guard-vscode-paste.test.d.ts.map +1 -0
- package/dist/utils/guard-vscode-paste.test.js +86 -0
- package/dist/utils/media-size.d.ts +29 -0
- package/dist/utils/media-size.d.ts.map +1 -0
- package/dist/utils/media-size.js +90 -0
- package/dist/utils/resize.d.ts +12 -0
- package/dist/utils/resize.d.ts.map +1 -1
- package/dist/utils/resize.js +5 -5
- package/dist/utils/sanitize.d.ts +2 -0
- package/dist/utils/sanitize.d.ts.map +1 -1
- package/dist/utils/sanitize.js +28 -3
- package/package.json +8 -1
|
@@ -2,6 +2,7 @@ import { Node as TiptapNode, mergeAttributes } from "@tiptap/core";
|
|
|
2
2
|
import { NodeSelection } from "@tiptap/pm/state";
|
|
3
3
|
import { getPdfJs } from "../utils/pdf";
|
|
4
4
|
import { attachResize } from "../utils/resize";
|
|
5
|
+
import { getEditorTranslator } from "../i18n";
|
|
5
6
|
/**
|
|
6
7
|
* 방향키 → 쪽 넘김. 노드뷰가 쪽 상태를 쥐고 있으므로 **이벤트로 건넨다** —
|
|
7
8
|
* 확장이 노드뷰 내부를 직접 들여다보지 않게 하는 게 목적이다.
|
|
@@ -90,6 +91,7 @@ export const PdfBlock = TiptapNode.create({
|
|
|
90
91
|
},
|
|
91
92
|
addNodeView() {
|
|
92
93
|
return ({ node, getPos, editor }) => {
|
|
94
|
+
const t = getEditorTranslator(editor);
|
|
93
95
|
let destroyed = false;
|
|
94
96
|
let resizeObserver = null;
|
|
95
97
|
let resizeTimeout;
|
|
@@ -123,7 +125,7 @@ export const PdfBlock = TiptapNode.create({
|
|
|
123
125
|
*/
|
|
124
126
|
frame.tabIndex = 0;
|
|
125
127
|
frame.setAttribute("role", "group");
|
|
126
|
-
frame.setAttribute("aria-label", "
|
|
128
|
+
frame.setAttribute("aria-label", t("pdfViewerAria"));
|
|
127
129
|
dom.appendChild(frame);
|
|
128
130
|
// 리사이즈 핸들 (편집 가능 모드에서만)
|
|
129
131
|
if (editor.isEditable) {
|
|
@@ -133,7 +135,7 @@ export const PdfBlock = TiptapNode.create({
|
|
|
133
135
|
getPos,
|
|
134
136
|
getNode: () => currentNode,
|
|
135
137
|
axis: "x",
|
|
136
|
-
label: "
|
|
138
|
+
label: t("pdfResizeWidth"),
|
|
137
139
|
});
|
|
138
140
|
}
|
|
139
141
|
/*
|
|
@@ -213,10 +215,10 @@ export const PdfBlock = TiptapNode.create({
|
|
|
213
215
|
* 쉬운데 실제로는 페이지 원본 대비다 — 호버 설명에서 그걸 먼저 말한다.
|
|
214
216
|
*/
|
|
215
217
|
const ZOOM_TIPS = {
|
|
216
|
-
0.5: "
|
|
217
|
-
0.75: "
|
|
218
|
-
1: "
|
|
219
|
-
1.5: "
|
|
218
|
+
0.5: t("pdfZoom50"),
|
|
219
|
+
0.75: t("pdfZoom75"),
|
|
220
|
+
1: t("pdfZoom100"),
|
|
221
|
+
1.5: t("pdfZoom150"),
|
|
220
222
|
};
|
|
221
223
|
/** 저장값이 이것이면 창 높이에 맞춰 푼다. */
|
|
222
224
|
const FIT_SCREEN = "fit";
|
|
@@ -275,7 +277,7 @@ export const PdfBlock = TiptapNode.create({
|
|
|
275
277
|
const btn = document.createElement("button");
|
|
276
278
|
btn.type = "button";
|
|
277
279
|
btn.className = PRESET_IDLE;
|
|
278
|
-
btn.dataset.tip = ZOOM_TIPS[zoom] ??
|
|
280
|
+
btn.dataset.tip = ZOOM_TIPS[zoom] ?? t("pdfZoomOther", { label });
|
|
279
281
|
btn.setAttribute("aria-label", btn.dataset.tip);
|
|
280
282
|
btn.textContent = label;
|
|
281
283
|
// 원본 폭을 알기 전(로딩 중)에는 누를 수 없다 — 계산할 근거가 없다.
|
|
@@ -307,12 +309,12 @@ export const PdfBlock = TiptapNode.create({
|
|
|
307
309
|
const FITS = [
|
|
308
310
|
{
|
|
309
311
|
value: FIT_WIDTH,
|
|
310
|
-
tip: "
|
|
312
|
+
tip: t("pdfFitWidth"),
|
|
311
313
|
paths: '<polyline points="18 8 22 12 18 16"/><polyline points="6 8 2 12 6 16"/><line x1="2" x2="22" y1="12" y2="12"/>'
|
|
312
314
|
},
|
|
313
315
|
{
|
|
314
316
|
value: FIT_SCREEN,
|
|
315
|
-
tip: "
|
|
317
|
+
tip: t("pdfFitPage"),
|
|
316
318
|
paths: '<path d="M8 3H5a2 2 0 0 0-2 2v3"/><path d="M21 8V5a2 2 0 0 0-2-2h-3"/><path d="M3 16v3a2 2 0 0 0 2 2h3"/><path d="M16 21h3a2 2 0 0 0 2-2v-3"/>'
|
|
317
319
|
}
|
|
318
320
|
];
|
|
@@ -372,8 +374,8 @@ export const PdfBlock = TiptapNode.create({
|
|
|
372
374
|
downloadLink.rel = "noopener noreferrer";
|
|
373
375
|
downloadLink.setAttribute("download", cleanName(node.attrs.name));
|
|
374
376
|
downloadLink.className = "pdf-ctl";
|
|
375
|
-
downloadLink.dataset.tip = "
|
|
376
|
-
downloadLink.setAttribute("aria-label", "
|
|
377
|
+
downloadLink.dataset.tip = t("download");
|
|
378
|
+
downloadLink.setAttribute("aria-label", t("download"));
|
|
377
379
|
downloadLink.innerHTML = icon('<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/>');
|
|
378
380
|
downloadLink.addEventListener("click", async (e) => {
|
|
379
381
|
if (!downloadLink.href)
|
|
@@ -407,16 +409,16 @@ export const PdfBlock = TiptapNode.create({
|
|
|
407
409
|
openLink.target = "_blank";
|
|
408
410
|
openLink.rel = "noopener noreferrer";
|
|
409
411
|
openLink.className = "pdf-ctl";
|
|
410
|
-
openLink.dataset.tip = "
|
|
411
|
-
openLink.setAttribute("aria-label", "
|
|
412
|
+
openLink.dataset.tip = t("pdfOpenNewTabTip");
|
|
413
|
+
openLink.setAttribute("aria-label", t("pdfOpenNewTab"));
|
|
412
414
|
openLink.innerHTML = icon('<path d="M15 3h6v6"/><path d="M10 14 21 3"/><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/>');
|
|
413
415
|
endCluster.appendChild(openLink);
|
|
414
416
|
if (editor.isEditable) {
|
|
415
417
|
const deleteBtn = document.createElement("button");
|
|
416
418
|
deleteBtn.type = "button";
|
|
417
419
|
deleteBtn.className = "pdf-ctl pdf-ctl-danger";
|
|
418
|
-
deleteBtn.dataset.tip = "
|
|
419
|
-
deleteBtn.setAttribute("aria-label", "
|
|
420
|
+
deleteBtn.dataset.tip = t("delete");
|
|
421
|
+
deleteBtn.setAttribute("aria-label", t("delete"));
|
|
420
422
|
deleteBtn.innerHTML = icon('<path d="M3 6h18"/><path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6"/><path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2"/>');
|
|
421
423
|
deleteBtn.addEventListener("click", () => {
|
|
422
424
|
const pos = getPos();
|
|
@@ -433,7 +435,7 @@ export const PdfBlock = TiptapNode.create({
|
|
|
433
435
|
frame.appendChild(canvas);
|
|
434
436
|
const statusDiv = document.createElement("div");
|
|
435
437
|
statusDiv.className = "pdf-status";
|
|
436
|
-
statusDiv.textContent = "
|
|
438
|
+
statusDiv.textContent = t("pdfLoading");
|
|
437
439
|
frame.appendChild(statusDiv);
|
|
438
440
|
canvas.style.display = "none";
|
|
439
441
|
// \uC870\uC791\uBD80\uB294 \uB9C8\uC9C0\uB9C9 \u2014 `position:absolute` \uB77C \uC21C\uC11C\uC640 \uBB34\uAD00\uD558\uAC8C \uC704\uC5D0 \uADF8\uB824\uC9C0\uC9C0\uB9CC,
|
|
@@ -443,14 +445,14 @@ export const PdfBlock = TiptapNode.create({
|
|
|
443
445
|
const prevBtn = document.createElement("button");
|
|
444
446
|
prevBtn.type = "button";
|
|
445
447
|
prevBtn.className = "pdf-ctl pdf-nav pdf-nav-prev pdf-cluster";
|
|
446
|
-
prevBtn.title = "
|
|
447
|
-
prevBtn.setAttribute("aria-label", "
|
|
448
|
+
prevBtn.title = t("pdfPrevPage");
|
|
449
|
+
prevBtn.setAttribute("aria-label", t("pdfPrevPage"));
|
|
448
450
|
prevBtn.innerHTML = icon('<path d="m15 18-6-6 6-6"/>');
|
|
449
451
|
const nextBtn = document.createElement("button");
|
|
450
452
|
nextBtn.type = "button";
|
|
451
453
|
nextBtn.className = "pdf-ctl pdf-nav pdf-nav-next pdf-cluster";
|
|
452
|
-
nextBtn.title = "
|
|
453
|
-
nextBtn.setAttribute("aria-label", "
|
|
454
|
+
nextBtn.title = t("pdfNextPage");
|
|
455
|
+
nextBtn.setAttribute("aria-label", t("pdfNextPage"));
|
|
454
456
|
nextBtn.innerHTML = icon('<path d="m9 18 6-6-6-6"/>');
|
|
455
457
|
/*
|
|
456
458
|
* \uD310\uC774 \uD654\uBA74\uBCF4\uB2E4 \uAE38 \uB54C \uC77D\uAE30 \uC870\uC791\uBD80\uAC00 \uB530\uB77C\uC624\uAC8C \uD558\uB294 \uB808\uC77C. `position: sticky` \uB77C
|
|
@@ -464,7 +466,7 @@ export const PdfBlock = TiptapNode.create({
|
|
|
464
466
|
const pageBtn = document.createElement("button");
|
|
465
467
|
pageBtn.type = "button";
|
|
466
468
|
pageBtn.className = "pdf-page";
|
|
467
|
-
pageBtn.title = "
|
|
469
|
+
pageBtn.title = t("pdfGoToPage");
|
|
468
470
|
pageCluster.appendChild(pageBtn);
|
|
469
471
|
/*
|
|
470
472
|
* \uCABD\uC218\uB97C \uB20C\uB7EC **\uBC88\uD638\uB85C \uAC74\uB108\uB6F4\uB2E4.** \uC774\uAC8C \uC5C6\uC73C\uBA74 30\uCABD\uC9DC\uB9AC \uC790\uB8CC\uC5D0\uC11C \uB4A4\uB85C \uAC00\uB824\uBA74
|
|
@@ -474,7 +476,7 @@ export const PdfBlock = TiptapNode.create({
|
|
|
474
476
|
pageInput.type = "text";
|
|
475
477
|
pageInput.inputMode = "numeric";
|
|
476
478
|
pageInput.className = "pdf-page-input";
|
|
477
|
-
pageInput.setAttribute("aria-label", "
|
|
479
|
+
pageInput.setAttribute("aria-label", t("pdfPageNumber"));
|
|
478
480
|
pageInput.style.display = "none";
|
|
479
481
|
pageCluster.appendChild(pageInput);
|
|
480
482
|
let currentPage = 1;
|
|
@@ -707,7 +709,7 @@ export const PdfBlock = TiptapNode.create({
|
|
|
707
709
|
resizeObserver.observe(dom);
|
|
708
710
|
}
|
|
709
711
|
catch {
|
|
710
|
-
statusDiv.textContent = "
|
|
712
|
+
statusDiv.textContent = t("pdfLoadFailed");
|
|
711
713
|
}
|
|
712
714
|
}
|
|
713
715
|
// URL 해결 후 PDF 로드
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ResizableImage.d.ts","sourceRoot":"","sources":["../../src/extensions/ResizableImage.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ResizableImage.d.ts","sourceRoot":"","sources":["../../src/extensions/ResizableImage.ts"],"names":[],"mappings":"AAuCA;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAYjE;AAED,eAAO,MAAM,cAAc,kFA2GzB,CAAC;AAEH,eAAe,cAAc,CAAC"}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import Image from "@tiptap/extension-image";
|
|
2
2
|
import { attachResize } from "../utils/resize";
|
|
3
|
+
import { normalizeMediaAlign } from "../utils/media-size";
|
|
4
|
+
import { getEditorTranslator } from "../i18n";
|
|
3
5
|
/**
|
|
4
6
|
* 드래그로 폭을 조절할 수 있는 이미지.
|
|
5
7
|
*
|
|
@@ -74,6 +76,19 @@ export const ResizableImage = Image.extend({
|
|
|
74
76
|
const w = normalizeImageWidth(attributes.width);
|
|
75
77
|
return w == null ? {} : { width: String(w) };
|
|
76
78
|
}
|
|
79
|
+
},
|
|
80
|
+
/*
|
|
81
|
+
* 좌/중/우 정렬(미디어 툴바). `width` 와 같은 이유로 `style` 이 아니라 **속성**
|
|
82
|
+
* (`data-align`)으로 간다 — 살균기의 `img` 허용 목록에 이 이름을 넣어 두었고,
|
|
83
|
+
* 읽기 화면에서는 `editor.css` 의 `.tiptap img[data-align=…]` 규칙이 받는다.
|
|
84
|
+
*/
|
|
85
|
+
align: {
|
|
86
|
+
default: null,
|
|
87
|
+
parseHTML: (element) => normalizeMediaAlign(element.getAttribute("data-align")),
|
|
88
|
+
renderHTML: (attributes) => {
|
|
89
|
+
const align = normalizeMediaAlign(attributes.align);
|
|
90
|
+
return align ? { "data-align": align } : {};
|
|
91
|
+
}
|
|
77
92
|
}
|
|
78
93
|
};
|
|
79
94
|
},
|
|
@@ -108,6 +123,10 @@ export const ResizableImage = Image.extend({
|
|
|
108
123
|
dom.style.removeProperty("width");
|
|
109
124
|
else
|
|
110
125
|
dom.style.width = `${w}px`;
|
|
126
|
+
/* 정렬은 래퍼의 마진으로 — `width: fit-content` 라 `auto` 마진이 그대로 먹는다. */
|
|
127
|
+
const align = normalizeMediaAlign(attrs.align);
|
|
128
|
+
dom.style.marginLeft = align === "center" || align === "right" ? "auto" : "";
|
|
129
|
+
dom.style.marginRight = align === "center" ? "auto" : "";
|
|
111
130
|
};
|
|
112
131
|
applyAttrs(node.attrs);
|
|
113
132
|
if (editor.isEditable) {
|
|
@@ -119,7 +138,7 @@ export const ResizableImage = Image.extend({
|
|
|
119
138
|
axis: "x",
|
|
120
139
|
attr: "width",
|
|
121
140
|
min: MIN_WIDTH,
|
|
122
|
-
label: "
|
|
141
|
+
label: getEditorTranslator(editor)("imageResizeWidth"),
|
|
123
142
|
// 저장 형식은 단위 없는 숫자 — 살균기가 `style` 을 지우므로 `width` 속성으로 간다.
|
|
124
143
|
format: (v) => String(Math.round(v))
|
|
125
144
|
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Extension } from "@tiptap/core";
|
|
2
|
+
/**
|
|
3
|
+
* 표 키보드 동작: `Tab` 다음 셀(마지막 셀이면 행을 만들어 이동), `Shift-Tab` 이전 셀,
|
|
4
|
+
* `Backspace`/`Delete` 로 빈 표·선택된 열/행/표 삭제. (main 정올
|
|
5
|
+
* `plugin/table/index.ts` 의 `addKeyboardShortcuts` 참조 — 로직 재작성.)
|
|
6
|
+
*
|
|
7
|
+
* ⚠️ **업스트림 `Table` 에도 같은 Tab 단축키가 있는데 왜 또 다는가** — TipTap 은 키맵
|
|
8
|
+
* 플러그인을 **등록 역순 + priority 내림차순**으로 배치한다(core 의
|
|
9
|
+
* `sortExtensions([...this.extensions].reverse())`). `TipTapEditor.svelte` 에서 `Indent` 가
|
|
10
|
+
* 표보다 **뒤에** 등록돼 있어, 기본 priority 로는 표 안에서도 Indent 의 Tab(NBSP 삽입)이
|
|
11
|
+
* 먼저 먹어 셀 이동이 죽는다. 그래서 여기만 priority 를 올려 표 안에서는 셀 이동이
|
|
12
|
+
* 이기게 한다. 표 밖에서는 모든 핸들러가 false 를 돌려주므로 Indent·리스트 들여쓰기는
|
|
13
|
+
* 그대로다.
|
|
14
|
+
*/
|
|
15
|
+
export declare const TableKeymap: Extension<any, any>;
|
|
16
|
+
//# sourceMappingURL=TableKeymap.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TableKeymap.d.ts","sourceRoot":"","sources":["../../src/extensions/TableKeymap.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAuDzC;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,WAAW,qBAwBtB,CAAC"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { Extension } from "@tiptap/core";
|
|
2
|
+
import { CellSelection } from "@tiptap/pm/tables";
|
|
3
|
+
/**
|
|
4
|
+
* 표 안에 실제 내용(글자·이미지·수식 같은 리프 노드)이 하나도 없는가.
|
|
5
|
+
* 행·셀·빈 문단 같은 구조물만 있으면 빈 표로 본다.
|
|
6
|
+
*/
|
|
7
|
+
function isTableEmpty(table) {
|
|
8
|
+
let empty = true;
|
|
9
|
+
table.descendants((node) => {
|
|
10
|
+
if (!empty)
|
|
11
|
+
return false;
|
|
12
|
+
if (node.isText || node.isLeaf) {
|
|
13
|
+
empty = false;
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
return true;
|
|
17
|
+
});
|
|
18
|
+
return empty;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Backspace/Delete 로 표를 정리한다. (main 정올 `plugin/table/deleteTable.ts` 참조 —
|
|
22
|
+
* 로직 재작성. 열·행 판정은 TableMap 순회 대신 `CellSelection` 의 자체 판정을 쓴다.)
|
|
23
|
+
*
|
|
24
|
+
* · 셀 선택(CellSelection)이 표 전체를 덮으면 → 표 삭제, 열 전체 → 열 삭제, 행 전체 → 행 삭제
|
|
25
|
+
* · 커서(TextSelection)가 **빈 표** 안에 있으면 → 표를 통째로 삭제
|
|
26
|
+
*
|
|
27
|
+
* 그 밖에는 전부 false — 셀 안의 글자 지우기 같은 기본 동작에 끼어들지 않는다.
|
|
28
|
+
*/
|
|
29
|
+
const deleteTableOrSelection = ({ editor }) => {
|
|
30
|
+
if (!editor.isEditable)
|
|
31
|
+
return false;
|
|
32
|
+
const { selection } = editor.state;
|
|
33
|
+
if (selection instanceof CellSelection) {
|
|
34
|
+
const col = selection.isColSelection();
|
|
35
|
+
const row = selection.isRowSelection();
|
|
36
|
+
if (col && row)
|
|
37
|
+
return editor.commands.deleteTable();
|
|
38
|
+
if (col)
|
|
39
|
+
return editor.commands.deleteColumn();
|
|
40
|
+
if (row)
|
|
41
|
+
return editor.commands.deleteRow();
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
if (!selection.empty)
|
|
45
|
+
return false;
|
|
46
|
+
const { $from } = selection;
|
|
47
|
+
for (let d = $from.depth; d > 0; d--) {
|
|
48
|
+
const node = $from.node(d);
|
|
49
|
+
if (node.type.name !== "table")
|
|
50
|
+
continue;
|
|
51
|
+
if (!isTableEmpty(node))
|
|
52
|
+
return false;
|
|
53
|
+
return editor.commands.deleteTable();
|
|
54
|
+
}
|
|
55
|
+
return false;
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* 표 키보드 동작: `Tab` 다음 셀(마지막 셀이면 행을 만들어 이동), `Shift-Tab` 이전 셀,
|
|
59
|
+
* `Backspace`/`Delete` 로 빈 표·선택된 열/행/표 삭제. (main 정올
|
|
60
|
+
* `plugin/table/index.ts` 의 `addKeyboardShortcuts` 참조 — 로직 재작성.)
|
|
61
|
+
*
|
|
62
|
+
* ⚠️ **업스트림 `Table` 에도 같은 Tab 단축키가 있는데 왜 또 다는가** — TipTap 은 키맵
|
|
63
|
+
* 플러그인을 **등록 역순 + priority 내림차순**으로 배치한다(core 의
|
|
64
|
+
* `sortExtensions([...this.extensions].reverse())`). `TipTapEditor.svelte` 에서 `Indent` 가
|
|
65
|
+
* 표보다 **뒤에** 등록돼 있어, 기본 priority 로는 표 안에서도 Indent 의 Tab(NBSP 삽입)이
|
|
66
|
+
* 먼저 먹어 셀 이동이 죽는다. 그래서 여기만 priority 를 올려 표 안에서는 셀 이동이
|
|
67
|
+
* 이기게 한다. 표 밖에서는 모든 핸들러가 false 를 돌려주므로 Indent·리스트 들여쓰기는
|
|
68
|
+
* 그대로다.
|
|
69
|
+
*/
|
|
70
|
+
export const TableKeymap = Extension.create({
|
|
71
|
+
name: "tableKeymap",
|
|
72
|
+
priority: 110,
|
|
73
|
+
addKeyboardShortcuts() {
|
|
74
|
+
return {
|
|
75
|
+
Tab: () => {
|
|
76
|
+
/* 읽기 전용에서도 keydown 은 들어온다 — 행 추가까지 가지 않게 막는다. */
|
|
77
|
+
if (!this.editor.isEditable)
|
|
78
|
+
return false;
|
|
79
|
+
if (this.editor.commands.goToNextCell())
|
|
80
|
+
return true;
|
|
81
|
+
/* 마지막 셀: 행을 하나 만들어 이어서 이동. 표 밖에서는 둘 다 실패해 false. */
|
|
82
|
+
if (!this.editor.can().addRowAfter())
|
|
83
|
+
return false;
|
|
84
|
+
return this.editor.chain().addRowAfter().goToNextCell().run();
|
|
85
|
+
},
|
|
86
|
+
"Shift-Tab": () => {
|
|
87
|
+
if (!this.editor.isEditable)
|
|
88
|
+
return false;
|
|
89
|
+
return this.editor.commands.goToPreviousCell();
|
|
90
|
+
},
|
|
91
|
+
Backspace: deleteTableOrSelection,
|
|
92
|
+
"Mod-Backspace": deleteTableOrSelection,
|
|
93
|
+
Delete: deleteTableOrSelection,
|
|
94
|
+
"Mod-Delete": deleteTableOrSelection
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TableKeymap.test.d.ts","sourceRoot":"","sources":["../../src/extensions/TableKeymap.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { describe, it, expect, afterEach } from "vitest";
|
|
2
|
+
import { Editor } from "@tiptap/core";
|
|
3
|
+
import StarterKit from "@tiptap/starter-kit";
|
|
4
|
+
import { Table } from "@tiptap/extension-table";
|
|
5
|
+
import { TableRow } from "@tiptap/extension-table-row";
|
|
6
|
+
import { TableHeader } from "@tiptap/extension-table-header";
|
|
7
|
+
import { TableCell } from "@tiptap/extension-table-cell";
|
|
8
|
+
import { CellSelection } from "@tiptap/pm/tables";
|
|
9
|
+
import { TableKeymap } from "./TableKeymap";
|
|
10
|
+
import { Indent } from "./Indent";
|
|
11
|
+
const NBSP = "\u00a0";
|
|
12
|
+
describe("TableKeymap", () => {
|
|
13
|
+
let editor;
|
|
14
|
+
/*
|
|
15
|
+
* 등록 순서를 `TipTapEditor.svelte` 와 같게 둔다 — 표 계열 뒤에 `Indent`.
|
|
16
|
+
* TipTap 키맵은 등록 역순이라 기본 priority 면 Indent 의 Tab 이 표를 가린다.
|
|
17
|
+
* 이 테스트 절반은 그 순서 문제(priority 110)가 무너지지 않는지 잡는 것이다.
|
|
18
|
+
*/
|
|
19
|
+
function createEditor(content) {
|
|
20
|
+
editor = new Editor({
|
|
21
|
+
element: document.createElement("div"),
|
|
22
|
+
extensions: [StarterKit, Table, TableRow, TableHeader, TableCell, TableKeymap, Indent],
|
|
23
|
+
content
|
|
24
|
+
});
|
|
25
|
+
return editor;
|
|
26
|
+
}
|
|
27
|
+
afterEach(() => {
|
|
28
|
+
editor?.destroy();
|
|
29
|
+
});
|
|
30
|
+
function press(key, init = {}) {
|
|
31
|
+
const event = new KeyboardEvent("keydown", { key, ...init });
|
|
32
|
+
return editor.view.someProp("handleKeyDown", (f) => f(editor.view, event)) ?? false;
|
|
33
|
+
}
|
|
34
|
+
/** 본문 텍스트 `text` 가 시작하는 문서 위치. */
|
|
35
|
+
function posOf(text) {
|
|
36
|
+
let pos = -1;
|
|
37
|
+
editor.state.doc.descendants((node, p) => {
|
|
38
|
+
if (pos !== -1)
|
|
39
|
+
return false;
|
|
40
|
+
if (node.isText && node.text?.includes(text))
|
|
41
|
+
pos = p;
|
|
42
|
+
return pos === -1;
|
|
43
|
+
});
|
|
44
|
+
if (pos === -1)
|
|
45
|
+
throw new Error(`"${text}" not found`);
|
|
46
|
+
return pos;
|
|
47
|
+
}
|
|
48
|
+
/** 첫 셀(tableCell/tableHeader) 노드의 문서 위치. */
|
|
49
|
+
function cellPositions() {
|
|
50
|
+
const cells = [];
|
|
51
|
+
editor.state.doc.descendants((node, p) => {
|
|
52
|
+
if (node.type.name === "tableCell" || node.type.name === "tableHeader")
|
|
53
|
+
cells.push(p);
|
|
54
|
+
return true;
|
|
55
|
+
});
|
|
56
|
+
return cells;
|
|
57
|
+
}
|
|
58
|
+
function hasTable() {
|
|
59
|
+
let found = false;
|
|
60
|
+
editor.state.doc.descendants((node) => {
|
|
61
|
+
if (node.type.name === "table")
|
|
62
|
+
found = true;
|
|
63
|
+
return !found;
|
|
64
|
+
});
|
|
65
|
+
return found;
|
|
66
|
+
}
|
|
67
|
+
function rowCount() {
|
|
68
|
+
let rows = 0;
|
|
69
|
+
editor.state.doc.descendants((node) => {
|
|
70
|
+
if (node.type.name === "tableRow")
|
|
71
|
+
rows += 1;
|
|
72
|
+
return true;
|
|
73
|
+
});
|
|
74
|
+
return rows;
|
|
75
|
+
}
|
|
76
|
+
const TWO_CELLS = "<table><tbody><tr><td><p>a</p></td><td><p>b</p></td></tr></tbody></table>";
|
|
77
|
+
const EMPTY_TABLE = "<table><tbody><tr><td><p></p></td><td><p></p></td></tr></tbody></table><p>after</p>";
|
|
78
|
+
it("Tab 은 다음 셀로 이동한다 — Indent 의 NBSP 삽입이 가로채지 않는다", () => {
|
|
79
|
+
createEditor(TWO_CELLS);
|
|
80
|
+
editor.commands.setTextSelection(posOf("a") + 1);
|
|
81
|
+
expect(press("Tab")).toBe(true);
|
|
82
|
+
expect(editor.state.selection.$from.parent.textContent).toBe("b");
|
|
83
|
+
expect(editor.getText()).not.toContain(NBSP);
|
|
84
|
+
});
|
|
85
|
+
it("마지막 셀에서 Tab 은 행을 만들어 이동한다", () => {
|
|
86
|
+
createEditor(TWO_CELLS);
|
|
87
|
+
editor.commands.setTextSelection(posOf("b") + 1);
|
|
88
|
+
expect(rowCount()).toBe(1);
|
|
89
|
+
expect(press("Tab")).toBe(true);
|
|
90
|
+
expect(rowCount()).toBe(2);
|
|
91
|
+
/* 새 행의 첫 셀(빈 문단)에 있어야 한다. */
|
|
92
|
+
expect(editor.state.selection.$from.parent.textContent).toBe("");
|
|
93
|
+
});
|
|
94
|
+
it("Shift-Tab 은 이전 셀로 이동한다", () => {
|
|
95
|
+
createEditor(TWO_CELLS);
|
|
96
|
+
editor.commands.setTextSelection(posOf("b") + 1);
|
|
97
|
+
expect(press("Tab", { shiftKey: true })).toBe(true);
|
|
98
|
+
expect(editor.state.selection.$from.parent.textContent).toBe("a");
|
|
99
|
+
});
|
|
100
|
+
it("표 밖 Tab 은 손대지 않는다 — Indent 가 그대로 동작한다", () => {
|
|
101
|
+
createEditor("<p>outside</p>");
|
|
102
|
+
editor.commands.setTextSelection(posOf("outside") + 1);
|
|
103
|
+
expect(press("Tab")).toBe(true);
|
|
104
|
+
expect(editor.getText()).toContain(NBSP.repeat(4));
|
|
105
|
+
});
|
|
106
|
+
it("빈 표 안에서 Backspace 는 표를 통째로 지운다", () => {
|
|
107
|
+
createEditor(EMPTY_TABLE);
|
|
108
|
+
/* 첫 셀의 빈 문단 안(셀 위치 +2)에 커서를 둔다. */
|
|
109
|
+
editor.commands.setTextSelection(cellPositions()[0] + 2);
|
|
110
|
+
expect(press("Backspace")).toBe(true);
|
|
111
|
+
expect(hasTable()).toBe(false);
|
|
112
|
+
expect(editor.getText()).toContain("after");
|
|
113
|
+
});
|
|
114
|
+
it("빈 표 안에서 Delete 도 표를 지운다", () => {
|
|
115
|
+
createEditor(EMPTY_TABLE);
|
|
116
|
+
editor.commands.setTextSelection(cellPositions()[0] + 2);
|
|
117
|
+
expect(press("Delete")).toBe(true);
|
|
118
|
+
expect(hasTable()).toBe(false);
|
|
119
|
+
});
|
|
120
|
+
it("내용이 있는 표에서는 Backspace 로 표를 지우지 않는다", () => {
|
|
121
|
+
createEditor(TWO_CELLS);
|
|
122
|
+
editor.commands.setTextSelection(posOf("a"));
|
|
123
|
+
press("Backspace");
|
|
124
|
+
expect(hasTable()).toBe(true);
|
|
125
|
+
expect(editor.getText()).toContain("a");
|
|
126
|
+
});
|
|
127
|
+
it("표 전체 셀 선택 + Backspace 는 표를 지운다", () => {
|
|
128
|
+
createEditor(TWO_CELLS);
|
|
129
|
+
const cells = cellPositions();
|
|
130
|
+
const sel = CellSelection.create(editor.state.doc, cells[0], cells[cells.length - 1]);
|
|
131
|
+
editor.view.dispatch(editor.state.tr.setSelection(sel));
|
|
132
|
+
expect(press("Backspace")).toBe(true);
|
|
133
|
+
expect(hasTable()).toBe(false);
|
|
134
|
+
});
|
|
135
|
+
it("열 전체 선택 + Backspace 는 그 열만 지운다", () => {
|
|
136
|
+
createEditor("<table><tbody>" +
|
|
137
|
+
"<tr><td><p>a</p></td><td><p>b</p></td></tr>" +
|
|
138
|
+
"<tr><td><p>c</p></td><td><p>d</p></td></tr>" +
|
|
139
|
+
"</tbody></table>");
|
|
140
|
+
const sel = CellSelection.colSelection(editor.state.doc.resolve(cellPositions()[0]));
|
|
141
|
+
editor.view.dispatch(editor.state.tr.setSelection(sel));
|
|
142
|
+
expect(press("Backspace")).toBe(true);
|
|
143
|
+
expect(hasTable()).toBe(true);
|
|
144
|
+
expect(editor.getText()).not.toContain("a");
|
|
145
|
+
expect(editor.getText()).toContain("b");
|
|
146
|
+
});
|
|
147
|
+
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TabsBlock.d.ts","sourceRoot":"","sources":["../../src/extensions/TabsBlock.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAmB,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"TabsBlock.d.ts","sourceRoot":"","sources":["../../src/extensions/TabsBlock.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAmB,MAAM,cAAc,CAAC;AA6DrD;;;;;GAKG;AACH,eAAO,MAAM,GAAG,gBAqDd,CAAC;AAEH,eAAO,MAAM,SAAS,gBAmnBpB,CAAC;AAEH,OAAO,QAAQ,cAAc,CAAC;IAC7B,UAAU,QAAQ,CAAC,UAAU;QAC5B,IAAI,EAAE;YACL,qCAAqC;YACrC,OAAO,EAAE,CAAC,KAAK,CAAC,EAAE,MAAM,KAAK,UAAU,CAAC;SACxC,CAAC;KACF;CACD"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Node, mergeAttributes } from "@tiptap/core";
|
|
2
|
+
import { getEditorTranslator } from "../i18n";
|
|
2
3
|
import { Selection } from "@tiptap/pm/state";
|
|
3
4
|
/**
|
|
4
5
|
* 탭 블록 — 한 상자 안의 내용을 **탭으로 나눠** 한 번에 하나만 보여준다(노션의 그것).
|
|
@@ -33,7 +34,7 @@ import { Selection } from "@tiptap/pm/state";
|
|
|
33
34
|
* (`editor.css` 의 `:not(.hce-tabs-live)` 규칙). 그래야 에디터가 서기 전후로 높이가 튀지 않는다.
|
|
34
35
|
*/
|
|
35
36
|
/** 이름을 따로 주지 않은 탭이 보여줄 기본 이름. */
|
|
36
|
-
const defaultTitle = (index) =>
|
|
37
|
+
const defaultTitle = (t, index) => t("tabDefaultTitle", { n: index + 1 });
|
|
37
38
|
/**
|
|
38
39
|
* 여기까지는 **클릭이다.** 손가락·마우스는 누를 때 몇 px 씩 흔들리므로, 이만큼 넘어야
|
|
39
40
|
* "끌었다"로 본다. 넘지 않고 떼면 평소대로 탭 전환이 되고 두 번 누르면 이름 고치기가 뜬다.
|
|
@@ -120,6 +121,7 @@ export const TabsBlock = Node.create({
|
|
|
120
121
|
},
|
|
121
122
|
addNodeView() {
|
|
122
123
|
return ({ node, editor, getPos }) => {
|
|
124
|
+
const t = getEditorTranslator(editor);
|
|
123
125
|
let currentNode = node;
|
|
124
126
|
/** 지금 보고 있는 탭. 문서에 저장하지 않는다(파일 첫머리 주석 참고). */
|
|
125
127
|
let active = 0;
|
|
@@ -170,7 +172,7 @@ export const TabsBlock = Node.create({
|
|
|
170
172
|
};
|
|
171
173
|
const titleOf = (index) => {
|
|
172
174
|
const raw = String(currentNode.child(index).attrs.title ?? "").trim();
|
|
173
|
-
return raw || defaultTitle(index);
|
|
175
|
+
return raw || defaultTitle(t, index);
|
|
174
176
|
};
|
|
175
177
|
/** `index` 번째 탭 노드의 문서상 위치. `tabs` 가 문서에서 빠졌으면 null. */
|
|
176
178
|
const childPos = (index) => {
|
|
@@ -287,7 +289,7 @@ export const TabsBlock = Node.create({
|
|
|
287
289
|
return;
|
|
288
290
|
const index = currentNode.childCount;
|
|
289
291
|
// `content: "block+"` 이라 `createAndFill` 이 빈 문단 하나를 채워 준다.
|
|
290
|
-
const created = type.createAndFill({ title: defaultTitle(index) });
|
|
292
|
+
const created = type.createAndFill({ title: defaultTitle(t, index) });
|
|
291
293
|
if (!created)
|
|
292
294
|
return;
|
|
293
295
|
active = index;
|
|
@@ -557,7 +559,7 @@ export const TabsBlock = Node.create({
|
|
|
557
559
|
setActive(index);
|
|
558
560
|
});
|
|
559
561
|
if (editable) {
|
|
560
|
-
label.title = "
|
|
562
|
+
label.title = t("tabRenameTip");
|
|
561
563
|
label.addEventListener("dblclick", (e) => {
|
|
562
564
|
e.preventDefault();
|
|
563
565
|
e.stopPropagation();
|
|
@@ -581,7 +583,7 @@ export const TabsBlock = Node.create({
|
|
|
581
583
|
close.className = deletable
|
|
582
584
|
? "hce-tab-chip-close"
|
|
583
585
|
: "hce-tab-chip-close is-disabled";
|
|
584
|
-
close.setAttribute("aria-label",
|
|
586
|
+
close.setAttribute("aria-label", t("tabDelete", { title: titleOf(index) }));
|
|
585
587
|
close.textContent = "×";
|
|
586
588
|
if (deletable) {
|
|
587
589
|
close.addEventListener("mousedown", (e) => e.preventDefault());
|
|
@@ -604,8 +606,8 @@ export const TabsBlock = Node.create({
|
|
|
604
606
|
const add = document.createElement("button");
|
|
605
607
|
add.type = "button";
|
|
606
608
|
add.className = "hce-tabs-add";
|
|
607
|
-
add.setAttribute("aria-label", "
|
|
608
|
-
add.title = "
|
|
609
|
+
add.setAttribute("aria-label", t("tabAdd"));
|
|
610
|
+
add.title = t("tabAdd");
|
|
609
611
|
add.textContent = "+";
|
|
610
612
|
add.addEventListener("mousedown", (e) => e.preventDefault());
|
|
611
613
|
add.addEventListener("click", (e) => {
|
|
@@ -669,14 +671,15 @@ export const TabsBlock = Node.create({
|
|
|
669
671
|
},
|
|
670
672
|
addCommands() {
|
|
671
673
|
return {
|
|
672
|
-
setTabs: (count = 3) => ({ chain }) => {
|
|
674
|
+
setTabs: (count = 3) => ({ chain, editor }) => {
|
|
675
|
+
const t = getEditorTranslator(editor);
|
|
673
676
|
const total = Math.max(1, Math.floor(count));
|
|
674
677
|
return (chain()
|
|
675
678
|
.insertContent({
|
|
676
679
|
type: this.name,
|
|
677
680
|
content: Array.from({ length: total }, (_unused, i) => ({
|
|
678
681
|
type: "tab",
|
|
679
|
-
attrs: { title: defaultTitle(i) },
|
|
682
|
+
attrs: { title: defaultTitle(t, i) },
|
|
680
683
|
content: [{ type: "paragraph" }]
|
|
681
684
|
}))
|
|
682
685
|
})
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TiptapMidibus.d.ts","sourceRoot":"","sources":["../../src/extensions/TiptapMidibus.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,OAAO,EAAE,IAAI,EAAmB,KAAK,MAAM,EAAE,MAAM,cAAc,CAAC;AAClE,OAAO,KAAK,EAAE,IAAI,IAAI,eAAe,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"TiptapMidibus.d.ts","sourceRoot":"","sources":["../../src/extensions/TiptapMidibus.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,OAAO,EAAE,IAAI,EAAmB,KAAK,MAAM,EAAE,MAAM,cAAc,CAAC;AAClE,OAAO,KAAK,EAAE,IAAI,IAAI,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAKhE,8EAA8E;AAC9E,MAAM,WAAW,oBAAoB;IACpC,oCAAoC;IACpC,OAAO,EAAE,WAAW,CAAC;IACrB,IAAI,EAAE,eAAe,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;CACf;AAED;;;GAGG;AACH,MAAM,MAAM,mBAAmB,GAAG,CAAC,MAAM,IAAI,CAAC,GAAG,IAAI,GAAG,KAAK,CAAC;AAC9D,MAAM,MAAM,eAAe,GAAG,CAAC,OAAO,EAAE,oBAAoB,KAAK,mBAAmB,CAAC;AAErF,MAAM,WAAW,oBAAoB;IACpC,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACxC,0CAA0C;IAC1C,aAAa,EAAE,MAAM,CAAC;IACtB;;;;;OAKG;IACH,QAAQ,EAAE,eAAe,GAAG,IAAI,CAAC;CACjC;AAED,OAAO,QAAQ,cAAc,CAAC;IAC7B,UAAU,QAAQ,CAAC,UAAU;QAC5B,aAAa,EAAE;YACd,gBAAgB,EAAE,CAAC,KAAK,EAAE;gBACzB,EAAE,EAAE,MAAM,CAAC;gBACX,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;gBACxB,IAAI,CAAC,EAAE,MAAM,CAAC;gBACd,KAAK,CAAC,EAAE,MAAM,CAAC;gBACf,MAAM,CAAC,EAAE,MAAM,CAAC;aAChB,KAAK,UAAU,CAAC;SACjB,CAAC;KACF;CACD;AAED,8CAA8C;AAC9C,MAAM,MAAM,eAAe,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAiBrD,eAAO,MAAM,aAAa,iCAoMxB,CAAC"}
|