fable-editor 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.
- package/LICENSE +1 -1
- package/README.md +1 -1
- package/dist/angular/LICENSE +1 -1
- package/dist/angular/README.md +1 -1
- package/dist/angular/core/editor.d.ts +16 -0
- package/dist/angular/core/i18n.d.ts +6 -0
- package/dist/angular/esm2022/core/config.mjs +2 -2
- package/dist/angular/esm2022/core/editor.mjs +194 -6
- package/dist/angular/esm2022/core/i18n.mjs +15 -3
- package/dist/angular/esm2022/core/icons.mjs +6 -1
- package/dist/angular/fesm2022/fable-editor.mjs +213 -8
- package/dist/angular/fesm2022/fable-editor.mjs.map +1 -1
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/editor.css +142 -0
- package/dist/core/editor.d.ts +16 -0
- package/dist/core/editor.d.ts.map +1 -1
- package/dist/core/i18n.d.ts +6 -0
- package/dist/core/i18n.d.ts.map +1 -1
- package/dist/core/icons.d.ts.map +1 -1
- package/dist/core/index.cjs +3 -3
- package/dist/core/index.mjs +282 -174
- package/dist/react/index.cjs +12 -12
- package/dist/react/index.mjs +411 -306
- package/package.json +10 -2
- package/style.css +142 -0
|
@@ -136,7 +136,13 @@ const I18N = {
|
|
|
136
136
|
insertimg: 'Insert',
|
|
137
137
|
dropimage: 'Drop image here',
|
|
138
138
|
uploading: 'Uploading…',
|
|
139
|
-
uploadfailed: 'Upload failed — click to retry'
|
|
139
|
+
uploadfailed: 'Upload failed — click to retry',
|
|
140
|
+
template: 'Templates',
|
|
141
|
+
tplimgleft: 'Image left, text right',
|
|
142
|
+
tplimgright: 'Image right, text left',
|
|
143
|
+
tplimgtop: 'Image top, text below',
|
|
144
|
+
tplimgcenter: 'Text top, image center',
|
|
145
|
+
deltemplate: 'Delete template'
|
|
140
146
|
},
|
|
141
147
|
ar: {
|
|
142
148
|
dir: 'rtl',
|
|
@@ -272,7 +278,13 @@ const I18N = {
|
|
|
272
278
|
insertimg: 'إدراج',
|
|
273
279
|
dropimage: 'أسقط الصورة هنا',
|
|
274
280
|
uploading: 'جارٍ الرفع…',
|
|
275
|
-
uploadfailed: 'فشل الرفع — انقر لإعادة المحاولة'
|
|
281
|
+
uploadfailed: 'فشل الرفع — انقر لإعادة المحاولة',
|
|
282
|
+
template: 'قوالب',
|
|
283
|
+
tplimgleft: 'صورة يسار، نص يمين',
|
|
284
|
+
tplimgright: 'صورة يمين، نص يسار',
|
|
285
|
+
tplimgtop: 'صورة أعلى، نص أسفل',
|
|
286
|
+
tplimgcenter: 'نص أعلى، صورة وسط',
|
|
287
|
+
deltemplate: 'حذف القالب'
|
|
276
288
|
}
|
|
277
289
|
};
|
|
278
290
|
function getStrings(lang) {
|
|
@@ -284,7 +296,7 @@ function getStrings(lang) {
|
|
|
284
296
|
const DEFAULT_TOOLBAR = 'undo redo | preview print | importword revhistory | fontfamily fontsize | fontsizeincrease fontsizedecrease | ' +
|
|
285
297
|
'bold italic underline strikethrough | forecolor backcolor | alignleft aligncenter alignright alignjustify | ' +
|
|
286
298
|
'bullist numlist | outdent indent | link blockquote changecase lineheight wordspacing | removeformat | blocks | ' +
|
|
287
|
-
'ltr rtl | quickimage quicktable charmap | fullscreen sourcecode';
|
|
299
|
+
'ltr rtl | quickimage quicktable template charmap | fullscreen sourcecode';
|
|
288
300
|
/** Transcribed from the editor's built-in menu keys so that `menubar: true` (the
|
|
289
301
|
* default) renders identically to the hard-coded original. */
|
|
290
302
|
const DEFAULT_MENUBAR = 'file edit view insert format tools table help';
|
|
@@ -475,6 +487,11 @@ const IC = {
|
|
|
475
487
|
wordic: S('<path d="M6 3h8l5 5v13H6z"/><path d="M14 3v5h5"/><text x="8" y="17" font-size="8.5" stroke="none" fill="#222f3e" font-family="Arial" font-weight="bold">W</text>'),
|
|
476
488
|
draftic: G('M480-160q-134 0-227-93t-93-227q0-134 93-227t227-93q69 0 132 28.5T720-690v-110h80v280H520v-80h168q-32-56-87.5-88T480-720q-100 0-170 70t-70 170q0 100 70 170t170 70q77 0 139-44t87-116h84q-28 106-114 173t-196 67Z'),
|
|
477
489
|
trash: S('<path d="M5 7h14M10 7V5h4v2M7.5 7l1 13h7l1-13"/>'),
|
|
490
|
+
templateic: S('<rect x="4" y="4" width="16" height="16" rx="1.5"/><rect x="6.5" y="6.5" width="5" height="5" rx=".5"/><path d="M14 7.5h3.5M14 10h3.5M6.5 14h11M6.5 17h11"/>'),
|
|
491
|
+
tplleft: S('<rect x="3" y="5" width="8" height="8" rx=".8"/><path d="M13.5 6.5H21M13.5 9.5H21M13.5 12.5H21M3 15.5h18M3 18.5h18"/>'),
|
|
492
|
+
tplright: S('<rect x="13" y="5" width="8" height="8" rx=".8"/><path d="M3 6.5h7.5M3 9.5h7.5M3 12.5h7.5M3 15.5h18M3 18.5h18"/>'),
|
|
493
|
+
tpltop: S('<rect x="5" y="3" width="14" height="9" rx=".8"/><path d="M3 15h18M3 18h18M3 21h12"/>'),
|
|
494
|
+
tplcenter: S('<path d="M3 4h18M3 7h18M3 10h12"/><rect x="5" y="12.5" width="14" height="9" rx=".8"/>'),
|
|
478
495
|
rotateleft: S('<polyline points="1 4 1 10 7 10"/><path d="M3.51 15a9 9 0 1 0 2.13-9.36L1 10"/>'),
|
|
479
496
|
rotateright: S('<polyline points="23 4 23 10 17 10"/><path d="M20.49 15a9 9 0 1 1-2.12-9.36L23 10"/>'),
|
|
480
497
|
fliphorizontal: S('<path d="M12 3v18"/><path d="M7 8l-4 4 4 4"/><path d="M17 8l4 4-4 4"/>'),
|
|
@@ -1364,6 +1381,19 @@ async function importDocxToHtml(input) {
|
|
|
1364
1381
|
}
|
|
1365
1382
|
|
|
1366
1383
|
const IMG_CORNERS = ['nw', 'ne', 'sw', 'se'];
|
|
1384
|
+
const TPL_LAYOUTS = ['img-left', 'img-right', 'img-top', 'img-center'];
|
|
1385
|
+
const TPL_LABEL = {
|
|
1386
|
+
'img-left': 'tplimgleft',
|
|
1387
|
+
'img-right': 'tplimgright',
|
|
1388
|
+
'img-top': 'tplimgtop',
|
|
1389
|
+
'img-center': 'tplimgcenter'
|
|
1390
|
+
};
|
|
1391
|
+
const TPL_ICON = {
|
|
1392
|
+
'img-left': IC.tplleft,
|
|
1393
|
+
'img-right': IC.tplright,
|
|
1394
|
+
'img-top': IC.tpltop,
|
|
1395
|
+
'img-center': IC.tplcenter
|
|
1396
|
+
};
|
|
1367
1397
|
class FableEditor {
|
|
1368
1398
|
static instanceCounter = 0;
|
|
1369
1399
|
options;
|
|
@@ -1403,6 +1433,8 @@ class FableEditor {
|
|
|
1403
1433
|
onImageUploadError;
|
|
1404
1434
|
contentStyle;
|
|
1405
1435
|
selCtx = null;
|
|
1436
|
+
tplActive = null;
|
|
1437
|
+
tplCtx = null;
|
|
1406
1438
|
docInput;
|
|
1407
1439
|
revisions = [];
|
|
1408
1440
|
revTimer = null;
|
|
@@ -1561,12 +1593,17 @@ class FableEditor {
|
|
|
1561
1593
|
this.positionTableHandles();
|
|
1562
1594
|
this.positionImageHandles();
|
|
1563
1595
|
this.positionImgPhCtx();
|
|
1596
|
+
this.positionTplCtx();
|
|
1564
1597
|
});
|
|
1565
1598
|
this.on(this.ed, 'keydown', (e) => {
|
|
1566
1599
|
if (e.altKey && e.key === '0') {
|
|
1567
1600
|
e.preventDefault();
|
|
1568
1601
|
this.helpDlg();
|
|
1569
1602
|
}
|
|
1603
|
+
if ((e.key === 'Delete' || e.key === 'Backspace') && this.phActive) {
|
|
1604
|
+
e.preventDefault();
|
|
1605
|
+
this.removeImgPlaceholder();
|
|
1606
|
+
}
|
|
1570
1607
|
});
|
|
1571
1608
|
this.on(this.ed, 'paste', (e) => this.handlePaste(e));
|
|
1572
1609
|
this.on(this.ed, 'drop', (e) => {
|
|
@@ -1591,11 +1628,17 @@ class FableEditor {
|
|
|
1591
1628
|
const ph = target.closest?.('.img-ph');
|
|
1592
1629
|
if (ph && this.ed.contains(ph) && !this.options.readonly) {
|
|
1593
1630
|
e.preventDefault();
|
|
1631
|
+
this.ed.focus();
|
|
1594
1632
|
this.selectImgPlaceholder(ph);
|
|
1595
1633
|
}
|
|
1596
1634
|
else {
|
|
1597
1635
|
this.clearImgPlaceholderSel();
|
|
1598
1636
|
}
|
|
1637
|
+
const tpl = target.closest?.('.tpl');
|
|
1638
|
+
if (tpl && this.ed.contains(tpl) && !this.options.readonly)
|
|
1639
|
+
this.selectTemplate(tpl);
|
|
1640
|
+
else
|
|
1641
|
+
this.clearTplSel();
|
|
1599
1642
|
const img = target.closest?.('img');
|
|
1600
1643
|
if (img && this.ed.contains(img) && !this.options.readonly) {
|
|
1601
1644
|
e.preventDefault();
|
|
@@ -1630,17 +1673,20 @@ class FableEditor {
|
|
|
1630
1673
|
this.positionTableHandles();
|
|
1631
1674
|
this.positionImageHandles();
|
|
1632
1675
|
this.positionImgPhCtx();
|
|
1676
|
+
this.positionTplCtx();
|
|
1633
1677
|
});
|
|
1634
1678
|
this.onWin('scroll', () => {
|
|
1635
1679
|
this.positionTableHandles();
|
|
1636
1680
|
this.positionImageHandles();
|
|
1637
1681
|
this.positionImgPhCtx();
|
|
1682
|
+
this.positionTplCtx();
|
|
1638
1683
|
this.repositionSelToolbar();
|
|
1639
1684
|
}, true);
|
|
1640
1685
|
this.onWin('resize', () => {
|
|
1641
1686
|
this.positionTableHandles();
|
|
1642
1687
|
this.positionImageHandles();
|
|
1643
1688
|
this.positionImgPhCtx();
|
|
1689
|
+
this.positionTplCtx();
|
|
1644
1690
|
this.repositionSelToolbar();
|
|
1645
1691
|
});
|
|
1646
1692
|
this.on(this.imgInput, 'change', () => {
|
|
@@ -1675,6 +1721,7 @@ class FableEditor {
|
|
|
1675
1721
|
this.imgHandles.includes(e.target) ||
|
|
1676
1722
|
(this.imgCtx && this.imgCtx.contains(e.target)) ||
|
|
1677
1723
|
(this.phCtx && this.phCtx.contains(e.target)) ||
|
|
1724
|
+
(this.tplCtx && this.tplCtx.contains(e.target)) ||
|
|
1678
1725
|
(this.selCtx && this.selCtx.contains(e.target)) ||
|
|
1679
1726
|
(this.openPop && this.openPop.contains(e.target)) ||
|
|
1680
1727
|
(this.openSubEl && this.openSubEl.contains(e.target)))
|
|
@@ -1682,6 +1729,7 @@ class FableEditor {
|
|
|
1682
1729
|
this.clearTableHandles();
|
|
1683
1730
|
this.clearImageHandles();
|
|
1684
1731
|
this.clearImgPlaceholderSel();
|
|
1732
|
+
this.clearTplSel();
|
|
1685
1733
|
this.clearSelToolbar();
|
|
1686
1734
|
});
|
|
1687
1735
|
}
|
|
@@ -1707,6 +1755,7 @@ class FableEditor {
|
|
|
1707
1755
|
this.clearTableHandles();
|
|
1708
1756
|
this.clearImageHandles();
|
|
1709
1757
|
this.clearImgPlaceholderSel();
|
|
1758
|
+
this.clearTplSel();
|
|
1710
1759
|
this.clearSelToolbar();
|
|
1711
1760
|
this.onChange();
|
|
1712
1761
|
}
|
|
@@ -1768,6 +1817,7 @@ class FableEditor {
|
|
|
1768
1817
|
this.clearTableHandles();
|
|
1769
1818
|
this.clearImageHandles();
|
|
1770
1819
|
this.clearImgPlaceholderSel();
|
|
1820
|
+
this.clearTplSel();
|
|
1771
1821
|
this.clearSelToolbar();
|
|
1772
1822
|
this.listeners.forEach((fn) => fn());
|
|
1773
1823
|
this.listeners = [];
|
|
@@ -2612,6 +2662,7 @@ class FableEditor {
|
|
|
2612
2662
|
rtl: () => this.tbtn(IC.rtl, this.t('rtl'), () => this.setDir('rtl'), 'rtl'),
|
|
2613
2663
|
quickimage: () => this.tbtn(IC.image, this.t('quickimage'), () => this.insertImagePlaceholder()),
|
|
2614
2664
|
quicktable: () => tableBtn,
|
|
2665
|
+
template: () => this.menuTbtn(IC.templateic, this.t('template'), (b) => this.templateMenu(b)),
|
|
2615
2666
|
charmap: () => this.tbtn(IC.charic, this.t('charmap'), () => this.charMap()),
|
|
2616
2667
|
fullscreen: () => this.tbtn(IC.fullscreen, this.t('fullscreen'), () => this.toggleFullscreen(), 'fullscreen'),
|
|
2617
2668
|
sourcecode: () => this.tbtn(IC.srcic, this.t('sourcecode'), () => this.sourceDlg())
|
|
@@ -2683,6 +2734,7 @@ class FableEditor {
|
|
|
2683
2734
|
this.mItem('link', IC.link, () => this.linkDlg()),
|
|
2684
2735
|
this.mItem('image', IC.image, () => this.insertImagePlaceholder()),
|
|
2685
2736
|
{ label: this.t('inserttable'), icon: IC.tableic, action: () => this.tableGrid(this.menubar.querySelector('[data-menu-key="table"]') || this.menubar) },
|
|
2737
|
+
{ label: this.t('template'), icon: IC.templateic, subBuild: (el) => this.buildTemplatePickInto(el) },
|
|
2686
2738
|
this.mItem('hr', IC.hric, () => this.exec('insertHorizontalRule')),
|
|
2687
2739
|
'|',
|
|
2688
2740
|
this.mItem('charmap', IC.charic, () => this.charMap()),
|
|
@@ -2755,7 +2807,7 @@ class FableEditor {
|
|
|
2755
2807
|
this.tablePropsDlg();
|
|
2756
2808
|
}
|
|
2757
2809
|
},
|
|
2758
|
-
{ label: this.t('deltable'), icon: IC.
|
|
2810
|
+
{ label: this.t('deltable'), icon: IC.trash, action: () => this.tableOp('deltable') }
|
|
2759
2811
|
],
|
|
2760
2812
|
help: [this.mItem('helpttl', IC.helpic, () => this.helpDlg(), 'Alt+0')]
|
|
2761
2813
|
};
|
|
@@ -3286,10 +3338,13 @@ class FableEditor {
|
|
|
3286
3338
|
this.imgInput.click();
|
|
3287
3339
|
}
|
|
3288
3340
|
/* ---------------------------------------------- image upload placeholder */
|
|
3341
|
+
imgPhHTML(id) {
|
|
3342
|
+
return `<div class="img-ph"${id ? ` id="${id}"` : ''} contenteditable="false">${IC.image}<span>${this.t('dropimage')}</span></div>`;
|
|
3343
|
+
}
|
|
3289
3344
|
insertImagePlaceholder() {
|
|
3290
3345
|
this.restoreSel();
|
|
3291
3346
|
const id = 'img-ph-' + Date.now().toString(36) + Math.random().toString(36).slice(2, 6);
|
|
3292
|
-
document.execCommand('insertHTML', false,
|
|
3347
|
+
document.execCommand('insertHTML', false, this.imgPhHTML(id));
|
|
3293
3348
|
const ph = this.ed.querySelector('#' + id);
|
|
3294
3349
|
if (ph) {
|
|
3295
3350
|
ph.removeAttribute('id');
|
|
@@ -3303,6 +3358,17 @@ class FableEditor {
|
|
|
3303
3358
|
this.phActive?.classList.remove('active');
|
|
3304
3359
|
this.phActive = null;
|
|
3305
3360
|
}
|
|
3361
|
+
removeImgPlaceholder() {
|
|
3362
|
+
const ph = this.phActive;
|
|
3363
|
+
if (!ph)
|
|
3364
|
+
return;
|
|
3365
|
+
this.clearImgPlaceholderSel();
|
|
3366
|
+
/* the placeholder inside a template block IS the media slot — deleting
|
|
3367
|
+
it drops the slot entirely, leaving a text-only block */
|
|
3368
|
+
(ph.closest('.tpl-media') || ph).remove();
|
|
3369
|
+
this.refreshState();
|
|
3370
|
+
this.onChange();
|
|
3371
|
+
}
|
|
3306
3372
|
selectImgPlaceholder(ph) {
|
|
3307
3373
|
if (this.phActive === ph)
|
|
3308
3374
|
return;
|
|
@@ -3323,7 +3389,7 @@ class FableEditor {
|
|
|
3323
3389
|
el.append(this.ctxBtn(IC.uploadic, this.t('uploadimg'), () => {
|
|
3324
3390
|
this.phUploadTarget = this.phActive;
|
|
3325
3391
|
this.pickImage();
|
|
3326
|
-
}), this.ctxBtn(IC.link, this.t('imagelink'), () => this.renderPhCtxUrlInput()));
|
|
3392
|
+
}), this.ctxBtn(IC.link, this.t('imagelink'), () => this.renderPhCtxUrlInput()), this.ctxSep(), this.ctxBtn(IC.trash, this.t('deleteimg'), () => this.removeImgPlaceholder()));
|
|
3327
3393
|
this.positionImgPhCtx();
|
|
3328
3394
|
}
|
|
3329
3395
|
renderPhCtxUrlInput() {
|
|
@@ -3448,6 +3514,139 @@ class FableEditor {
|
|
|
3448
3514
|
this.phCtx.style.left = cx + 'px';
|
|
3449
3515
|
this.phCtx.style.top = cy + 'px';
|
|
3450
3516
|
}
|
|
3517
|
+
/* ---------------------------------------------------------- templates */
|
|
3518
|
+
templateMenu(anchor) {
|
|
3519
|
+
this.popup(anchor, (el) => this.buildTemplatePickInto(el));
|
|
3520
|
+
}
|
|
3521
|
+
/** Miniature div-based mockup of a layout, mirroring what the block looks
|
|
3522
|
+
like in the editor: grey box = image slot, bars = heading/paragraph. */
|
|
3523
|
+
tplPreviewHTML(layout) {
|
|
3524
|
+
const img = '<span class="pv-img"></span>';
|
|
3525
|
+
const head = '<span class="pv-h"></span>';
|
|
3526
|
+
const line = (w) => `<span class="pv-l" style="width:${w}%"></span>`;
|
|
3527
|
+
const col = (inner) => `<span class="pv-col">${inner}</span>`;
|
|
3528
|
+
const row = (inner) => `<span class="pv-row">${inner}</span>`;
|
|
3529
|
+
const textCol = col(head + line(100) + line(88));
|
|
3530
|
+
if (layout === 'img-left')
|
|
3531
|
+
return `<span class="pv">${row(img + textCol)}${line(100)}${line(72)}</span>`;
|
|
3532
|
+
if (layout === 'img-right')
|
|
3533
|
+
return `<span class="pv">${row(textCol + img)}${line(100)}${line(72)}</span>`;
|
|
3534
|
+
if (layout === 'img-top')
|
|
3535
|
+
return `<span class="pv pv-c">${img}${head}${line(100)}${line(72)}</span>`;
|
|
3536
|
+
return `<span class="pv pv-c">${head}${line(100)}${line(72)}${img}</span>`;
|
|
3537
|
+
}
|
|
3538
|
+
buildTemplatePickInto(el, after) {
|
|
3539
|
+
const wrap = document.createElement('div');
|
|
3540
|
+
wrap.className = 'tplpick';
|
|
3541
|
+
TPL_LAYOUTS.forEach((layout) => {
|
|
3542
|
+
const tile = document.createElement('button');
|
|
3543
|
+
tile.type = 'button';
|
|
3544
|
+
tile.className = 'tpltile';
|
|
3545
|
+
tile.title = this.t(TPL_LABEL[layout]);
|
|
3546
|
+
tile.innerHTML = this.tplPreviewHTML(layout) + `<span class="tpllbl">${this.t(TPL_LABEL[layout])}</span>`;
|
|
3547
|
+
tile.addEventListener('click', () => {
|
|
3548
|
+
this.closePop();
|
|
3549
|
+
this.insertTemplate(layout);
|
|
3550
|
+
after?.();
|
|
3551
|
+
});
|
|
3552
|
+
wrap.appendChild(tile);
|
|
3553
|
+
});
|
|
3554
|
+
el.appendChild(wrap);
|
|
3555
|
+
}
|
|
3556
|
+
insertTemplate(layout) {
|
|
3557
|
+
this.restoreSel();
|
|
3558
|
+
const media = `<div class="tpl-media" contenteditable="false">${this.imgPhHTML()}</div>`;
|
|
3559
|
+
const text = `<div class="tpl-text"><h2>${this.t('heading')}</h2><p>${this.t('para')}</p></div>`;
|
|
3560
|
+
/* text-above-image layout keeps DOM order = visual order so the caret
|
|
3561
|
+
travels naturally; every other layout has the media slot first */
|
|
3562
|
+
const inner = layout === 'img-center' ? text + media : media + text;
|
|
3563
|
+
document.execCommand('insertHTML', false, `<div class="tpl tpl-${layout}">${inner}</div><p><br></p>`);
|
|
3564
|
+
this.saveSel();
|
|
3565
|
+
this.onChange();
|
|
3566
|
+
}
|
|
3567
|
+
clearTplSel() {
|
|
3568
|
+
this.tplCtx?.remove();
|
|
3569
|
+
this.tplCtx = null;
|
|
3570
|
+
this.tplActive?.classList.remove('tpl-selected');
|
|
3571
|
+
this.tplActive = null;
|
|
3572
|
+
}
|
|
3573
|
+
selectTemplate(tpl) {
|
|
3574
|
+
if (this.tplActive === tpl)
|
|
3575
|
+
return;
|
|
3576
|
+
this.clearTplSel();
|
|
3577
|
+
this.tplActive = tpl;
|
|
3578
|
+
tpl.classList.add('tpl-selected');
|
|
3579
|
+
this.tplCtx = document.createElement('div');
|
|
3580
|
+
this.tplCtx.className = 'imgctx tplctx';
|
|
3581
|
+
this.tplCtx.dir = this.dir();
|
|
3582
|
+
document.body.appendChild(this.tplCtx);
|
|
3583
|
+
this.renderTplCtxButtons();
|
|
3584
|
+
}
|
|
3585
|
+
renderTplCtxButtons() {
|
|
3586
|
+
const el = this.tplCtx;
|
|
3587
|
+
const tpl = this.tplActive;
|
|
3588
|
+
if (!el || !tpl)
|
|
3589
|
+
return;
|
|
3590
|
+
el.innerHTML = '';
|
|
3591
|
+
TPL_LAYOUTS.forEach((layout) => {
|
|
3592
|
+
const b = this.ctxBtn(TPL_ICON[layout], this.t(TPL_LABEL[layout]), () => this.setTplLayout(layout));
|
|
3593
|
+
b.classList.toggle('on', tpl.classList.contains('tpl-' + layout));
|
|
3594
|
+
el.appendChild(b);
|
|
3595
|
+
});
|
|
3596
|
+
el.append(this.ctxSep(), this.ctxBtn(IC.trash, this.t('deltemplate'), () => this.removeTemplate()));
|
|
3597
|
+
this.positionTplCtx();
|
|
3598
|
+
}
|
|
3599
|
+
setTplLayout(layout) {
|
|
3600
|
+
const tpl = this.tplActive;
|
|
3601
|
+
if (!tpl)
|
|
3602
|
+
return;
|
|
3603
|
+
TPL_LAYOUTS.forEach((l) => tpl.classList.remove('tpl-' + l));
|
|
3604
|
+
tpl.classList.add('tpl-' + layout);
|
|
3605
|
+
/* keep DOM order = visual order (see insertTemplate) */
|
|
3606
|
+
const media = tpl.querySelector(':scope > .tpl-media');
|
|
3607
|
+
const text = tpl.querySelector(':scope > .tpl-text');
|
|
3608
|
+
if (media && text) {
|
|
3609
|
+
if (layout === 'img-center')
|
|
3610
|
+
tpl.appendChild(media);
|
|
3611
|
+
else
|
|
3612
|
+
tpl.insertBefore(media, text);
|
|
3613
|
+
}
|
|
3614
|
+
this.renderTplCtxButtons();
|
|
3615
|
+
this.positionImgPhCtx();
|
|
3616
|
+
this.positionImageHandles();
|
|
3617
|
+
this.onChange();
|
|
3618
|
+
}
|
|
3619
|
+
removeTemplate() {
|
|
3620
|
+
const tpl = this.tplActive;
|
|
3621
|
+
if (!tpl)
|
|
3622
|
+
return;
|
|
3623
|
+
this.clearTplSel();
|
|
3624
|
+
if (this.phActive && tpl.contains(this.phActive))
|
|
3625
|
+
this.clearImgPlaceholderSel();
|
|
3626
|
+
if (this.imgActive && tpl.contains(this.imgActive))
|
|
3627
|
+
this.clearImageHandles();
|
|
3628
|
+
tpl.remove();
|
|
3629
|
+
this.refreshState();
|
|
3630
|
+
this.onChange();
|
|
3631
|
+
}
|
|
3632
|
+
positionTplCtx() {
|
|
3633
|
+
if (this.tplActive && !document.body.contains(this.tplActive)) {
|
|
3634
|
+
this.clearTplSel();
|
|
3635
|
+
return;
|
|
3636
|
+
}
|
|
3637
|
+
if (!this.tplActive || !this.tplCtx)
|
|
3638
|
+
return;
|
|
3639
|
+
const r = this.tplActive.getBoundingClientRect();
|
|
3640
|
+
const cw = this.tplCtx.offsetWidth;
|
|
3641
|
+
const ch = this.tplCtx.offsetHeight;
|
|
3642
|
+
let cx = r.left + scrollX + (r.width - cw) / 2;
|
|
3643
|
+
cx = Math.max(8 + scrollX, Math.min(cx, scrollX + innerWidth - cw - 8));
|
|
3644
|
+
let cy = r.top + scrollY - ch - 8;
|
|
3645
|
+
if (cy < this.ctxMinTop())
|
|
3646
|
+
cy = r.bottom + scrollY + 8;
|
|
3647
|
+
this.tplCtx.style.left = cx + 'px';
|
|
3648
|
+
this.tplCtx.style.top = cy + 'px';
|
|
3649
|
+
}
|
|
3451
3650
|
pickWordDoc() {
|
|
3452
3651
|
this.docInput.value = '';
|
|
3453
3652
|
this.docInput.click();
|
|
@@ -3554,7 +3753,7 @@ class FableEditor {
|
|
|
3554
3753
|
b.addEventListener('mouseleave', () => this.setOpTarget(null));
|
|
3555
3754
|
return b;
|
|
3556
3755
|
};
|
|
3557
|
-
el.append(hl(this.ctxBtn(IC.rowbefore, this.t('rowabove'), () => this.tableOp('rowabove')), 'row'), hl(this.ctxBtn(IC.rowafter, this.t('rowbelow'), () => this.tableOp('rowbelow')), 'row'), hl(this.ctxBtn(IC.rowdelete, this.t('delrow'), () => this.tableOp('delrow')), 'row'), this.ctxSep(), hl(this.ctxBtn(IC.colbefore, this.t('colbefore'), () => this.tableOp('colbefore')), 'col'), hl(this.ctxBtn(IC.colafter, this.t('colafter'), () => this.tableOp('colafter')), 'col'), hl(this.ctxBtn(IC.coldelete, this.t('delcol'), () => this.tableOp('delcol')), 'col'), this.ctxSep(), this.ctxBtn(IC.
|
|
3756
|
+
el.append(hl(this.ctxBtn(IC.rowbefore, this.t('rowabove'), () => this.tableOp('rowabove')), 'row'), hl(this.ctxBtn(IC.rowafter, this.t('rowbelow'), () => this.tableOp('rowbelow')), 'row'), hl(this.ctxBtn(IC.rowdelete, this.t('delrow'), () => this.tableOp('delrow')), 'row'), this.ctxSep(), hl(this.ctxBtn(IC.colbefore, this.t('colbefore'), () => this.tableOp('colbefore')), 'col'), hl(this.ctxBtn(IC.colafter, this.t('colafter'), () => this.tableOp('colafter')), 'col'), hl(this.ctxBtn(IC.coldelete, this.t('delcol'), () => this.tableOp('delcol')), 'col'), this.ctxSep(), this.ctxBtn(IC.trash, this.t('deltable'), () => this.tableOp('deltable')), this.ctxBtn(IC.tableic, this.t('tablepropsttl'), () => this.tablePropsDlg()));
|
|
3558
3757
|
document.body.appendChild(el);
|
|
3559
3758
|
return el;
|
|
3560
3759
|
}
|
|
@@ -3761,7 +3960,13 @@ class FableEditor {
|
|
|
3761
3960
|
this.flipImage(img, 'y');
|
|
3762
3961
|
rebuild();
|
|
3763
3962
|
}), this.ctxSep(), mkBtn(IC.trash, this.t('deleteimg'), false, () => {
|
|
3764
|
-
|
|
3963
|
+
/* inside a template media slot the image swaps back to an
|
|
3964
|
+
upload placeholder so the slot is not lost */
|
|
3965
|
+
const slot = img.closest('.tpl-media');
|
|
3966
|
+
if (slot && this.ed.contains(slot))
|
|
3967
|
+
img.outerHTML = this.imgPhHTML();
|
|
3968
|
+
else
|
|
3969
|
+
img.remove();
|
|
3765
3970
|
this.clearImageHandles();
|
|
3766
3971
|
this.onChange();
|
|
3767
3972
|
}));
|