@flogeez/angular-tiptap-editor 2.2.1 → 2.2.3
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.
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { input, output, ChangeDetectionStrategy, Component, signal, computed, Injectable, inject,
|
|
2
|
+
import { input, output, ChangeDetectionStrategy, Component, signal, computed, Injectable, inject, viewChild, effect, Directive, DestroyRef, untracked } from '@angular/core';
|
|
3
3
|
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
4
4
|
import { Node as Node$1, nodeInputRule, mergeAttributes, Extension, getAttributes, Editor } from '@tiptap/core';
|
|
5
5
|
import StarterKit from '@tiptap/starter-kit';
|
|
@@ -134,10 +134,12 @@ const AteResizableImage = Node$1.create({
|
|
|
134
134
|
img.alt = node.attrs["alt"] || "";
|
|
135
135
|
img.title = node.attrs["title"] || "";
|
|
136
136
|
img.className = "ate-image";
|
|
137
|
-
if (node.attrs["width"])
|
|
137
|
+
if (node.attrs["width"]) {
|
|
138
138
|
img.width = node.attrs["width"];
|
|
139
|
-
|
|
139
|
+
}
|
|
140
|
+
if (node.attrs["height"]) {
|
|
140
141
|
img.height = node.attrs["height"];
|
|
142
|
+
}
|
|
141
143
|
img.parentNode?.insertBefore(container, img);
|
|
142
144
|
container.appendChild(img);
|
|
143
145
|
// Add modern resize controls
|
|
@@ -173,13 +175,16 @@ const AteResizableImage = Node$1.create({
|
|
|
173
175
|
startX = e.clientX;
|
|
174
176
|
startY = e.clientY;
|
|
175
177
|
// Use current image dimensions instead of initial ones
|
|
176
|
-
startWidth =
|
|
177
|
-
|
|
178
|
+
startWidth =
|
|
179
|
+
parseInt(img.getAttribute("width") || "0") || node.attrs["width"] || img.naturalWidth;
|
|
180
|
+
startHeight =
|
|
181
|
+
parseInt(img.getAttribute("height") || "0") || node.attrs["height"] || img.naturalHeight;
|
|
178
182
|
// Add resizing class to body
|
|
179
183
|
document.body.classList.add("resizing");
|
|
180
184
|
const handleMouseMove = (e) => {
|
|
181
|
-
if (!isResizing)
|
|
185
|
+
if (!isResizing) {
|
|
182
186
|
return;
|
|
187
|
+
}
|
|
183
188
|
const deltaX = e.clientX - startX;
|
|
184
189
|
const deltaY = e.clientY - startY;
|
|
185
190
|
let newWidth = startWidth;
|
|
@@ -272,15 +277,18 @@ const AteResizableImage = Node$1.create({
|
|
|
272
277
|
selectNode,
|
|
273
278
|
deselectNode,
|
|
274
279
|
update: updatedNode => {
|
|
275
|
-
if (updatedNode.type.name !== "resizableImage")
|
|
280
|
+
if (updatedNode.type.name !== "resizableImage") {
|
|
276
281
|
return false;
|
|
282
|
+
}
|
|
277
283
|
img.src = updatedNode.attrs["src"];
|
|
278
284
|
img.alt = updatedNode.attrs["alt"] || "";
|
|
279
285
|
img.title = updatedNode.attrs["title"] || "";
|
|
280
|
-
if (updatedNode.attrs["width"])
|
|
286
|
+
if (updatedNode.attrs["width"]) {
|
|
281
287
|
img.width = updatedNode.attrs["width"];
|
|
282
|
-
|
|
288
|
+
}
|
|
289
|
+
if (updatedNode.attrs["height"]) {
|
|
283
290
|
img.height = updatedNode.attrs["height"];
|
|
291
|
+
}
|
|
284
292
|
return true;
|
|
285
293
|
},
|
|
286
294
|
};
|
|
@@ -600,14 +608,16 @@ const ATE_INITIAL_EDITOR_STATE = {
|
|
|
600
608
|
*/
|
|
601
609
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
602
610
|
function fastMerge(target, source) {
|
|
603
|
-
if (!source)
|
|
611
|
+
if (!source) {
|
|
604
612
|
return;
|
|
613
|
+
}
|
|
605
614
|
for (const key in source) {
|
|
606
615
|
const sourceVal = source[key];
|
|
607
616
|
// If the value is an object (marks, can, nodes, selection)
|
|
608
617
|
if (sourceVal !== null && typeof sourceVal === "object" && !Array.isArray(sourceVal)) {
|
|
609
|
-
if (!target[key])
|
|
618
|
+
if (!target[key]) {
|
|
610
619
|
target[key] = {};
|
|
620
|
+
}
|
|
611
621
|
// Merge internal properties
|
|
612
622
|
for (const subKey in sourceVal) {
|
|
613
623
|
const subVal = sourceVal[subKey];
|
|
@@ -644,8 +654,9 @@ const AteTiptapStateExtension = Extension.create({
|
|
|
644
654
|
view: () => ({
|
|
645
655
|
update: _view => {
|
|
646
656
|
const { editor } = this;
|
|
647
|
-
if (!editor)
|
|
657
|
+
if (!editor) {
|
|
648
658
|
return;
|
|
659
|
+
}
|
|
649
660
|
const snapshot = createFreshSnapshot();
|
|
650
661
|
const calcs = this.options.calculators;
|
|
651
662
|
for (const calculator of calcs) {
|
|
@@ -1246,14 +1257,20 @@ class AteImageService {
|
|
|
1246
1257
|
updateImageAttributes(editor, attributes) {
|
|
1247
1258
|
if (editor.isActive("resizableImage")) {
|
|
1248
1259
|
const pos = editor.state.selection.from;
|
|
1249
|
-
editor
|
|
1260
|
+
editor
|
|
1261
|
+
.chain()
|
|
1262
|
+
.focus()
|
|
1263
|
+
.updateAttributes("resizableImage", attributes)
|
|
1264
|
+
.setNodeSelection(pos)
|
|
1265
|
+
.run();
|
|
1250
1266
|
this.updateSelectedImage(attributes);
|
|
1251
1267
|
}
|
|
1252
1268
|
}
|
|
1253
1269
|
/** Resize image with optional aspect ratio maintenance */
|
|
1254
1270
|
resizeImage(editor, options) {
|
|
1255
|
-
if (!editor.isActive("resizableImage"))
|
|
1271
|
+
if (!editor.isActive("resizableImage")) {
|
|
1256
1272
|
return;
|
|
1273
|
+
}
|
|
1257
1274
|
const currentAttrs = editor.getAttributes("resizableImage");
|
|
1258
1275
|
let newWidth = options.width;
|
|
1259
1276
|
let newHeight = options.height;
|
|
@@ -1268,10 +1285,12 @@ class AteImageService {
|
|
|
1268
1285
|
}
|
|
1269
1286
|
}
|
|
1270
1287
|
// Apply minimum limits
|
|
1271
|
-
if (newWidth)
|
|
1288
|
+
if (newWidth) {
|
|
1272
1289
|
newWidth = Math.max(50, newWidth);
|
|
1273
|
-
|
|
1290
|
+
}
|
|
1291
|
+
if (newHeight) {
|
|
1274
1292
|
newHeight = Math.max(50, newHeight);
|
|
1293
|
+
}
|
|
1275
1294
|
this.updateImageAttributes(editor, {
|
|
1276
1295
|
width: newWidth,
|
|
1277
1296
|
height: newHeight,
|
|
@@ -1288,8 +1307,9 @@ class AteImageService {
|
|
|
1288
1307
|
this.resizeImage(editor, { width: 800, height: 600, maintainAspectRatio: true });
|
|
1289
1308
|
}
|
|
1290
1309
|
resizeImageToOriginal(editor) {
|
|
1291
|
-
if (!editor.isActive("resizableImage"))
|
|
1310
|
+
if (!editor.isActive("resizableImage")) {
|
|
1292
1311
|
return;
|
|
1312
|
+
}
|
|
1293
1313
|
const img = new Image();
|
|
1294
1314
|
img.onload = () => {
|
|
1295
1315
|
this.resizeImage(editor, { width: img.naturalWidth, height: img.naturalHeight });
|
|
@@ -1298,8 +1318,9 @@ class AteImageService {
|
|
|
1298
1318
|
}
|
|
1299
1319
|
/** Get current image dimensions */
|
|
1300
1320
|
getImageDimensions(editor) {
|
|
1301
|
-
if (!editor.isActive("resizableImage"))
|
|
1321
|
+
if (!editor.isActive("resizableImage")) {
|
|
1302
1322
|
return null;
|
|
1323
|
+
}
|
|
1303
1324
|
const attrs = editor.getAttributes("resizableImage");
|
|
1304
1325
|
return {
|
|
1305
1326
|
width: attrs["width"] || 0,
|
|
@@ -1396,8 +1417,9 @@ class AteImageService {
|
|
|
1396
1417
|
maxSize: options?.maxSize,
|
|
1397
1418
|
allowedTypes: options?.allowedTypes,
|
|
1398
1419
|
});
|
|
1399
|
-
if (!validation.valid)
|
|
1420
|
+
if (!validation.valid) {
|
|
1400
1421
|
throw new Error(validation.error);
|
|
1422
|
+
}
|
|
1401
1423
|
this.uploadProgress.set(30);
|
|
1402
1424
|
this.uploadMessage.set(this.t().compressing);
|
|
1403
1425
|
this.forceEditorUpdate();
|
|
@@ -1419,8 +1441,9 @@ class AteImageService {
|
|
|
1419
1441
|
? await firstValueFrom(handlerResponse)
|
|
1420
1442
|
: await handlerResponse;
|
|
1421
1443
|
result.src = handlerResult.src;
|
|
1422
|
-
if (handlerResult.alt)
|
|
1444
|
+
if (handlerResult.alt) {
|
|
1423
1445
|
result.name = handlerResult.alt;
|
|
1446
|
+
}
|
|
1424
1447
|
}
|
|
1425
1448
|
catch (handlerError) {
|
|
1426
1449
|
console.error(this.t().uploadError, handlerError);
|
|
@@ -1528,7 +1551,11 @@ class AteImageService {
|
|
|
1528
1551
|
};
|
|
1529
1552
|
// If the image was active or is still active, update it atomically
|
|
1530
1553
|
if (wasActive || ed.isActive("resizableImage")) {
|
|
1531
|
-
ed.chain()
|
|
1554
|
+
ed.chain()
|
|
1555
|
+
.focus()
|
|
1556
|
+
.updateAttributes("resizableImage", imageData)
|
|
1557
|
+
.setNodeSelection(pos)
|
|
1558
|
+
.run();
|
|
1532
1559
|
this.updateSelectedImage(imageData);
|
|
1533
1560
|
}
|
|
1534
1561
|
else {
|
|
@@ -1550,20 +1577,25 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
|
|
|
1550
1577
|
* Normalizes color values (rgb/rgba to hex).
|
|
1551
1578
|
*/
|
|
1552
1579
|
function normalizeColor(color) {
|
|
1553
|
-
if (!color || color === "transparent" || color === "rgba(0, 0, 0, 0)")
|
|
1580
|
+
if (!color || color === "transparent" || color === "rgba(0, 0, 0, 0)") {
|
|
1554
1581
|
return null;
|
|
1555
|
-
|
|
1582
|
+
}
|
|
1583
|
+
if (color.startsWith("#")) {
|
|
1556
1584
|
return color;
|
|
1585
|
+
}
|
|
1557
1586
|
// Support both comma-separated (legacy) and space-separated (modern) rgb/rgba
|
|
1558
|
-
const rgbMatch = color
|
|
1587
|
+
const rgbMatch = color
|
|
1588
|
+
.trim()
|
|
1589
|
+
.match(/^rgba?\(\s*([\d.]+)[,\s]+([\d.]+)[,\s]+([\d.]+)(?:[,\s/]+([\d.]+))?\s*\)$/i);
|
|
1559
1590
|
// If it's a named color or something else we don't recognize,
|
|
1560
1591
|
// we can't easily normalize it to hex without a heavy dictionary.
|
|
1561
1592
|
// However, getComputedStyle usually returns rgb() which we handle above.
|
|
1562
1593
|
if (!rgbMatch) {
|
|
1563
1594
|
// Simple fallback for common named colors if getComputedStyle fails to return rgb
|
|
1564
1595
|
const named = { black: "#000000", white: "#ffffff", red: "#ff0000" };
|
|
1565
|
-
if (named[color.toLowerCase()])
|
|
1596
|
+
if (named[color.toLowerCase()]) {
|
|
1566
1597
|
return named[color.toLowerCase()];
|
|
1598
|
+
}
|
|
1567
1599
|
return null;
|
|
1568
1600
|
}
|
|
1569
1601
|
const r = Math.max(0, Math.min(255, Math.round(parseFloat(rgbMatch[1]))));
|
|
@@ -1571,8 +1603,9 @@ function normalizeColor(color) {
|
|
|
1571
1603
|
const b = Math.max(0, Math.min(255, Math.round(parseFloat(rgbMatch[3]))));
|
|
1572
1604
|
const a = rgbMatch[4] ? parseFloat(rgbMatch[4]) : 1;
|
|
1573
1605
|
// If completely transparent, return null
|
|
1574
|
-
if (a === 0)
|
|
1606
|
+
if (a === 0) {
|
|
1575
1607
|
return null;
|
|
1608
|
+
}
|
|
1576
1609
|
return ("#" +
|
|
1577
1610
|
[r, g, b]
|
|
1578
1611
|
.map(n => n.toString(16).padStart(2, "0"))
|
|
@@ -1584,8 +1617,9 @@ function normalizeColor(color) {
|
|
|
1584
1617
|
*/
|
|
1585
1618
|
function getLuminance(color) {
|
|
1586
1619
|
const normalized = normalizeColor(color);
|
|
1587
|
-
if (!normalized)
|
|
1588
|
-
return 0;
|
|
1620
|
+
if (!normalized) {
|
|
1621
|
+
return 0;
|
|
1622
|
+
} // Default to dark (luminance 0) if no color
|
|
1589
1623
|
const hex = normalized.replace("#", "");
|
|
1590
1624
|
const r = parseInt(hex.substring(0, 2), 16);
|
|
1591
1625
|
const g = parseInt(hex.substring(2, 4), 16);
|
|
@@ -1639,8 +1673,9 @@ class AteColorPickerService {
|
|
|
1639
1673
|
* Toggle color picker from UI (extracts trigger from event).
|
|
1640
1674
|
*/
|
|
1641
1675
|
toggle(editor, mode, event) {
|
|
1642
|
-
if (!editor)
|
|
1676
|
+
if (!editor) {
|
|
1643
1677
|
return;
|
|
1678
|
+
}
|
|
1644
1679
|
let trigger;
|
|
1645
1680
|
if (event && typeof event !== "string") {
|
|
1646
1681
|
const target = event.target;
|
|
@@ -1655,8 +1690,9 @@ class AteColorPickerService {
|
|
|
1655
1690
|
* Capture current editor selection.
|
|
1656
1691
|
*/
|
|
1657
1692
|
captureSelection(editor) {
|
|
1658
|
-
if (!editor)
|
|
1693
|
+
if (!editor) {
|
|
1659
1694
|
return;
|
|
1695
|
+
}
|
|
1660
1696
|
this.storedSelection = {
|
|
1661
1697
|
from: editor.state.selection.from,
|
|
1662
1698
|
to: editor.state.selection.to,
|
|
@@ -1682,42 +1718,44 @@ class AteColorPickerService {
|
|
|
1682
1718
|
* Apply text color to selection.
|
|
1683
1719
|
*/
|
|
1684
1720
|
applyColor(editor, color, addToHistory = true, focus = true) {
|
|
1685
|
-
if (!editor)
|
|
1721
|
+
if (!editor) {
|
|
1686
1722
|
return;
|
|
1723
|
+
}
|
|
1687
1724
|
const stored = this.storedSelection;
|
|
1688
1725
|
let chain = editor.chain();
|
|
1689
|
-
if (focus)
|
|
1726
|
+
if (focus) {
|
|
1690
1727
|
chain = chain.focus();
|
|
1728
|
+
}
|
|
1691
1729
|
if (stored && (editor.state.selection.empty || !editor.isFocused)) {
|
|
1692
1730
|
chain = chain.setTextSelection(stored);
|
|
1693
1731
|
}
|
|
1694
1732
|
else if (editor.state.selection.empty && !stored) {
|
|
1695
1733
|
chain = chain.extendMarkRange("textStyle");
|
|
1696
1734
|
}
|
|
1697
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1698
1735
|
chain.setColor(color);
|
|
1699
|
-
|
|
1700
|
-
if (addToHistory === false)
|
|
1736
|
+
if (addToHistory === false) {
|
|
1701
1737
|
chain = chain.setMeta("addToHistory", false);
|
|
1738
|
+
}
|
|
1702
1739
|
chain.run();
|
|
1703
1740
|
}
|
|
1704
1741
|
/**
|
|
1705
1742
|
* Remove text color from selection.
|
|
1706
1743
|
*/
|
|
1707
1744
|
unsetColor(editor, focus = true) {
|
|
1708
|
-
if (!editor)
|
|
1745
|
+
if (!editor) {
|
|
1709
1746
|
return;
|
|
1747
|
+
}
|
|
1710
1748
|
const stored = this.storedSelection;
|
|
1711
1749
|
let chain = editor.chain();
|
|
1712
|
-
if (focus)
|
|
1750
|
+
if (focus) {
|
|
1713
1751
|
chain = chain.focus();
|
|
1752
|
+
}
|
|
1714
1753
|
if (stored) {
|
|
1715
1754
|
chain = chain.setTextSelection(stored);
|
|
1716
1755
|
}
|
|
1717
1756
|
else if (editor.state.selection.empty) {
|
|
1718
1757
|
chain = chain.extendMarkRange("textStyle");
|
|
1719
1758
|
}
|
|
1720
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1721
1759
|
chain.unsetColor();
|
|
1722
1760
|
chain.run();
|
|
1723
1761
|
}
|
|
@@ -1725,42 +1763,44 @@ class AteColorPickerService {
|
|
|
1725
1763
|
* Apply highlight color to selection.
|
|
1726
1764
|
*/
|
|
1727
1765
|
applyHighlight(editor, color, addToHistory = true, focus = true) {
|
|
1728
|
-
if (!editor)
|
|
1766
|
+
if (!editor) {
|
|
1729
1767
|
return;
|
|
1768
|
+
}
|
|
1730
1769
|
const stored = this.storedSelection;
|
|
1731
1770
|
let chain = editor.chain();
|
|
1732
|
-
if (focus)
|
|
1771
|
+
if (focus) {
|
|
1733
1772
|
chain = chain.focus();
|
|
1773
|
+
}
|
|
1734
1774
|
if (stored && (editor.state.selection.empty || !editor.isFocused)) {
|
|
1735
1775
|
chain = chain.setTextSelection(stored);
|
|
1736
1776
|
}
|
|
1737
1777
|
else if (editor.state.selection.empty && !stored) {
|
|
1738
1778
|
chain = chain.extendMarkRange("highlight");
|
|
1739
1779
|
}
|
|
1740
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1741
1780
|
chain.setHighlight({ color });
|
|
1742
|
-
|
|
1743
|
-
if (addToHistory === false)
|
|
1781
|
+
if (addToHistory === false) {
|
|
1744
1782
|
chain = chain.setMeta("addToHistory", false);
|
|
1783
|
+
}
|
|
1745
1784
|
chain.run();
|
|
1746
1785
|
}
|
|
1747
1786
|
/**
|
|
1748
1787
|
* Remove highlight from selection.
|
|
1749
1788
|
*/
|
|
1750
1789
|
unsetHighlight(editor, focus = true) {
|
|
1751
|
-
if (!editor)
|
|
1790
|
+
if (!editor) {
|
|
1752
1791
|
return;
|
|
1792
|
+
}
|
|
1753
1793
|
const stored = this.storedSelection;
|
|
1754
1794
|
let chain = editor.chain();
|
|
1755
|
-
if (focus)
|
|
1795
|
+
if (focus) {
|
|
1756
1796
|
chain = chain.focus();
|
|
1797
|
+
}
|
|
1757
1798
|
if (stored) {
|
|
1758
1799
|
chain = chain.setTextSelection(stored);
|
|
1759
1800
|
}
|
|
1760
1801
|
else if (editor.state.selection.empty) {
|
|
1761
1802
|
chain = chain.extendMarkRange("highlight");
|
|
1762
1803
|
}
|
|
1763
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1764
1804
|
chain.unsetHighlight();
|
|
1765
1805
|
chain.run();
|
|
1766
1806
|
}
|
|
@@ -1830,8 +1870,9 @@ class AteLinkService {
|
|
|
1830
1870
|
* If an Event is provided, extracts the trigger for anchoring.
|
|
1831
1871
|
*/
|
|
1832
1872
|
toggle(editor, urlOrEvent) {
|
|
1833
|
-
if (!editor)
|
|
1873
|
+
if (!editor) {
|
|
1834
1874
|
return;
|
|
1875
|
+
}
|
|
1835
1876
|
// If a string URL is provided, set the link and close
|
|
1836
1877
|
if (urlOrEvent && typeof urlOrEvent === "string") {
|
|
1837
1878
|
this.setLink(editor, urlOrEvent);
|
|
@@ -1841,7 +1882,8 @@ class AteLinkService {
|
|
|
1841
1882
|
let trigger;
|
|
1842
1883
|
if (urlOrEvent && typeof urlOrEvent !== "string") {
|
|
1843
1884
|
const target = urlOrEvent.target;
|
|
1844
|
-
trigger =
|
|
1885
|
+
trigger =
|
|
1886
|
+
urlOrEvent.currentTarget || target?.closest("button") || target;
|
|
1845
1887
|
}
|
|
1846
1888
|
// Open the edit menu
|
|
1847
1889
|
this.open(trigger);
|
|
@@ -1853,8 +1895,9 @@ class AteLinkService {
|
|
|
1853
1895
|
* Apply a link to the current selection.
|
|
1854
1896
|
*/
|
|
1855
1897
|
setLink(editor, url) {
|
|
1856
|
-
if (!editor)
|
|
1898
|
+
if (!editor) {
|
|
1857
1899
|
return;
|
|
1900
|
+
}
|
|
1858
1901
|
editor.chain().focus().extendMarkRange("link").setLink({ href: url }).run();
|
|
1859
1902
|
this.close();
|
|
1860
1903
|
}
|
|
@@ -1862,8 +1905,9 @@ class AteLinkService {
|
|
|
1862
1905
|
* Remove link from the current selection.
|
|
1863
1906
|
*/
|
|
1864
1907
|
unsetLink(editor) {
|
|
1865
|
-
if (!editor)
|
|
1908
|
+
if (!editor) {
|
|
1866
1909
|
return;
|
|
1910
|
+
}
|
|
1867
1911
|
editor.chain().focus().extendMarkRange("link").unsetLink().run();
|
|
1868
1912
|
this.close();
|
|
1869
1913
|
}
|
|
@@ -1881,64 +1925,78 @@ class AteEditorCommandsService {
|
|
|
1881
1925
|
this.linkSvc = inject(AteLinkService);
|
|
1882
1926
|
this._editorState = signal(ATE_INITIAL_EDITOR_STATE, ...(ngDevMode ? [{ debugName: "_editorState", equal: (a, b) => {
|
|
1883
1927
|
// 1. Primitive global states
|
|
1884
|
-
if (a.isFocused !== b.isFocused || a.isEditable !== b.isEditable)
|
|
1928
|
+
if (a.isFocused !== b.isFocused || a.isEditable !== b.isEditable) {
|
|
1885
1929
|
return false;
|
|
1930
|
+
}
|
|
1886
1931
|
// 2. Detailed selection comparison
|
|
1887
1932
|
if (a.selection.from !== b.selection.from ||
|
|
1888
1933
|
a.selection.to !== b.selection.to ||
|
|
1889
1934
|
a.selection.type !== b.selection.type ||
|
|
1890
1935
|
a.selection.empty !== b.selection.empty ||
|
|
1891
|
-
a.selection.isSingleCell !== b.selection.isSingleCell)
|
|
1936
|
+
a.selection.isSingleCell !== b.selection.isSingleCell) {
|
|
1892
1937
|
return false;
|
|
1938
|
+
}
|
|
1893
1939
|
// Helper for object comparison
|
|
1894
1940
|
const isRecordEqual = (objA, objB) => {
|
|
1895
1941
|
const keysA = Object.keys(objA);
|
|
1896
1942
|
const keysB = Object.keys(objB);
|
|
1897
|
-
if (keysA.length !== keysB.length)
|
|
1943
|
+
if (keysA.length !== keysB.length) {
|
|
1898
1944
|
return false;
|
|
1945
|
+
}
|
|
1899
1946
|
return keysA.every(key => objA[key] === objB[key]);
|
|
1900
1947
|
};
|
|
1901
1948
|
// 3. Compare sub-states (marks, can, nodes)
|
|
1902
|
-
if (!isRecordEqual(a.marks, b.marks))
|
|
1949
|
+
if (!isRecordEqual(a.marks, b.marks)) {
|
|
1903
1950
|
return false;
|
|
1904
|
-
|
|
1951
|
+
}
|
|
1952
|
+
if (!isRecordEqual(a.can, b.can)) {
|
|
1905
1953
|
return false;
|
|
1906
|
-
|
|
1954
|
+
}
|
|
1955
|
+
if (!isRecordEqual(a.nodes, b.nodes)) {
|
|
1907
1956
|
return false;
|
|
1957
|
+
}
|
|
1908
1958
|
// 4. Compare custom extension states
|
|
1909
|
-
if (!isRecordEqual(a.custom, b.custom))
|
|
1959
|
+
if (!isRecordEqual(a.custom, b.custom)) {
|
|
1910
1960
|
return false;
|
|
1961
|
+
}
|
|
1911
1962
|
return true;
|
|
1912
1963
|
} }] : [{
|
|
1913
1964
|
equal: (a, b) => {
|
|
1914
1965
|
// 1. Primitive global states
|
|
1915
|
-
if (a.isFocused !== b.isFocused || a.isEditable !== b.isEditable)
|
|
1966
|
+
if (a.isFocused !== b.isFocused || a.isEditable !== b.isEditable) {
|
|
1916
1967
|
return false;
|
|
1968
|
+
}
|
|
1917
1969
|
// 2. Detailed selection comparison
|
|
1918
1970
|
if (a.selection.from !== b.selection.from ||
|
|
1919
1971
|
a.selection.to !== b.selection.to ||
|
|
1920
1972
|
a.selection.type !== b.selection.type ||
|
|
1921
1973
|
a.selection.empty !== b.selection.empty ||
|
|
1922
|
-
a.selection.isSingleCell !== b.selection.isSingleCell)
|
|
1974
|
+
a.selection.isSingleCell !== b.selection.isSingleCell) {
|
|
1923
1975
|
return false;
|
|
1976
|
+
}
|
|
1924
1977
|
// Helper for object comparison
|
|
1925
1978
|
const isRecordEqual = (objA, objB) => {
|
|
1926
1979
|
const keysA = Object.keys(objA);
|
|
1927
1980
|
const keysB = Object.keys(objB);
|
|
1928
|
-
if (keysA.length !== keysB.length)
|
|
1981
|
+
if (keysA.length !== keysB.length) {
|
|
1929
1982
|
return false;
|
|
1983
|
+
}
|
|
1930
1984
|
return keysA.every(key => objA[key] === objB[key]);
|
|
1931
1985
|
};
|
|
1932
1986
|
// 3. Compare sub-states (marks, can, nodes)
|
|
1933
|
-
if (!isRecordEqual(a.marks, b.marks))
|
|
1987
|
+
if (!isRecordEqual(a.marks, b.marks)) {
|
|
1934
1988
|
return false;
|
|
1935
|
-
|
|
1989
|
+
}
|
|
1990
|
+
if (!isRecordEqual(a.can, b.can)) {
|
|
1936
1991
|
return false;
|
|
1937
|
-
|
|
1992
|
+
}
|
|
1993
|
+
if (!isRecordEqual(a.nodes, b.nodes)) {
|
|
1938
1994
|
return false;
|
|
1995
|
+
}
|
|
1939
1996
|
// 4. Compare custom extension states
|
|
1940
|
-
if (!isRecordEqual(a.custom, b.custom))
|
|
1997
|
+
if (!isRecordEqual(a.custom, b.custom)) {
|
|
1941
1998
|
return false;
|
|
1999
|
+
}
|
|
1942
2000
|
return true;
|
|
1943
2001
|
},
|
|
1944
2002
|
}]));
|
|
@@ -1988,8 +2046,9 @@ class AteEditorCommandsService {
|
|
|
1988
2046
|
// ============================================
|
|
1989
2047
|
/** Generic method to execute any command by name */
|
|
1990
2048
|
execute(editor, command, ...args) {
|
|
1991
|
-
if (!editor)
|
|
2049
|
+
if (!editor) {
|
|
1992
2050
|
return;
|
|
2051
|
+
}
|
|
1993
2052
|
switch (command) {
|
|
1994
2053
|
case "toggleBold":
|
|
1995
2054
|
this.toggleBold(editor);
|
|
@@ -2112,53 +2171,63 @@ class AteEditorCommandsService {
|
|
|
2112
2171
|
}
|
|
2113
2172
|
// --- Formatting Commands ---
|
|
2114
2173
|
toggleBold(editor) {
|
|
2115
|
-
if (!editor)
|
|
2174
|
+
if (!editor) {
|
|
2116
2175
|
return;
|
|
2176
|
+
}
|
|
2117
2177
|
editor.chain().focus().toggleBold().run();
|
|
2118
2178
|
}
|
|
2119
2179
|
toggleItalic(editor) {
|
|
2120
|
-
if (!editor)
|
|
2180
|
+
if (!editor) {
|
|
2121
2181
|
return;
|
|
2182
|
+
}
|
|
2122
2183
|
editor.chain().focus().toggleItalic().run();
|
|
2123
2184
|
}
|
|
2124
2185
|
toggleStrike(editor) {
|
|
2125
|
-
if (!editor)
|
|
2186
|
+
if (!editor) {
|
|
2126
2187
|
return;
|
|
2188
|
+
}
|
|
2127
2189
|
editor.chain().focus().toggleStrike().run();
|
|
2128
2190
|
}
|
|
2129
2191
|
toggleCode(editor) {
|
|
2130
|
-
if (!editor)
|
|
2192
|
+
if (!editor) {
|
|
2131
2193
|
return;
|
|
2194
|
+
}
|
|
2132
2195
|
editor.chain().focus().toggleCode().run();
|
|
2133
2196
|
}
|
|
2134
2197
|
toggleCodeBlock(editor) {
|
|
2135
|
-
if (!editor)
|
|
2198
|
+
if (!editor) {
|
|
2136
2199
|
return;
|
|
2200
|
+
}
|
|
2137
2201
|
editor.chain().focus().toggleCodeBlock().run();
|
|
2138
2202
|
}
|
|
2139
2203
|
toggleUnderline(editor) {
|
|
2140
|
-
if (!editor)
|
|
2204
|
+
if (!editor) {
|
|
2141
2205
|
return;
|
|
2206
|
+
}
|
|
2142
2207
|
editor.chain().focus().toggleUnderline().run();
|
|
2143
2208
|
}
|
|
2144
2209
|
toggleSuperscript(editor) {
|
|
2145
|
-
if (!editor)
|
|
2210
|
+
if (!editor) {
|
|
2146
2211
|
return;
|
|
2212
|
+
}
|
|
2147
2213
|
editor.chain().focus().toggleSuperscript().run();
|
|
2148
2214
|
}
|
|
2149
2215
|
toggleSubscript(editor) {
|
|
2150
|
-
if (!editor)
|
|
2216
|
+
if (!editor) {
|
|
2151
2217
|
return;
|
|
2218
|
+
}
|
|
2152
2219
|
editor.chain().focus().toggleSubscript().run();
|
|
2153
2220
|
}
|
|
2154
2221
|
toggleHeading(editor, level) {
|
|
2155
|
-
if (!editor)
|
|
2222
|
+
if (!editor) {
|
|
2156
2223
|
return;
|
|
2224
|
+
}
|
|
2157
2225
|
editor.chain().focus().toggleHeading({ level }).run();
|
|
2158
2226
|
}
|
|
2159
2227
|
toggleHighlight(editor, color) {
|
|
2160
|
-
if (!editor)
|
|
2228
|
+
if (!editor) {
|
|
2161
2229
|
return;
|
|
2230
|
+
}
|
|
2162
2231
|
if (color) {
|
|
2163
2232
|
editor.chain().focus().setHighlight({ color }).run();
|
|
2164
2233
|
}
|
|
@@ -2168,131 +2237,156 @@ class AteEditorCommandsService {
|
|
|
2168
2237
|
}
|
|
2169
2238
|
// --- Structure Commands ---
|
|
2170
2239
|
toggleBulletList(editor) {
|
|
2171
|
-
if (!editor)
|
|
2240
|
+
if (!editor) {
|
|
2172
2241
|
return;
|
|
2242
|
+
}
|
|
2173
2243
|
editor.chain().focus().toggleBulletList().run();
|
|
2174
2244
|
}
|
|
2175
2245
|
toggleOrderedList(editor) {
|
|
2176
|
-
if (!editor)
|
|
2246
|
+
if (!editor) {
|
|
2177
2247
|
return;
|
|
2248
|
+
}
|
|
2178
2249
|
editor.chain().focus().toggleOrderedList().run();
|
|
2179
2250
|
}
|
|
2180
2251
|
toggleBlockquote(editor) {
|
|
2181
|
-
if (!editor)
|
|
2252
|
+
if (!editor) {
|
|
2182
2253
|
return;
|
|
2254
|
+
}
|
|
2183
2255
|
editor.chain().focus().toggleBlockquote().run();
|
|
2184
2256
|
}
|
|
2185
2257
|
setTextAlign(editor, alignment) {
|
|
2186
|
-
if (!editor)
|
|
2258
|
+
if (!editor) {
|
|
2187
2259
|
return;
|
|
2260
|
+
}
|
|
2188
2261
|
editor.chain().focus().setTextAlign(alignment).run();
|
|
2189
2262
|
}
|
|
2190
2263
|
insertHorizontalRule(editor) {
|
|
2191
|
-
if (!editor)
|
|
2264
|
+
if (!editor) {
|
|
2192
2265
|
return;
|
|
2266
|
+
}
|
|
2193
2267
|
editor.chain().focus().setHorizontalRule().run();
|
|
2194
2268
|
}
|
|
2195
2269
|
// --- History Commands ---
|
|
2196
2270
|
undo(editor) {
|
|
2197
|
-
if (!editor)
|
|
2271
|
+
if (!editor) {
|
|
2198
2272
|
return;
|
|
2273
|
+
}
|
|
2199
2274
|
editor.chain().focus().undo().run();
|
|
2200
2275
|
}
|
|
2201
2276
|
redo(editor) {
|
|
2202
|
-
if (!editor)
|
|
2277
|
+
if (!editor) {
|
|
2203
2278
|
return;
|
|
2279
|
+
}
|
|
2204
2280
|
editor.chain().focus().redo().run();
|
|
2205
2281
|
}
|
|
2206
2282
|
// --- Table Commands ---
|
|
2207
2283
|
insertTable(editor, rows = 3, cols = 3) {
|
|
2208
|
-
if (!editor)
|
|
2284
|
+
if (!editor) {
|
|
2209
2285
|
return;
|
|
2286
|
+
}
|
|
2210
2287
|
editor.chain().focus().insertTable({ rows, cols }).run();
|
|
2211
2288
|
}
|
|
2212
2289
|
addColumnBefore(editor) {
|
|
2213
|
-
if (!editor)
|
|
2290
|
+
if (!editor) {
|
|
2214
2291
|
return;
|
|
2292
|
+
}
|
|
2215
2293
|
editor.chain().focus().addColumnBefore().run();
|
|
2216
2294
|
}
|
|
2217
2295
|
addColumnAfter(editor) {
|
|
2218
|
-
if (!editor)
|
|
2296
|
+
if (!editor) {
|
|
2219
2297
|
return;
|
|
2298
|
+
}
|
|
2220
2299
|
editor.chain().focus().addColumnAfter().run();
|
|
2221
2300
|
}
|
|
2222
2301
|
deleteColumn(editor) {
|
|
2223
|
-
if (!editor)
|
|
2302
|
+
if (!editor) {
|
|
2224
2303
|
return;
|
|
2304
|
+
}
|
|
2225
2305
|
editor.chain().focus().deleteColumn().run();
|
|
2226
2306
|
}
|
|
2227
2307
|
addRowBefore(editor) {
|
|
2228
|
-
if (!editor)
|
|
2308
|
+
if (!editor) {
|
|
2229
2309
|
return;
|
|
2310
|
+
}
|
|
2230
2311
|
editor.chain().focus().addRowBefore().run();
|
|
2231
2312
|
}
|
|
2232
2313
|
addRowAfter(editor) {
|
|
2233
|
-
if (!editor)
|
|
2314
|
+
if (!editor) {
|
|
2234
2315
|
return;
|
|
2316
|
+
}
|
|
2235
2317
|
editor.chain().focus().addRowAfter().run();
|
|
2236
2318
|
}
|
|
2237
2319
|
deleteRow(editor) {
|
|
2238
|
-
if (!editor)
|
|
2320
|
+
if (!editor) {
|
|
2239
2321
|
return;
|
|
2322
|
+
}
|
|
2240
2323
|
editor.chain().focus().deleteRow().run();
|
|
2241
2324
|
}
|
|
2242
2325
|
deleteTable(editor) {
|
|
2243
|
-
if (!editor)
|
|
2326
|
+
if (!editor) {
|
|
2244
2327
|
return;
|
|
2328
|
+
}
|
|
2245
2329
|
editor.chain().focus().deleteTable().run();
|
|
2246
2330
|
}
|
|
2247
2331
|
mergeCells(editor) {
|
|
2248
|
-
if (!editor)
|
|
2332
|
+
if (!editor) {
|
|
2249
2333
|
return;
|
|
2334
|
+
}
|
|
2250
2335
|
editor.chain().focus().mergeCells().run();
|
|
2251
2336
|
}
|
|
2252
2337
|
splitCell(editor) {
|
|
2253
|
-
if (!editor)
|
|
2338
|
+
if (!editor) {
|
|
2254
2339
|
return;
|
|
2340
|
+
}
|
|
2255
2341
|
editor.chain().focus().splitCell().run();
|
|
2256
2342
|
}
|
|
2257
2343
|
toggleHeaderColumn(editor) {
|
|
2258
|
-
if (!editor)
|
|
2344
|
+
if (!editor) {
|
|
2259
2345
|
return;
|
|
2346
|
+
}
|
|
2260
2347
|
editor.chain().focus().toggleHeaderColumn().run();
|
|
2261
2348
|
}
|
|
2262
2349
|
toggleHeaderRow(editor) {
|
|
2263
|
-
if (!editor)
|
|
2350
|
+
if (!editor) {
|
|
2264
2351
|
return;
|
|
2352
|
+
}
|
|
2265
2353
|
editor.chain().focus().toggleHeaderRow().run();
|
|
2266
2354
|
}
|
|
2267
2355
|
// --- Utility Commands ---
|
|
2268
2356
|
clearContent(editor) {
|
|
2269
|
-
if (!editor)
|
|
2357
|
+
if (!editor) {
|
|
2270
2358
|
return;
|
|
2359
|
+
}
|
|
2271
2360
|
editor.commands.clearContent(true);
|
|
2272
2361
|
}
|
|
2273
2362
|
focus(editor) {
|
|
2274
|
-
if (!editor)
|
|
2363
|
+
if (!editor) {
|
|
2275
2364
|
return;
|
|
2365
|
+
}
|
|
2276
2366
|
editor.chain().focus().run();
|
|
2277
2367
|
}
|
|
2278
2368
|
blur(editor) {
|
|
2279
|
-
if (!editor)
|
|
2369
|
+
if (!editor) {
|
|
2280
2370
|
return;
|
|
2371
|
+
}
|
|
2281
2372
|
editor.chain().blur().run();
|
|
2282
2373
|
}
|
|
2283
2374
|
setContent(editor, content, emitUpdate = true) {
|
|
2284
|
-
if (!editor)
|
|
2375
|
+
if (!editor) {
|
|
2285
2376
|
return;
|
|
2377
|
+
}
|
|
2286
2378
|
editor.commands.setContent(content, emitUpdate);
|
|
2287
2379
|
}
|
|
2288
2380
|
setEditable(editor, editable) {
|
|
2289
|
-
if (!editor)
|
|
2381
|
+
if (!editor) {
|
|
2290
2382
|
return;
|
|
2383
|
+
}
|
|
2291
2384
|
editor.setEditable(editable);
|
|
2292
2385
|
}
|
|
2293
2386
|
insertContent(editor, content) {
|
|
2294
|
-
if (!editor)
|
|
2387
|
+
if (!editor) {
|
|
2295
2388
|
return;
|
|
2389
|
+
}
|
|
2296
2390
|
editor.chain().focus().insertContent(content).run();
|
|
2297
2391
|
}
|
|
2298
2392
|
async insertImage(editor, options) {
|
|
@@ -2448,8 +2542,9 @@ class AteToolbarComponent {
|
|
|
2448
2542
|
}
|
|
2449
2543
|
onCommand(command, ...args) {
|
|
2450
2544
|
const editor = this.editor();
|
|
2451
|
-
if (!editor)
|
|
2545
|
+
if (!editor) {
|
|
2452
2546
|
return;
|
|
2547
|
+
}
|
|
2453
2548
|
this.editorCommands.execute(editor, command, ...args);
|
|
2454
2549
|
}
|
|
2455
2550
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: AteToolbarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
@@ -2528,7 +2623,10 @@ class AteToolbarComponent {
|
|
|
2528
2623
|
(buttonClick)="onCommand('toggleHighlight')" />
|
|
2529
2624
|
}
|
|
2530
2625
|
@if (config().highlightPicker) {
|
|
2531
|
-
<ate-color-picker
|
|
2626
|
+
<ate-color-picker
|
|
2627
|
+
mode="highlight"
|
|
2628
|
+
[editor]="editor()"
|
|
2629
|
+
[disabled]="!state().can.setHighlight" />
|
|
2532
2630
|
}
|
|
2533
2631
|
@if (config().textColor) {
|
|
2534
2632
|
<ate-color-picker mode="text" [editor]="editor()" [disabled]="!state().can.setColor" />
|
|
@@ -2561,7 +2659,9 @@ class AteToolbarComponent {
|
|
|
2561
2659
|
[disabled]="!state().can.toggleHeading3"
|
|
2562
2660
|
(buttonClick)="onCommand('toggleHeading', 3)" />
|
|
2563
2661
|
}
|
|
2564
|
-
@if (
|
|
2662
|
+
@if (
|
|
2663
|
+
config().separator && (config().bulletList || config().orderedList || config().blockquote)
|
|
2664
|
+
) {
|
|
2565
2665
|
<ate-separator />
|
|
2566
2666
|
}
|
|
2567
2667
|
@if (config().bulletList) {
|
|
@@ -2665,10 +2765,18 @@ class AteToolbarComponent {
|
|
|
2665
2765
|
<ate-separator />
|
|
2666
2766
|
}
|
|
2667
2767
|
@if (config().undo) {
|
|
2668
|
-
<ate-button
|
|
2768
|
+
<ate-button
|
|
2769
|
+
icon="undo"
|
|
2770
|
+
[title]="t().undo"
|
|
2771
|
+
[disabled]="!state().can.undo"
|
|
2772
|
+
(buttonClick)="onCommand('undo')" />
|
|
2669
2773
|
}
|
|
2670
2774
|
@if (config().redo) {
|
|
2671
|
-
<ate-button
|
|
2775
|
+
<ate-button
|
|
2776
|
+
icon="redo"
|
|
2777
|
+
[title]="t().redo"
|
|
2778
|
+
[disabled]="!state().can.redo"
|
|
2779
|
+
(buttonClick)="onCommand('redo')" />
|
|
2672
2780
|
}
|
|
2673
2781
|
@if (config().separator && config().clear) {
|
|
2674
2782
|
<ate-separator />
|
|
@@ -2682,7 +2790,10 @@ class AteToolbarComponent {
|
|
|
2682
2790
|
}
|
|
2683
2791
|
@if (config().custom?.length) {
|
|
2684
2792
|
@for (item of config().custom; track item.key) {
|
|
2685
|
-
<ate-button
|
|
2793
|
+
<ate-button
|
|
2794
|
+
[icon]="item.icon"
|
|
2795
|
+
[title]="item.label"
|
|
2796
|
+
(buttonClick)="item.command(editor())"></ate-button>
|
|
2686
2797
|
}
|
|
2687
2798
|
}
|
|
2688
2799
|
</div>
|
|
@@ -2765,7 +2876,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
|
|
|
2765
2876
|
(buttonClick)="onCommand('toggleHighlight')" />
|
|
2766
2877
|
}
|
|
2767
2878
|
@if (config().highlightPicker) {
|
|
2768
|
-
<ate-color-picker
|
|
2879
|
+
<ate-color-picker
|
|
2880
|
+
mode="highlight"
|
|
2881
|
+
[editor]="editor()"
|
|
2882
|
+
[disabled]="!state().can.setHighlight" />
|
|
2769
2883
|
}
|
|
2770
2884
|
@if (config().textColor) {
|
|
2771
2885
|
<ate-color-picker mode="text" [editor]="editor()" [disabled]="!state().can.setColor" />
|
|
@@ -2798,7 +2912,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
|
|
|
2798
2912
|
[disabled]="!state().can.toggleHeading3"
|
|
2799
2913
|
(buttonClick)="onCommand('toggleHeading', 3)" />
|
|
2800
2914
|
}
|
|
2801
|
-
@if (
|
|
2915
|
+
@if (
|
|
2916
|
+
config().separator && (config().bulletList || config().orderedList || config().blockquote)
|
|
2917
|
+
) {
|
|
2802
2918
|
<ate-separator />
|
|
2803
2919
|
}
|
|
2804
2920
|
@if (config().bulletList) {
|
|
@@ -2902,10 +3018,18 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
|
|
|
2902
3018
|
<ate-separator />
|
|
2903
3019
|
}
|
|
2904
3020
|
@if (config().undo) {
|
|
2905
|
-
<ate-button
|
|
3021
|
+
<ate-button
|
|
3022
|
+
icon="undo"
|
|
3023
|
+
[title]="t().undo"
|
|
3024
|
+
[disabled]="!state().can.undo"
|
|
3025
|
+
(buttonClick)="onCommand('undo')" />
|
|
2906
3026
|
}
|
|
2907
3027
|
@if (config().redo) {
|
|
2908
|
-
<ate-button
|
|
3028
|
+
<ate-button
|
|
3029
|
+
icon="redo"
|
|
3030
|
+
[title]="t().redo"
|
|
3031
|
+
[disabled]="!state().can.redo"
|
|
3032
|
+
(buttonClick)="onCommand('redo')" />
|
|
2909
3033
|
}
|
|
2910
3034
|
@if (config().separator && config().clear) {
|
|
2911
3035
|
<ate-separator />
|
|
@@ -2919,7 +3043,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
|
|
|
2919
3043
|
}
|
|
2920
3044
|
@if (config().custom?.length) {
|
|
2921
3045
|
@for (item of config().custom; track item.key) {
|
|
2922
|
-
<ate-button
|
|
3046
|
+
<ate-button
|
|
3047
|
+
[icon]="item.icon"
|
|
3048
|
+
[title]="item.label"
|
|
3049
|
+
(buttonClick)="item.command(editor())"></ate-button>
|
|
2923
3050
|
}
|
|
2924
3051
|
}
|
|
2925
3052
|
</div>
|
|
@@ -2936,6 +3063,8 @@ class AteBaseBubbleMenu {
|
|
|
2936
3063
|
this.editorCommands = inject(AteEditorCommandsService);
|
|
2937
3064
|
// Core Inputs
|
|
2938
3065
|
this.editor = input.required(...(ngDevMode ? [{ debugName: "editor" }] : []));
|
|
3066
|
+
// Required viewChild for the menu container
|
|
3067
|
+
this.menuRef = viewChild.required("menuRef");
|
|
2939
3068
|
// Internal State
|
|
2940
3069
|
this.tippyInstance = null;
|
|
2941
3070
|
this.updateTimeout = null;
|
|
@@ -2952,8 +3081,9 @@ class AteBaseBubbleMenu {
|
|
|
2952
3081
|
}
|
|
2953
3082
|
this.updateTimeout = window.setTimeout(() => {
|
|
2954
3083
|
const ed = this.editor();
|
|
2955
|
-
if (!ed)
|
|
3084
|
+
if (!ed) {
|
|
2956
3085
|
return;
|
|
3086
|
+
}
|
|
2957
3087
|
// Hide when interacting with the main toolbar
|
|
2958
3088
|
if (this.isToolbarInteracting()) {
|
|
2959
3089
|
this.hideTippy();
|
|
@@ -2995,7 +3125,7 @@ class AteBaseBubbleMenu {
|
|
|
2995
3125
|
* Can be overridden for specific Tippy configurations.
|
|
2996
3126
|
*/
|
|
2997
3127
|
initTippy() {
|
|
2998
|
-
if (!this.menuRef?.nativeElement) {
|
|
3128
|
+
if (!this.menuRef()?.nativeElement) {
|
|
2999
3129
|
// Re-try if the view child is not yet available
|
|
3000
3130
|
setTimeout(() => this.initTippy(), 50);
|
|
3001
3131
|
return;
|
|
@@ -3005,7 +3135,7 @@ class AteBaseBubbleMenu {
|
|
|
3005
3135
|
this.tippyInstance.destroy();
|
|
3006
3136
|
}
|
|
3007
3137
|
this.tippyInstance = tippy(document.body, {
|
|
3008
|
-
content: this.menuRef.nativeElement,
|
|
3138
|
+
content: this.menuRef().nativeElement,
|
|
3009
3139
|
trigger: "manual",
|
|
3010
3140
|
placement: "top-start",
|
|
3011
3141
|
appendTo: () => (ed ? ed.options.element : document.body),
|
|
@@ -3042,8 +3172,9 @@ class AteBaseBubbleMenu {
|
|
|
3042
3172
|
* Helper to show the Tippy instance with updated positioning.
|
|
3043
3173
|
*/
|
|
3044
3174
|
showTippy() {
|
|
3045
|
-
if (!this.tippyInstance)
|
|
3175
|
+
if (!this.tippyInstance) {
|
|
3046
3176
|
return;
|
|
3177
|
+
}
|
|
3047
3178
|
// Update position before showing
|
|
3048
3179
|
this.tippyInstance.setProps({
|
|
3049
3180
|
getReferenceClientRect: () => this.getSelectionRect(),
|
|
@@ -3079,11 +3210,13 @@ class AteBaseBubbleMenu {
|
|
|
3079
3210
|
* Uses a combination of native selection and ProseMirror coordinates.
|
|
3080
3211
|
*/
|
|
3081
3212
|
getRectForSelection(ed) {
|
|
3082
|
-
if (!ed)
|
|
3213
|
+
if (!ed) {
|
|
3083
3214
|
return new DOMRect(0, 0, 0, 0);
|
|
3215
|
+
}
|
|
3084
3216
|
const { from, to, empty } = ed.state.selection;
|
|
3085
|
-
if (empty)
|
|
3217
|
+
if (empty) {
|
|
3086
3218
|
return new DOMRect(-9999, -9999, 0, 0);
|
|
3219
|
+
}
|
|
3087
3220
|
// 1. Try native selection for multi-line accuracy
|
|
3088
3221
|
const selection = window.getSelection();
|
|
3089
3222
|
if (selection && selection.rangeCount > 0) {
|
|
@@ -3116,14 +3249,11 @@ class AteBaseBubbleMenu {
|
|
|
3116
3249
|
/* empty */
|
|
3117
3250
|
}
|
|
3118
3251
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: AteBaseBubbleMenu, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
3119
|
-
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.
|
|
3252
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.2.0", version: "20.3.16", type: AteBaseBubbleMenu, isStandalone: true, inputs: { editor: { classPropertyName: "editor", publicName: "editor", isSignal: true, isRequired: true, transformFunction: null } }, viewQueries: [{ propertyName: "menuRef", first: true, predicate: ["menuRef"], descendants: true, isSignal: true }], ngImport: i0 }); }
|
|
3120
3253
|
}
|
|
3121
3254
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: AteBaseBubbleMenu, decorators: [{
|
|
3122
3255
|
type: Directive
|
|
3123
|
-
}], ctorParameters: () => [], propDecorators: { editor: [{ type: i0.Input, args: [{ isSignal: true, alias: "editor", required: true }] }], menuRef: [{
|
|
3124
|
-
type: ViewChild,
|
|
3125
|
-
args: ["menuRef", { static: false }]
|
|
3126
|
-
}] } });
|
|
3256
|
+
}], ctorParameters: () => [], propDecorators: { editor: [{ type: i0.Input, args: [{ isSignal: true, alias: "editor", required: true }] }], menuRef: [{ type: i0.ViewChild, args: ["menuRef", { isSignal: true }] }] } });
|
|
3127
3257
|
|
|
3128
3258
|
class AteBubbleMenuComponent extends AteBaseBubbleMenu {
|
|
3129
3259
|
constructor() {
|
|
@@ -3194,7 +3324,7 @@ class AteBubbleMenuComponent extends AteBaseBubbleMenu {
|
|
|
3194
3324
|
}
|
|
3195
3325
|
// Only show text bubble menu if there is a non-empty text selection
|
|
3196
3326
|
// and no higher-priority node (image, table) is selected.
|
|
3197
|
-
return selection.type === "text" && !selection.empty && !nodes.isImage && !nodes.isTableNodeSelected;
|
|
3327
|
+
return (selection.type === "text" && !selection.empty && !nodes.isImage && !nodes.isTableNodeSelected);
|
|
3198
3328
|
}
|
|
3199
3329
|
getSelectionRect() {
|
|
3200
3330
|
return this.getRectForSelection(this.editor());
|
|
@@ -3281,7 +3411,11 @@ class AteBubbleMenuComponent extends AteBaseBubbleMenu {
|
|
|
3281
3411
|
[anchorToText]="true" />
|
|
3282
3412
|
}
|
|
3283
3413
|
@if (bubbleMenuConfig().textColor) {
|
|
3284
|
-
<ate-color-picker
|
|
3414
|
+
<ate-color-picker
|
|
3415
|
+
mode="text"
|
|
3416
|
+
[editor]="editor()"
|
|
3417
|
+
[disabled]="!state().can.setColor"
|
|
3418
|
+
[anchorToText]="true" />
|
|
3285
3419
|
}
|
|
3286
3420
|
@if (bubbleMenuConfig().link) {
|
|
3287
3421
|
<ate-button
|
|
@@ -3293,7 +3427,10 @@ class AteBubbleMenuComponent extends AteBaseBubbleMenu {
|
|
|
3293
3427
|
}
|
|
3294
3428
|
@if (bubbleMenuConfig().custom?.length) {
|
|
3295
3429
|
@for (item of bubbleMenuConfig().custom; track item.key) {
|
|
3296
|
-
<ate-button
|
|
3430
|
+
<ate-button
|
|
3431
|
+
[icon]="item.icon"
|
|
3432
|
+
[title]="item.label"
|
|
3433
|
+
(buttonClick)="item.command(editor())"></ate-button>
|
|
3297
3434
|
}
|
|
3298
3435
|
}
|
|
3299
3436
|
</div>
|
|
@@ -3380,7 +3517,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
|
|
|
3380
3517
|
[anchorToText]="true" />
|
|
3381
3518
|
}
|
|
3382
3519
|
@if (bubbleMenuConfig().textColor) {
|
|
3383
|
-
<ate-color-picker
|
|
3520
|
+
<ate-color-picker
|
|
3521
|
+
mode="text"
|
|
3522
|
+
[editor]="editor()"
|
|
3523
|
+
[disabled]="!state().can.setColor"
|
|
3524
|
+
[anchorToText]="true" />
|
|
3384
3525
|
}
|
|
3385
3526
|
@if (bubbleMenuConfig().link) {
|
|
3386
3527
|
<ate-button
|
|
@@ -3392,7 +3533,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
|
|
|
3392
3533
|
}
|
|
3393
3534
|
@if (bubbleMenuConfig().custom?.length) {
|
|
3394
3535
|
@for (item of bubbleMenuConfig().custom; track item.key) {
|
|
3395
|
-
<ate-button
|
|
3536
|
+
<ate-button
|
|
3537
|
+
[icon]="item.icon"
|
|
3538
|
+
[title]="item.label"
|
|
3539
|
+
(buttonClick)="item.command(editor())"></ate-button>
|
|
3396
3540
|
}
|
|
3397
3541
|
}
|
|
3398
3542
|
</div>
|
|
@@ -3438,8 +3582,9 @@ class AteImageBubbleMenuComponent extends AteBaseBubbleMenu {
|
|
|
3438
3582
|
}
|
|
3439
3583
|
getSelectionRect() {
|
|
3440
3584
|
const ed = this.editor();
|
|
3441
|
-
if (!ed)
|
|
3585
|
+
if (!ed) {
|
|
3442
3586
|
return new DOMRect(0, 0, 0, 0);
|
|
3587
|
+
}
|
|
3443
3588
|
const { from } = ed.state.selection;
|
|
3444
3589
|
try {
|
|
3445
3590
|
// 1. Direct ProseMirror approach: get DOM node at position
|
|
@@ -3448,8 +3593,9 @@ class AteImageBubbleMenuComponent extends AteBaseBubbleMenu {
|
|
|
3448
3593
|
// If it's a resizable container, look for the image inside
|
|
3449
3594
|
if (dom.classList.contains("resizable-image-container")) {
|
|
3450
3595
|
const img = dom.querySelector("img");
|
|
3451
|
-
if (img)
|
|
3596
|
+
if (img) {
|
|
3452
3597
|
return img.getBoundingClientRect();
|
|
3598
|
+
}
|
|
3453
3599
|
}
|
|
3454
3600
|
return dom.getBoundingClientRect();
|
|
3455
3601
|
}
|
|
@@ -3489,8 +3635,9 @@ class AteImageBubbleMenuComponent extends AteBaseBubbleMenu {
|
|
|
3489
3635
|
}
|
|
3490
3636
|
async changeImage() {
|
|
3491
3637
|
const ed = this.editor();
|
|
3492
|
-
if (!ed)
|
|
3638
|
+
if (!ed) {
|
|
3493
3639
|
return;
|
|
3640
|
+
}
|
|
3494
3641
|
try {
|
|
3495
3642
|
// Use dedicated method to replace an existing image
|
|
3496
3643
|
await this.imageService.selectAndReplaceImage(ed, this.imageUpload());
|
|
@@ -3653,8 +3800,9 @@ class AteTableBubbleMenuComponent extends AteBaseBubbleMenu {
|
|
|
3653
3800
|
}
|
|
3654
3801
|
getSelectionRect() {
|
|
3655
3802
|
const ed = this.editor();
|
|
3656
|
-
if (!ed)
|
|
3803
|
+
if (!ed) {
|
|
3657
3804
|
return new DOMRect(0, 0, 0, 0);
|
|
3805
|
+
}
|
|
3658
3806
|
const { from } = ed.state.selection;
|
|
3659
3807
|
try {
|
|
3660
3808
|
// 1. Direct ProseMirror approach: get DOM node at position
|
|
@@ -3674,8 +3822,9 @@ class AteTableBubbleMenuComponent extends AteBaseBubbleMenu {
|
|
|
3674
3822
|
// Search for table element at these coordinates
|
|
3675
3823
|
const element = document.elementFromPoint(coords.left, coords.top);
|
|
3676
3824
|
const table = element?.closest("table");
|
|
3677
|
-
if (table)
|
|
3825
|
+
if (table) {
|
|
3678
3826
|
return table.getBoundingClientRect();
|
|
3827
|
+
}
|
|
3679
3828
|
}
|
|
3680
3829
|
// 3. Ultimate fallback if selection is ambiguous
|
|
3681
3830
|
const activeTable = ed.view.dom.querySelector("table.selected, table:has(.selected), table:has(.selected-cell), table:has(.selected-node)");
|
|
@@ -3893,8 +4042,9 @@ class AteCellBubbleMenuComponent extends AteBaseBubbleMenu {
|
|
|
3893
4042
|
}
|
|
3894
4043
|
getSelectionRect() {
|
|
3895
4044
|
const ed = this.editor();
|
|
3896
|
-
if (!ed)
|
|
4045
|
+
if (!ed) {
|
|
3897
4046
|
return new DOMRect(0, 0, 0, 0);
|
|
4047
|
+
}
|
|
3898
4048
|
// CellSelection
|
|
3899
4049
|
const selection = ed.state.selection;
|
|
3900
4050
|
// 1. Multiple cells selected
|
|
@@ -3902,8 +4052,9 @@ class AteCellBubbleMenuComponent extends AteBaseBubbleMenu {
|
|
|
3902
4052
|
const cells = [];
|
|
3903
4053
|
// Try to find all selected cell nodes
|
|
3904
4054
|
ed.view.dom.querySelectorAll(".selectedCell").forEach(el => {
|
|
3905
|
-
if (el instanceof HTMLElement)
|
|
4055
|
+
if (el instanceof HTMLElement) {
|
|
3906
4056
|
cells.push(el);
|
|
4057
|
+
}
|
|
3907
4058
|
});
|
|
3908
4059
|
if (cells.length > 0) {
|
|
3909
4060
|
let top = Infinity, bottom = -Infinity, left = Infinity, right = -Infinity;
|
|
@@ -3989,21 +4140,30 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
|
|
|
3989
4140
|
}]
|
|
3990
4141
|
}], propDecorators: { config: [{ type: i0.Input, args: [{ isSignal: true, alias: "config", required: false }] }] } });
|
|
3991
4142
|
|
|
3992
|
-
|
|
4143
|
+
/**
|
|
4144
|
+
* Base abstract class for Sub-Bubble Menus (Link, Color).
|
|
4145
|
+
* These menus are "sub-menus" of the main bubble menu or triggered/anchored by specific UI elements.
|
|
4146
|
+
*/
|
|
4147
|
+
class AteBaseSubBubbleMenu {
|
|
3993
4148
|
constructor() {
|
|
3994
4149
|
this.i18nService = inject(AteI18nService);
|
|
3995
4150
|
this.editorCommands = inject(AteEditorCommandsService);
|
|
3996
|
-
|
|
3997
|
-
this.t = this.i18nService.bubbleMenu;
|
|
3998
|
-
this.common = this.i18nService.common;
|
|
3999
|
-
this.state = this.editorCommands.editorState;
|
|
4151
|
+
// Core Inputs
|
|
4000
4152
|
this.editor = input.required(...(ngDevMode ? [{ debugName: "editor" }] : []));
|
|
4153
|
+
// Required viewChild for the menu container
|
|
4154
|
+
this.menuRef = viewChild.required("menuRef");
|
|
4155
|
+
// Internal State
|
|
4001
4156
|
this.tippyInstance = null;
|
|
4002
4157
|
this.updateTimeout = null;
|
|
4003
|
-
|
|
4158
|
+
// Reactive state alias for templates
|
|
4159
|
+
this.state = this.editorCommands.editorState;
|
|
4160
|
+
/**
|
|
4161
|
+
* Core logic to decide whether to show or hide the menu.
|
|
4162
|
+
*/
|
|
4004
4163
|
this.updateMenu = () => {
|
|
4005
|
-
if (this.updateTimeout)
|
|
4164
|
+
if (this.updateTimeout) {
|
|
4006
4165
|
clearTimeout(this.updateTimeout);
|
|
4166
|
+
}
|
|
4007
4167
|
this.updateTimeout = setTimeout(() => {
|
|
4008
4168
|
if (this.shouldShow()) {
|
|
4009
4169
|
this.showTippy();
|
|
@@ -4013,24 +4173,11 @@ class AteLinkBubbleMenuComponent {
|
|
|
4013
4173
|
}
|
|
4014
4174
|
}, 10);
|
|
4015
4175
|
};
|
|
4016
|
-
// Reactive effect for URL sync and focus
|
|
4017
|
-
effect(() => {
|
|
4018
|
-
const state = this.state();
|
|
4019
|
-
const isInteracting = this.linkSvc.isInteracting();
|
|
4020
|
-
const currentLinkHref = state.marks.linkHref || "";
|
|
4021
|
-
// SYNC LOGIC:
|
|
4022
|
-
// If we are NOT currently typing (interacting),
|
|
4023
|
-
// always keep the input in sync with the current editor selection.
|
|
4024
|
-
if (!isInteracting) {
|
|
4025
|
-
this.editUrl.set(currentLinkHref);
|
|
4026
|
-
}
|
|
4027
|
-
});
|
|
4028
4176
|
// Reactive effect for menu updates (re-positioning)
|
|
4029
4177
|
effect(() => {
|
|
4178
|
+
// Monitor editor state and specific sub-menu states
|
|
4030
4179
|
this.state();
|
|
4031
|
-
this.
|
|
4032
|
-
this.linkSvc.menuTrigger();
|
|
4033
|
-
this.linkSvc.isInteracting();
|
|
4180
|
+
this.onStateChange();
|
|
4034
4181
|
this.updateMenu();
|
|
4035
4182
|
});
|
|
4036
4183
|
}
|
|
@@ -4038,21 +4185,28 @@ class AteLinkBubbleMenuComponent {
|
|
|
4038
4185
|
this.initTippy();
|
|
4039
4186
|
}
|
|
4040
4187
|
ngOnDestroy() {
|
|
4041
|
-
if (this.updateTimeout)
|
|
4188
|
+
if (this.updateTimeout) {
|
|
4042
4189
|
clearTimeout(this.updateTimeout);
|
|
4190
|
+
}
|
|
4043
4191
|
if (this.tippyInstance) {
|
|
4044
4192
|
this.tippyInstance.destroy();
|
|
4045
4193
|
this.tippyInstance = null;
|
|
4046
4194
|
}
|
|
4047
4195
|
}
|
|
4196
|
+
/**
|
|
4197
|
+
* Initializes the Tippy instance with sub-menu defaults.
|
|
4198
|
+
*/
|
|
4048
4199
|
initTippy() {
|
|
4049
|
-
if (!this.menuRef?.nativeElement) {
|
|
4200
|
+
if (!this.menuRef()?.nativeElement) {
|
|
4050
4201
|
setTimeout(() => this.initTippy(), 50);
|
|
4051
4202
|
return;
|
|
4052
4203
|
}
|
|
4053
4204
|
const ed = this.editor();
|
|
4205
|
+
if (this.tippyInstance) {
|
|
4206
|
+
this.tippyInstance.destroy();
|
|
4207
|
+
}
|
|
4054
4208
|
this.tippyInstance = tippy(document.body, {
|
|
4055
|
-
content: this.menuRef.nativeElement,
|
|
4209
|
+
content: this.menuRef().nativeElement,
|
|
4056
4210
|
trigger: "manual",
|
|
4057
4211
|
placement: "bottom-start",
|
|
4058
4212
|
appendTo: () => ed.options.element,
|
|
@@ -4075,41 +4229,76 @@ class AteLinkBubbleMenuComponent {
|
|
|
4075
4229
|
},
|
|
4076
4230
|
],
|
|
4077
4231
|
},
|
|
4078
|
-
onShow:
|
|
4079
|
-
|
|
4080
|
-
// The user can click the input if they want to type, but for swatches/preview,
|
|
4081
|
-
// staying in the editor is better for UX.
|
|
4082
|
-
},
|
|
4083
|
-
onHide: () => {
|
|
4084
|
-
// Clear trigger only AFTER the menu is hidden to maintain anchor stability during animation
|
|
4085
|
-
this.linkSvc.done();
|
|
4086
|
-
this.linkSvc.close();
|
|
4087
|
-
},
|
|
4232
|
+
onShow: instance => this.onTippyShow(instance),
|
|
4233
|
+
onHide: instance => this.onTippyHide(instance),
|
|
4088
4234
|
});
|
|
4089
4235
|
this.updateMenu();
|
|
4090
4236
|
}
|
|
4237
|
+
/**
|
|
4238
|
+
* Helper to show the Tippy instance with updated positioning.
|
|
4239
|
+
*/
|
|
4091
4240
|
showTippy() {
|
|
4092
4241
|
if (this.tippyInstance) {
|
|
4093
4242
|
this.tippyInstance.setProps({ getReferenceClientRect: () => this.getSelectionRect() });
|
|
4094
4243
|
this.tippyInstance.show();
|
|
4095
4244
|
}
|
|
4096
4245
|
}
|
|
4246
|
+
/**
|
|
4247
|
+
* Helper to hide the Tippy instance.
|
|
4248
|
+
*/
|
|
4097
4249
|
hideTippy() {
|
|
4098
4250
|
this.tippyInstance?.hide();
|
|
4099
4251
|
}
|
|
4100
|
-
|
|
4101
|
-
|
|
4102
|
-
|
|
4103
|
-
|
|
4104
|
-
|
|
4252
|
+
/**
|
|
4253
|
+
* Optional hook called when Tippy is about to be shown.
|
|
4254
|
+
*/
|
|
4255
|
+
onTippyShow(_instance) {
|
|
4256
|
+
/* empty */
|
|
4105
4257
|
}
|
|
4106
|
-
|
|
4107
|
-
|
|
4258
|
+
/**
|
|
4259
|
+
* Optional hook called when Tippy is about to be hidden.
|
|
4260
|
+
*/
|
|
4261
|
+
onTippyHide(_instance) {
|
|
4262
|
+
/* empty */
|
|
4263
|
+
}
|
|
4264
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: AteBaseSubBubbleMenu, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
4265
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.2.0", version: "20.3.16", type: AteBaseSubBubbleMenu, isStandalone: true, inputs: { editor: { classPropertyName: "editor", publicName: "editor", isSignal: true, isRequired: true, transformFunction: null } }, viewQueries: [{ propertyName: "menuRef", first: true, predicate: ["menuRef"], descendants: true, isSignal: true }], ngImport: i0 }); }
|
|
4266
|
+
}
|
|
4267
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: AteBaseSubBubbleMenu, decorators: [{
|
|
4268
|
+
type: Directive
|
|
4269
|
+
}], ctorParameters: () => [], propDecorators: { editor: [{ type: i0.Input, args: [{ isSignal: true, alias: "editor", required: true }] }], menuRef: [{ type: i0.ViewChild, args: ["menuRef", { isSignal: true }] }] } });
|
|
4270
|
+
|
|
4271
|
+
class AteLinkBubbleMenuComponent extends AteBaseSubBubbleMenu {
|
|
4272
|
+
constructor() {
|
|
4273
|
+
super();
|
|
4274
|
+
this.linkSvc = inject(AteLinkService);
|
|
4275
|
+
this.t = this.i18nService.bubbleMenu;
|
|
4276
|
+
this.common = this.i18nService.common;
|
|
4277
|
+
this.linkInput = viewChild("linkInput", ...(ngDevMode ? [{ debugName: "linkInput" }] : []));
|
|
4278
|
+
this.editUrl = signal("", ...(ngDevMode ? [{ debugName: "editUrl" }] : []));
|
|
4279
|
+
// Reactive effect for URL sync
|
|
4280
|
+
effect(() => {
|
|
4281
|
+
const state = this.state();
|
|
4282
|
+
const isInteracting = this.linkSvc.isInteracting();
|
|
4283
|
+
const currentLinkHref = state.marks.linkHref || "";
|
|
4284
|
+
// SYNC LOGIC:
|
|
4285
|
+
// If we are NOT currently typing (interacting),
|
|
4286
|
+
// always keep the input in sync with the current editor selection.
|
|
4287
|
+
if (!isInteracting) {
|
|
4288
|
+
this.editUrl.set(currentLinkHref);
|
|
4289
|
+
}
|
|
4290
|
+
});
|
|
4291
|
+
}
|
|
4292
|
+
onStateChange() {
|
|
4293
|
+
this.linkSvc.editMode();
|
|
4294
|
+
this.linkSvc.menuTrigger();
|
|
4295
|
+
this.linkSvc.isInteracting();
|
|
4108
4296
|
}
|
|
4109
4297
|
shouldShow() {
|
|
4110
4298
|
const { selection, marks, isEditable, isFocused } = this.state();
|
|
4111
|
-
if (!isEditable)
|
|
4299
|
+
if (!isEditable) {
|
|
4112
4300
|
return false;
|
|
4301
|
+
}
|
|
4113
4302
|
// Show if explicitly in edit mode (from toolbar/bubble menu) or interacting with input
|
|
4114
4303
|
if (this.linkSvc.editMode() || this.linkSvc.isInteracting()) {
|
|
4115
4304
|
return true;
|
|
@@ -4120,14 +4309,16 @@ class AteLinkBubbleMenuComponent {
|
|
|
4120
4309
|
getSelectionRect() {
|
|
4121
4310
|
const trigger = this.linkSvc.menuTrigger();
|
|
4122
4311
|
const ed = this.editor();
|
|
4123
|
-
if (!ed)
|
|
4312
|
+
if (!ed) {
|
|
4124
4313
|
return new DOMRect(0, 0, 0, 0);
|
|
4314
|
+
}
|
|
4125
4315
|
// 1. If we have a stable trigger from service (toolbar or parent menu), anchor to it
|
|
4126
4316
|
if (trigger) {
|
|
4127
4317
|
const rect = trigger.getBoundingClientRect();
|
|
4128
4318
|
// Only use if it's still visible/in DOM (width > 0)
|
|
4129
|
-
if (rect.width > 0)
|
|
4319
|
+
if (rect.width > 0) {
|
|
4130
4320
|
return rect;
|
|
4321
|
+
}
|
|
4131
4322
|
}
|
|
4132
4323
|
// 2. Otherwise (bubble menu / relay), anchor to text selection
|
|
4133
4324
|
const { from } = ed.state.selection;
|
|
@@ -4135,8 +4326,9 @@ class AteLinkBubbleMenuComponent {
|
|
|
4135
4326
|
const { node } = ed.view.domAtPos(from);
|
|
4136
4327
|
const element = node instanceof Element ? node : node.parentElement;
|
|
4137
4328
|
const linkElement = element?.closest("a");
|
|
4138
|
-
if (linkElement)
|
|
4329
|
+
if (linkElement) {
|
|
4139
4330
|
return linkElement.getBoundingClientRect();
|
|
4331
|
+
}
|
|
4140
4332
|
}
|
|
4141
4333
|
catch (_e) {
|
|
4142
4334
|
/* ignore */
|
|
@@ -4146,13 +4338,22 @@ class AteLinkBubbleMenuComponent {
|
|
|
4146
4338
|
if (selection && selection.rangeCount > 0) {
|
|
4147
4339
|
const range = selection.getRangeAt(0);
|
|
4148
4340
|
const rect = range.getBoundingClientRect();
|
|
4149
|
-
if (rect.width > 0 && rect.height > 0)
|
|
4341
|
+
if (rect.width > 0 && rect.height > 0) {
|
|
4150
4342
|
return rect;
|
|
4343
|
+
}
|
|
4151
4344
|
}
|
|
4152
4345
|
// Final fallback to coordinates at cursor
|
|
4153
4346
|
const { top, bottom, left, right } = ed.view.coordsAtPos(from);
|
|
4154
4347
|
return new DOMRect(left, top, right - left, bottom - top);
|
|
4155
4348
|
}
|
|
4349
|
+
onTippyHide() {
|
|
4350
|
+
// Clear trigger only AFTER the menu is hidden to maintain anchor stability during animation
|
|
4351
|
+
this.linkSvc.done();
|
|
4352
|
+
this.linkSvc.close();
|
|
4353
|
+
}
|
|
4354
|
+
currentUrl() {
|
|
4355
|
+
return this.state().marks.linkHref || "";
|
|
4356
|
+
}
|
|
4156
4357
|
onMouseDown(event) {
|
|
4157
4358
|
event.stopPropagation();
|
|
4158
4359
|
const target = event.target;
|
|
@@ -4173,8 +4374,9 @@ class AteLinkBubbleMenuComponent {
|
|
|
4173
4374
|
event.preventDefault();
|
|
4174
4375
|
event.stopPropagation();
|
|
4175
4376
|
const url = this.currentUrl();
|
|
4176
|
-
if (url)
|
|
4377
|
+
if (url) {
|
|
4177
4378
|
window.open(url, "_blank", "noopener,noreferrer");
|
|
4379
|
+
}
|
|
4178
4380
|
}
|
|
4179
4381
|
onRemove(event) {
|
|
4180
4382
|
event.preventDefault();
|
|
@@ -4202,7 +4404,7 @@ class AteLinkBubbleMenuComponent {
|
|
|
4202
4404
|
this.hideTippy();
|
|
4203
4405
|
}
|
|
4204
4406
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: AteLinkBubbleMenuComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
4205
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.
|
|
4407
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "20.3.16", type: AteLinkBubbleMenuComponent, isStandalone: true, selector: "ate-link-bubble-menu", viewQueries: [{ propertyName: "linkInput", first: true, predicate: ["linkInput"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: `
|
|
4206
4408
|
<div
|
|
4207
4409
|
#menuRef
|
|
4208
4410
|
class="bubble-menu"
|
|
@@ -4303,13 +4505,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
|
|
|
4303
4505
|
</div>
|
|
4304
4506
|
</div>
|
|
4305
4507
|
`, styles: [".link-input-row{display:flex;align-items:center;gap:6px}.url-input-container{flex:1;display:flex;align-items:center;background:var(--ate-surface-secondary, #f8fafc);border:1px solid var(--ate-border, #e2e8f0);border-radius:8px;padding:0 10px;height:32px;transition:all .15s ease}.url-input-container:focus-within{border-color:var(--ate-primary, #3b82f6);background:var(--ate-surface, #ffffff);box-shadow:0 0 0 2px var(--ate-primary-light, rgba(59, 130, 246, .1))}.icon-link{font-size:16px;color:var(--ate-text-muted, #94a3b8);margin-right:6px}.url-field{background:transparent;border:none;outline:none;color:var(--ate-text, #1e293b);font-size:13px;width:100%;font-family:inherit}.action-buttons{display:flex;align-items:center;gap:2px}\n"] }]
|
|
4306
|
-
}], ctorParameters: () => [], propDecorators: {
|
|
4307
|
-
type: ViewChild,
|
|
4308
|
-
args: ["linkInput"]
|
|
4309
|
-
}], menuRef: [{
|
|
4310
|
-
type: ViewChild,
|
|
4311
|
-
args: ["menuRef", { static: false }]
|
|
4312
|
-
}] } });
|
|
4508
|
+
}], ctorParameters: () => [], propDecorators: { linkInput: [{ type: i0.ViewChild, args: ["linkInput", { isSignal: true }] }] } });
|
|
4313
4509
|
|
|
4314
4510
|
const PRESET_COLORS = [
|
|
4315
4511
|
"#000000",
|
|
@@ -4325,36 +4521,19 @@ const PRESET_COLORS = [
|
|
|
4325
4521
|
"#9C27B0",
|
|
4326
4522
|
"#E91E63",
|
|
4327
4523
|
];
|
|
4328
|
-
class AteColorBubbleMenuComponent {
|
|
4524
|
+
class AteColorBubbleMenuComponent extends AteBaseSubBubbleMenu {
|
|
4329
4525
|
constructor() {
|
|
4330
|
-
|
|
4331
|
-
this.editorCommands = inject(AteEditorCommandsService);
|
|
4526
|
+
super(...arguments);
|
|
4332
4527
|
this.colorPickerSvc = inject(AteColorPickerService);
|
|
4333
4528
|
this.t = this.i18nService.toolbar;
|
|
4334
4529
|
this.common = this.i18nService.common;
|
|
4335
|
-
this.state = this.editorCommands.editorState;
|
|
4336
4530
|
this.presets = PRESET_COLORS;
|
|
4337
|
-
this.editor = input.required(...(ngDevMode ? [{ debugName: "editor" }] : []));
|
|
4338
4531
|
this.colorInputRef = viewChild("colorInput", ...(ngDevMode ? [{ debugName: "colorInputRef" }] : []));
|
|
4339
|
-
this.tippyInstance = null;
|
|
4340
|
-
this.updateTimeout = null;
|
|
4341
4532
|
/**
|
|
4342
4533
|
* LOCAL MODE: We lock the mode when the menu is shown to avoid race conditions
|
|
4343
4534
|
* where the parent (bubble menu) clears the global signal before we apply the color.
|
|
4344
4535
|
*/
|
|
4345
4536
|
this.activeMode = signal("text", ...(ngDevMode ? [{ debugName: "activeMode" }] : []));
|
|
4346
|
-
this.updateMenu = () => {
|
|
4347
|
-
if (this.updateTimeout)
|
|
4348
|
-
clearTimeout(this.updateTimeout);
|
|
4349
|
-
this.updateTimeout = setTimeout(() => {
|
|
4350
|
-
if (this.shouldShow()) {
|
|
4351
|
-
this.showTippy();
|
|
4352
|
-
}
|
|
4353
|
-
else {
|
|
4354
|
-
this.hideTippy();
|
|
4355
|
-
}
|
|
4356
|
-
}, 10);
|
|
4357
|
-
};
|
|
4358
4537
|
this.currentColor = computed(() => {
|
|
4359
4538
|
const marks = this.state().marks;
|
|
4360
4539
|
const color = this.activeMode() === "text" ? marks.computedColor : marks.computedBackground;
|
|
@@ -4364,85 +4543,17 @@ class AteColorBubbleMenuComponent {
|
|
|
4364
4543
|
const color = this.currentColor();
|
|
4365
4544
|
return color.replace("#", "").toUpperCase();
|
|
4366
4545
|
}, ...(ngDevMode ? [{ debugName: "hexValue" }] : []));
|
|
4367
|
-
effect(() => {
|
|
4368
|
-
this.state();
|
|
4369
|
-
this.colorPickerSvc.editMode();
|
|
4370
|
-
this.colorPickerSvc.menuTrigger();
|
|
4371
|
-
this.colorPickerSvc.isInteracting();
|
|
4372
|
-
this.updateMenu();
|
|
4373
|
-
});
|
|
4374
|
-
}
|
|
4375
|
-
ngOnInit() {
|
|
4376
|
-
this.initTippy();
|
|
4377
|
-
}
|
|
4378
|
-
ngOnDestroy() {
|
|
4379
|
-
if (this.updateTimeout)
|
|
4380
|
-
clearTimeout(this.updateTimeout);
|
|
4381
|
-
if (this.tippyInstance) {
|
|
4382
|
-
this.tippyInstance.destroy();
|
|
4383
|
-
this.tippyInstance = null;
|
|
4384
|
-
}
|
|
4385
|
-
}
|
|
4386
|
-
initTippy() {
|
|
4387
|
-
if (!this.menuRef?.nativeElement) {
|
|
4388
|
-
setTimeout(() => this.initTippy(), 50);
|
|
4389
|
-
return;
|
|
4390
|
-
}
|
|
4391
|
-
const ed = this.editor();
|
|
4392
|
-
this.tippyInstance = tippy(document.body, {
|
|
4393
|
-
content: this.menuRef.nativeElement,
|
|
4394
|
-
trigger: "manual",
|
|
4395
|
-
placement: "bottom-start",
|
|
4396
|
-
appendTo: () => ed.options.element,
|
|
4397
|
-
interactive: true,
|
|
4398
|
-
arrow: false,
|
|
4399
|
-
offset: [0, 8],
|
|
4400
|
-
hideOnClick: true,
|
|
4401
|
-
plugins: [sticky],
|
|
4402
|
-
sticky: false,
|
|
4403
|
-
getReferenceClientRect: () => this.getSelectionRect(),
|
|
4404
|
-
popperOptions: {
|
|
4405
|
-
modifiers: [
|
|
4406
|
-
{
|
|
4407
|
-
name: "preventOverflow",
|
|
4408
|
-
options: { boundary: ed.options.element, padding: 8 },
|
|
4409
|
-
},
|
|
4410
|
-
{
|
|
4411
|
-
name: "flip",
|
|
4412
|
-
options: { fallbackPlacements: ["top-start", "bottom-end", "top-end"] },
|
|
4413
|
-
},
|
|
4414
|
-
],
|
|
4415
|
-
},
|
|
4416
|
-
onShow: () => {
|
|
4417
|
-
// 1. Lock the mode immediately to be immune to external signal changes
|
|
4418
|
-
const currentMode = this.colorPickerSvc.editMode() || "text";
|
|
4419
|
-
this.activeMode.set(currentMode);
|
|
4420
|
-
// 2. Capture selection for the command fallback
|
|
4421
|
-
this.colorPickerSvc.captureSelection(this.editor());
|
|
4422
|
-
// Note: We don't auto-focus the Hex input anymore to keep the
|
|
4423
|
-
// visual selection (blue highlight) active in the editor.
|
|
4424
|
-
},
|
|
4425
|
-
onHide: () => {
|
|
4426
|
-
// Clear trigger only AFTER the menu is hidden to maintain anchor stability during animation
|
|
4427
|
-
this.colorPickerSvc.done();
|
|
4428
|
-
this.colorPickerSvc.close();
|
|
4429
|
-
},
|
|
4430
|
-
});
|
|
4431
|
-
this.updateMenu();
|
|
4432
4546
|
}
|
|
4433
|
-
|
|
4434
|
-
|
|
4435
|
-
|
|
4436
|
-
|
|
4437
|
-
}
|
|
4438
|
-
}
|
|
4439
|
-
hideTippy() {
|
|
4440
|
-
this.tippyInstance?.hide();
|
|
4547
|
+
onStateChange() {
|
|
4548
|
+
this.colorPickerSvc.editMode();
|
|
4549
|
+
this.colorPickerSvc.menuTrigger();
|
|
4550
|
+
this.colorPickerSvc.isInteracting();
|
|
4441
4551
|
}
|
|
4442
4552
|
shouldShow() {
|
|
4443
4553
|
const { isEditable } = this.state();
|
|
4444
|
-
if (!isEditable)
|
|
4554
|
+
if (!isEditable) {
|
|
4445
4555
|
return false;
|
|
4556
|
+
}
|
|
4446
4557
|
if (this.colorPickerSvc.editMode() !== null || this.colorPickerSvc.isInteracting()) {
|
|
4447
4558
|
return true;
|
|
4448
4559
|
}
|
|
@@ -4451,14 +4562,16 @@ class AteColorBubbleMenuComponent {
|
|
|
4451
4562
|
getSelectionRect() {
|
|
4452
4563
|
const trigger = this.colorPickerSvc.menuTrigger();
|
|
4453
4564
|
const ed = this.editor();
|
|
4454
|
-
if (!ed)
|
|
4565
|
+
if (!ed) {
|
|
4455
4566
|
return new DOMRect(0, 0, 0, 0);
|
|
4567
|
+
}
|
|
4456
4568
|
// 1. If we have a stable trigger from service (toolbar or parent menu), anchor to it
|
|
4457
4569
|
if (trigger) {
|
|
4458
4570
|
const rect = trigger.getBoundingClientRect();
|
|
4459
4571
|
// Only use if it's still visible/in DOM (width > 0)
|
|
4460
|
-
if (rect.width > 0)
|
|
4572
|
+
if (rect.width > 0) {
|
|
4461
4573
|
return rect;
|
|
4574
|
+
}
|
|
4462
4575
|
}
|
|
4463
4576
|
// 2. Otherwise (bubble menu / relay), anchor to text selection
|
|
4464
4577
|
const { from } = ed.state.selection;
|
|
@@ -4466,8 +4579,9 @@ class AteColorBubbleMenuComponent {
|
|
|
4466
4579
|
const { node } = ed.view.domAtPos(from);
|
|
4467
4580
|
const element = node instanceof Element ? node : node.parentElement;
|
|
4468
4581
|
const colorElement = element?.closest('[style*="color"], [style*="background"], mark');
|
|
4469
|
-
if (colorElement)
|
|
4582
|
+
if (colorElement) {
|
|
4470
4583
|
return colorElement.getBoundingClientRect();
|
|
4584
|
+
}
|
|
4471
4585
|
}
|
|
4472
4586
|
catch (_e) {
|
|
4473
4587
|
/* ignore */
|
|
@@ -4477,15 +4591,29 @@ class AteColorBubbleMenuComponent {
|
|
|
4477
4591
|
if (selection && selection.rangeCount > 0) {
|
|
4478
4592
|
const range = selection.getRangeAt(0);
|
|
4479
4593
|
const rect = range.getBoundingClientRect();
|
|
4480
|
-
if (rect.width > 0 && rect.height > 0)
|
|
4594
|
+
if (rect.width > 0 && rect.height > 0) {
|
|
4481
4595
|
return rect;
|
|
4596
|
+
}
|
|
4482
4597
|
}
|
|
4483
4598
|
// Final fallback to coordinates at cursor
|
|
4484
4599
|
const { top, bottom, left, right } = ed.view.coordsAtPos(from);
|
|
4485
4600
|
return new DOMRect(left, top, right - left, bottom - top);
|
|
4486
4601
|
}
|
|
4602
|
+
onTippyShow(_instance) {
|
|
4603
|
+
// 1. Lock the mode immediately to be immune to external signal changes
|
|
4604
|
+
const currentMode = this.colorPickerSvc.editMode() || "text";
|
|
4605
|
+
this.activeMode.set(currentMode);
|
|
4606
|
+
// 2. Capture selection for the command fallback
|
|
4607
|
+
this.colorPickerSvc.captureSelection(this.editor());
|
|
4608
|
+
}
|
|
4609
|
+
onTippyHide(_instance) {
|
|
4610
|
+
// Clear trigger only AFTER the menu is hidden to maintain anchor stability during animation
|
|
4611
|
+
this.colorPickerSvc.done();
|
|
4612
|
+
this.colorPickerSvc.close();
|
|
4613
|
+
}
|
|
4487
4614
|
isColorActive(color) {
|
|
4488
|
-
return this.colorPickerSvc.normalizeColor(this.currentColor()) ===
|
|
4615
|
+
return (this.colorPickerSvc.normalizeColor(this.currentColor()) ===
|
|
4616
|
+
this.colorPickerSvc.normalizeColor(color));
|
|
4489
4617
|
}
|
|
4490
4618
|
applyColor(color, addToHistory = true, event) {
|
|
4491
4619
|
if (event) {
|
|
@@ -4513,8 +4641,9 @@ class AteColorBubbleMenuComponent {
|
|
|
4513
4641
|
onHexInput(event) {
|
|
4514
4642
|
const input = event.target;
|
|
4515
4643
|
let value = input.value.trim();
|
|
4516
|
-
if (!value.startsWith("#"))
|
|
4644
|
+
if (!value.startsWith("#")) {
|
|
4517
4645
|
value = "#" + value;
|
|
4646
|
+
}
|
|
4518
4647
|
if (/^#?[0-9A-Fa-f]{3,6}$/.test(value)) {
|
|
4519
4648
|
this.applyColor(value, false);
|
|
4520
4649
|
}
|
|
@@ -4522,8 +4651,9 @@ class AteColorBubbleMenuComponent {
|
|
|
4522
4651
|
onHexChange(event) {
|
|
4523
4652
|
const input = event.target;
|
|
4524
4653
|
let value = input.value.trim();
|
|
4525
|
-
if (!value.startsWith("#"))
|
|
4654
|
+
if (!value.startsWith("#")) {
|
|
4526
4655
|
value = "#" + value;
|
|
4656
|
+
}
|
|
4527
4657
|
if (/^#?[0-9A-Fa-f]{3,6}$/.test(value)) {
|
|
4528
4658
|
this.applyColor(value, true, event);
|
|
4529
4659
|
}
|
|
@@ -4570,8 +4700,8 @@ class AteColorBubbleMenuComponent {
|
|
|
4570
4700
|
this.updateMenu();
|
|
4571
4701
|
}, 150);
|
|
4572
4702
|
}
|
|
4573
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: AteColorBubbleMenuComponent, deps:
|
|
4574
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: AteColorBubbleMenuComponent, isStandalone: true, selector: "ate-color-bubble-menu",
|
|
4703
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: AteColorBubbleMenuComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
|
|
4704
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: AteColorBubbleMenuComponent, isStandalone: true, selector: "ate-color-bubble-menu", viewQueries: [{ propertyName: "colorInputRef", first: true, predicate: ["colorInput"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: `
|
|
4575
4705
|
<div
|
|
4576
4706
|
#menuRef
|
|
4577
4707
|
class="bubble-menu color-bubble-menu"
|
|
@@ -4732,10 +4862,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
|
|
|
4732
4862
|
</div>
|
|
4733
4863
|
</div>
|
|
4734
4864
|
`, styles: [".color-picker-container{display:flex;flex-direction:column;gap:8px}.dropdown-row{display:flex;align-items:center;width:100%}.dropdown-row.presets{justify-content:center}.dropdown-row.controls{gap:8px;justify-content:space-between;padding-top:4px;border-top:1px solid var(--ate-border, #e2e8f0)}.color-grid{display:grid;grid-template-columns:repeat(12,1fr);gap:4px;width:100%}:host ::ng-deep .color-swatch-btn .ate-button{width:100%;aspect-ratio:1;height:auto;border-radius:4px;border:1px solid rgba(0,0,0,.1);padding:0}:host ::ng-deep .color-swatch-btn .ate-button.is-active{border-color:var(--ate-primary, #3b82f6);box-shadow:0 0 0 2px #3b82f64d}:host ::ng-deep .btn-native-picker-trigger .ate-button{color:#fff;text-shadow:0 1px 2px rgba(0,0,0,.2)}.divider-v{width:1px;height:24px;background:var(--ate-border, #e2e8f0)}.hex-input-wrapper{flex:1;display:flex;align-items:center;background:var(--ate-surface-secondary, #f8fafc);border:1px solid var(--ate-border, #e2e8f0);border-radius:8px;padding:0 10px;height:32px;transition:border-color .15s ease}.hex-input-wrapper:focus-within{border-color:var(--ate-primary, #3b82f6);background:var(--ate-menu-bg, #ffffff)}.hex-hash{color:var(--ate-text-muted, #94a3b8);font-family:monospace;font-size:.875rem}.hex-input{background:transparent;border:none;outline:none;color:var(--ate-text, #1e293b);font-family:monospace;font-size:.875rem;width:100%;padding-left:4px}.native-trigger-wrapper{position:relative;width:32px;height:32px}.hidden-native-input{position:absolute;inset:0;opacity:0;width:100%;height:100%;cursor:pointer}\n"] }]
|
|
4735
|
-
}],
|
|
4736
|
-
type: ViewChild,
|
|
4737
|
-
args: ["menuRef", { static: false }]
|
|
4738
|
-
}], colorInputRef: [{ type: i0.ViewChild, args: ["colorInput", { isSignal: true }] }] } });
|
|
4865
|
+
}], propDecorators: { colorInputRef: [{ type: i0.ViewChild, args: ["colorInput", { isSignal: true }] }] } });
|
|
4739
4866
|
|
|
4740
4867
|
/**
|
|
4741
4868
|
* Clés des commandes natives dans l'ordre d'affichage
|
|
@@ -4873,6 +5000,7 @@ class AteSlashCommandsComponent {
|
|
|
4873
5000
|
this.i18nService = inject(AteI18nService);
|
|
4874
5001
|
this.editor = input.required(...(ngDevMode ? [{ debugName: "editor" }] : []));
|
|
4875
5002
|
this.config = input(undefined, ...(ngDevMode ? [{ debugName: "config" }] : []));
|
|
5003
|
+
this.menuRef = viewChild.required("menuRef");
|
|
4876
5004
|
this.tippyInstance = null;
|
|
4877
5005
|
this.editorCommands = inject(AteEditorCommandsService);
|
|
4878
5006
|
// Local state
|
|
@@ -4903,8 +5031,9 @@ class AteSlashCommandsComponent {
|
|
|
4903
5031
|
}, ...(ngDevMode ? [{ debugName: "filteredCommands" }] : []));
|
|
4904
5032
|
this.updateMenu = () => {
|
|
4905
5033
|
const ed = this.editor();
|
|
4906
|
-
if (!ed)
|
|
5034
|
+
if (!ed) {
|
|
4907
5035
|
return;
|
|
5036
|
+
}
|
|
4908
5037
|
const { from } = ed.state.selection;
|
|
4909
5038
|
// Check if '/' was typed at the beginning of a line or after a space
|
|
4910
5039
|
const textBefore = ed.state.doc.textBetween(Math.max(0, from - 20), from, "\n");
|
|
@@ -4937,57 +5066,11 @@ class AteSlashCommandsComponent {
|
|
|
4937
5066
|
this.handleBlur = () => {
|
|
4938
5067
|
setTimeout(() => this.hideTippy(), 100);
|
|
4939
5068
|
};
|
|
4940
|
-
this.handleKeyDown = (event) => {
|
|
4941
|
-
// Only handle keys if menu is active
|
|
4942
|
-
if (!this.isActive || this.filteredCommands().length === 0) {
|
|
4943
|
-
return;
|
|
4944
|
-
}
|
|
4945
|
-
switch (event.key) {
|
|
4946
|
-
case "ArrowDown": {
|
|
4947
|
-
event.preventDefault();
|
|
4948
|
-
event.stopPropagation();
|
|
4949
|
-
const nextIndex = (this.selectedIndex() + 1) % this.filteredCommands().length;
|
|
4950
|
-
this.selectedIndex.set(nextIndex);
|
|
4951
|
-
this.scrollToSelected();
|
|
4952
|
-
break;
|
|
4953
|
-
}
|
|
4954
|
-
case "ArrowUp": {
|
|
4955
|
-
event.preventDefault();
|
|
4956
|
-
event.stopPropagation();
|
|
4957
|
-
const prevIndex = this.selectedIndex() === 0 ? this.filteredCommands().length - 1 : this.selectedIndex() - 1;
|
|
4958
|
-
this.selectedIndex.set(prevIndex);
|
|
4959
|
-
this.scrollToSelected();
|
|
4960
|
-
break;
|
|
4961
|
-
}
|
|
4962
|
-
case "Enter": {
|
|
4963
|
-
event.preventDefault();
|
|
4964
|
-
event.stopPropagation();
|
|
4965
|
-
const selectedCommand = this.filteredCommands()[this.selectedIndex()];
|
|
4966
|
-
if (selectedCommand) {
|
|
4967
|
-
this.executeCommand(selectedCommand);
|
|
4968
|
-
}
|
|
4969
|
-
break;
|
|
4970
|
-
}
|
|
4971
|
-
case "Escape": {
|
|
4972
|
-
event.preventDefault();
|
|
4973
|
-
event.stopPropagation();
|
|
4974
|
-
this.isActive = false;
|
|
4975
|
-
this.hideTippy();
|
|
4976
|
-
// Optional: remove the typed "/"
|
|
4977
|
-
const ed = this.editor();
|
|
4978
|
-
if (ed && this.slashRange) {
|
|
4979
|
-
const { tr } = ed.state;
|
|
4980
|
-
tr.delete(this.slashRange.from, this.slashRange.to);
|
|
4981
|
-
ed.view.dispatch(tr);
|
|
4982
|
-
}
|
|
4983
|
-
break;
|
|
4984
|
-
}
|
|
4985
|
-
}
|
|
4986
|
-
};
|
|
4987
5069
|
effect(() => {
|
|
4988
5070
|
const ed = this.editor();
|
|
4989
|
-
if (!ed)
|
|
5071
|
+
if (!ed) {
|
|
4990
5072
|
return;
|
|
5073
|
+
}
|
|
4991
5074
|
// Clean up old listeners
|
|
4992
5075
|
ed.off("selectionUpdate", this.updateMenu);
|
|
4993
5076
|
ed.off("transaction", this.updateMenu);
|
|
@@ -5020,11 +5103,11 @@ class AteSlashCommandsComponent {
|
|
|
5020
5103
|
}
|
|
5021
5104
|
}
|
|
5022
5105
|
initTippy() {
|
|
5023
|
-
if (!this.menuRef?.nativeElement) {
|
|
5106
|
+
if (!this.menuRef()?.nativeElement) {
|
|
5024
5107
|
setTimeout(() => this.initTippy(), 50);
|
|
5025
5108
|
return;
|
|
5026
5109
|
}
|
|
5027
|
-
const menuElement = this.menuRef.nativeElement;
|
|
5110
|
+
const menuElement = this.menuRef().nativeElement;
|
|
5028
5111
|
if (this.tippyInstance) {
|
|
5029
5112
|
this.tippyInstance.destroy();
|
|
5030
5113
|
}
|
|
@@ -5093,8 +5176,9 @@ class AteSlashCommandsComponent {
|
|
|
5093
5176
|
}
|
|
5094
5177
|
scrollToSelected() {
|
|
5095
5178
|
// Scroll to the selected element
|
|
5096
|
-
|
|
5097
|
-
|
|
5179
|
+
const menuEl = this.menuRef()?.nativeElement;
|
|
5180
|
+
if (menuEl) {
|
|
5181
|
+
const selectedItem = menuEl.querySelector(".slash-command-item.selected");
|
|
5098
5182
|
if (selectedItem) {
|
|
5099
5183
|
selectedItem.scrollIntoView({ block: "nearest", behavior: "smooth" });
|
|
5100
5184
|
}
|
|
@@ -5114,8 +5198,9 @@ class AteSlashCommandsComponent {
|
|
|
5114
5198
|
}
|
|
5115
5199
|
executeCommand(command) {
|
|
5116
5200
|
const ed = this.editor();
|
|
5117
|
-
if (!ed || !this.slashRange)
|
|
5201
|
+
if (!ed || !this.slashRange) {
|
|
5118
5202
|
return;
|
|
5203
|
+
}
|
|
5119
5204
|
// Remove slash text ("/")
|
|
5120
5205
|
const { tr } = ed.state;
|
|
5121
5206
|
tr.delete(this.slashRange.from, this.slashRange.to);
|
|
@@ -5150,7 +5235,9 @@ class AteSlashCommandsComponent {
|
|
|
5150
5235
|
}
|
|
5151
5236
|
case "ArrowUp": {
|
|
5152
5237
|
event.preventDefault();
|
|
5153
|
-
const prevIndex = this.selectedIndex() === 0
|
|
5238
|
+
const prevIndex = this.selectedIndex() === 0
|
|
5239
|
+
? this.filteredCommands().length - 1
|
|
5240
|
+
: this.selectedIndex() - 1;
|
|
5154
5241
|
this.selectedIndex.set(prevIndex);
|
|
5155
5242
|
this.scrollToSelected();
|
|
5156
5243
|
return true;
|
|
@@ -5186,7 +5273,7 @@ class AteSlashCommandsComponent {
|
|
|
5186
5273
|
}));
|
|
5187
5274
|
}
|
|
5188
5275
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: AteSlashCommandsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
5189
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: AteSlashCommandsComponent, isStandalone: true, selector: "ate-slash-commands", inputs: { editor: { classPropertyName: "editor", publicName: "editor", isSignal: true, isRequired: true, transformFunction: null }, config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "menuRef", first: true, predicate: ["menuRef"], descendants: true }], ngImport: i0, template: `
|
|
5276
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: AteSlashCommandsComponent, isStandalone: true, selector: "ate-slash-commands", inputs: { editor: { classPropertyName: "editor", publicName: "editor", isSignal: true, isRequired: true, transformFunction: null }, config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "menuRef", first: true, predicate: ["menuRef"], descendants: true, isSignal: true }], ngImport: i0, template: `
|
|
5190
5277
|
<div #menuRef class="slash-commands-menu">
|
|
5191
5278
|
@for (command of filteredCommands(); track command.title) {
|
|
5192
5279
|
<div
|
|
@@ -5227,10 +5314,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
|
|
|
5227
5314
|
}
|
|
5228
5315
|
</div>
|
|
5229
5316
|
`, styles: [".slash-commands-menu{background:var(--ate-menu-bg);border:1px solid var(--ate-menu-border);border-radius:var(--ate-menu-border-radius, 12px);box-shadow:var(--ate-menu-shadow);padding:var(--ate-menu-padding);max-height:320px;overflow-y:auto;min-width:280px;outline:none;animation:slashMenuFadeIn .2s cubic-bezier(0,0,.2,1);scrollbar-width:thin;scrollbar-color:var(--ate-scrollbar-thumb) var(--ate-scrollbar-track)}.slash-commands-menu::-webkit-scrollbar{width:var(--ate-scrollbar-width)}.slash-commands-menu::-webkit-scrollbar-track{background:var(--ate-scrollbar-track)}.slash-commands-menu::-webkit-scrollbar-thumb{background:var(--ate-scrollbar-thumb);border:3px solid transparent;background-clip:content-box;border-radius:10px}.slash-commands-menu::-webkit-scrollbar-thumb:hover{background:var(--ate-scrollbar-thumb-hover);background-clip:content-box}@keyframes slashMenuFadeIn{0%{opacity:0;transform:translateY(4px)}to{opacity:1;transform:translateY(0)}}.slash-command-item{display:flex;align-items:center;gap:12px;padding:var(--ate-menu-padding);border-radius:var(--ate-menu-border-radius, 8px);cursor:pointer;transition:all .15s ease;border:var(--ate-border-width, 1px) solid transparent;outline:none;margin-bottom:2px}.slash-command-item:last-child{margin-bottom:0}.slash-command-item:hover{background:var(--ate-surface-secondary)}.slash-command-item.selected{background:var(--ate-primary-light);border-color:var(--ate-primary-light-alpha)}.slash-command-icon{display:flex;align-items:center;justify-content:center;width:32px;height:32px;background:var(--ate-surface-tertiary);border-radius:var(--ate-sub-border-radius, 8px);color:var(--ate-primary);flex-shrink:0;transition:all .15s ease}.slash-command-item.selected .slash-command-icon{background:var(--ate-primary);color:var(--ate-primary-contrast, #ffffff)}.slash-command-icon .material-symbols-outlined{font-size:18px}.slash-command-content{flex:1;min-width:0}.slash-command-title{font-weight:500;color:var(--ate-text);font-size:14px;margin-bottom:1px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.slash-command-description{color:var(--ate-text-secondary);font-size:11px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}\n"] }]
|
|
5230
|
-
}], ctorParameters: () => [], propDecorators: { editor: [{ type: i0.Input, args: [{ isSignal: true, alias: "editor", required: true }] }], config: [{ type: i0.Input, args: [{ isSignal: true, alias: "config", required: false }] }], menuRef: [{
|
|
5231
|
-
type: ViewChild,
|
|
5232
|
-
args: ["menuRef", { static: false }]
|
|
5233
|
-
}] } });
|
|
5317
|
+
}], ctorParameters: () => [], propDecorators: { editor: [{ type: i0.Input, args: [{ isSignal: true, alias: "editor", required: true }] }], config: [{ type: i0.Input, args: [{ isSignal: true, alias: "config", required: false }] }], menuRef: [{ type: i0.ViewChild, args: ["menuRef", { isSignal: true }] }] } });
|
|
5234
5318
|
|
|
5235
5319
|
/**
|
|
5236
5320
|
* Edit Toggle Component
|
|
@@ -5311,12 +5395,15 @@ const AteSelectionCalculator = editor => {
|
|
|
5311
5395
|
const { selection } = editor.state;
|
|
5312
5396
|
const { from, to } = selection;
|
|
5313
5397
|
let selectionType = "none";
|
|
5314
|
-
if (selection instanceof TextSelection)
|
|
5398
|
+
if (selection instanceof TextSelection) {
|
|
5315
5399
|
selectionType = "text";
|
|
5316
|
-
|
|
5400
|
+
}
|
|
5401
|
+
else if (selection instanceof NodeSelection) {
|
|
5317
5402
|
selectionType = "node";
|
|
5318
|
-
|
|
5403
|
+
}
|
|
5404
|
+
else if (selection instanceof CellSelection) {
|
|
5319
5405
|
selectionType = "cell";
|
|
5406
|
+
}
|
|
5320
5407
|
let isSingleCell = false;
|
|
5321
5408
|
if (selection instanceof CellSelection) {
|
|
5322
5409
|
isSingleCell = selection.$anchorCell.pos === selection.$headCell.pos;
|
|
@@ -5348,8 +5435,9 @@ const AteMarksCalculator = editor => {
|
|
|
5348
5435
|
const isInsideInlineCode = isCode;
|
|
5349
5436
|
// 1. Resolve target element once for this calculation
|
|
5350
5437
|
const getTargetElement = () => {
|
|
5351
|
-
if (typeof window === "undefined" || !editor.view?.dom)
|
|
5438
|
+
if (typeof window === "undefined" || !editor.view?.dom) {
|
|
5352
5439
|
return null;
|
|
5440
|
+
}
|
|
5353
5441
|
try {
|
|
5354
5442
|
const { from } = editor.state.selection;
|
|
5355
5443
|
const { node } = editor.view.domAtPos(from);
|
|
@@ -5363,8 +5451,9 @@ const AteMarksCalculator = editor => {
|
|
|
5363
5451
|
const computedStyle = targetEl && typeof window !== "undefined" ? window.getComputedStyle(targetEl) : null;
|
|
5364
5452
|
// 2. Lightweight helper to extract properties from the pre-calculated style object
|
|
5365
5453
|
const getStyle = (prop) => {
|
|
5366
|
-
if (!computedStyle)
|
|
5454
|
+
if (!computedStyle) {
|
|
5367
5455
|
return null;
|
|
5456
|
+
}
|
|
5368
5457
|
const val = computedStyle.getPropertyValue(prop);
|
|
5369
5458
|
return normalizeColor(val);
|
|
5370
5459
|
};
|
|
@@ -5386,7 +5475,8 @@ const AteMarksCalculator = editor => {
|
|
|
5386
5475
|
computedColor: colorMark || getStyle("color"),
|
|
5387
5476
|
background: backgroundMark,
|
|
5388
5477
|
computedBackground: backgroundMark || getStyle("background-color"),
|
|
5389
|
-
linkOpenOnClick: editor.extensionManager.extensions.find(ext => ext.name === "link")?.options?.openOnClick ??
|
|
5478
|
+
linkOpenOnClick: editor.extensionManager.extensions.find(ext => ext.name === "link")?.options?.openOnClick ??
|
|
5479
|
+
false,
|
|
5390
5480
|
},
|
|
5391
5481
|
can: {
|
|
5392
5482
|
toggleBold: marksAllowed && !isInsideInlineCode && editor.can().toggleBold(),
|
|
@@ -5395,7 +5485,9 @@ const AteMarksCalculator = editor => {
|
|
|
5395
5485
|
toggleStrike: marksAllowed && !isInsideInlineCode && editor.can().toggleStrike(),
|
|
5396
5486
|
toggleCode: marksAllowed && editor.can().toggleCode(),
|
|
5397
5487
|
toggleHighlight: marksAllowed && !isInsideInlineCode && editor.can().toggleHighlight(),
|
|
5398
|
-
toggleLink: marksAllowed &&
|
|
5488
|
+
toggleLink: marksAllowed &&
|
|
5489
|
+
!isInsideInlineCode &&
|
|
5490
|
+
(editor.can().setLink({ href: "" }) || editor.can().unsetLink()),
|
|
5399
5491
|
toggleSuperscript: marksAllowed && !isInsideInlineCode && editor.can().toggleSuperscript(),
|
|
5400
5492
|
toggleSubscript: marksAllowed && !isInsideInlineCode && editor.can().toggleSubscript(),
|
|
5401
5493
|
setColor: marksAllowed && !isInsideInlineCode && editor.can().setColor(""),
|
|
@@ -5522,7 +5614,16 @@ const AteDiscoveryCalculator = (editor) => {
|
|
|
5522
5614
|
const name = extension.name;
|
|
5523
5615
|
const type = extension.type;
|
|
5524
5616
|
// Skip internal/core extensions or already handled ones
|
|
5525
|
-
if ([
|
|
5617
|
+
if ([
|
|
5618
|
+
"selection",
|
|
5619
|
+
"editable",
|
|
5620
|
+
"focus",
|
|
5621
|
+
"undo",
|
|
5622
|
+
"redo",
|
|
5623
|
+
"history",
|
|
5624
|
+
"placeholder",
|
|
5625
|
+
"characterCount",
|
|
5626
|
+
].includes(name)) {
|
|
5526
5627
|
return;
|
|
5527
5628
|
}
|
|
5528
5629
|
if (handled.includes(name)) {
|
|
@@ -5545,13 +5646,16 @@ const AteLinkClickBehavior = Extension.create({
|
|
|
5545
5646
|
new Plugin({
|
|
5546
5647
|
key: new PluginKey("linkClickBehavior"),
|
|
5547
5648
|
props: {
|
|
5548
|
-
handleClick(view, _pos,
|
|
5649
|
+
handleClick(view, _pos, event) {
|
|
5549
5650
|
// handleClick only runs in the browser, but we guard it for absolute SSR safety
|
|
5550
|
-
if (typeof window === "undefined")
|
|
5651
|
+
if (typeof window === "undefined") {
|
|
5551
5652
|
return false;
|
|
5552
|
-
|
|
5553
|
-
|
|
5653
|
+
}
|
|
5654
|
+
const isModKey = event.ctrlKey || event.metaKey;
|
|
5655
|
+
// If editor is editable, only proceed if Ctrl/Cmd is pressed
|
|
5656
|
+
if (view.editable && !isModKey) {
|
|
5554
5657
|
return false;
|
|
5658
|
+
}
|
|
5555
5659
|
const attrs = getAttributes(view.state, "link");
|
|
5556
5660
|
if (attrs["href"]) {
|
|
5557
5661
|
window.open(attrs["href"], "_blank", "noopener,noreferrer");
|
|
@@ -5753,12 +5857,14 @@ class AngularTiptapEditorComponent {
|
|
|
5753
5857
|
// Appearance & Fundamentals
|
|
5754
5858
|
this.finalSeamless = computed(() => {
|
|
5755
5859
|
const fromConfig = this.config().mode;
|
|
5756
|
-
if (fromConfig !== undefined)
|
|
5860
|
+
if (fromConfig !== undefined) {
|
|
5757
5861
|
return fromConfig === "seamless";
|
|
5862
|
+
}
|
|
5758
5863
|
return this.seamless();
|
|
5759
5864
|
}, ...(ngDevMode ? [{ debugName: "finalSeamless" }] : []));
|
|
5760
5865
|
this.finalEditable = computed(() => this.config().editable ?? this.editable(), ...(ngDevMode ? [{ debugName: "finalEditable" }] : []));
|
|
5761
|
-
this.finalPlaceholder = computed(() => this.config().placeholder ??
|
|
5866
|
+
this.finalPlaceholder = computed(() => this.config().placeholder ??
|
|
5867
|
+
(this.placeholder() || this.currentTranslations().editor.placeholder), ...(ngDevMode ? [{ debugName: "finalPlaceholder" }] : []));
|
|
5762
5868
|
this.finalFillContainer = computed(() => this.config().fillContainer ?? this.fillContainer(), ...(ngDevMode ? [{ debugName: "finalFillContainer" }] : []));
|
|
5763
5869
|
this.finalShowFooter = computed(() => this.config().showFooter ?? this.showFooter(), ...(ngDevMode ? [{ debugName: "finalShowFooter" }] : []));
|
|
5764
5870
|
this.finalShowEditToggle = computed(() => this.config().showEditToggle ?? this.showEditToggle(), ...(ngDevMode ? [{ debugName: "finalShowEditToggle" }] : []));
|
|
@@ -5772,8 +5878,9 @@ class AngularTiptapEditorComponent {
|
|
|
5772
5878
|
this.finalToolbarConfig = computed(() => {
|
|
5773
5879
|
const fromConfig = this.config().toolbar;
|
|
5774
5880
|
const base = ATE_DEFAULT_TOOLBAR_CONFIG;
|
|
5775
|
-
if (fromConfig)
|
|
5881
|
+
if (fromConfig) {
|
|
5776
5882
|
return { ...base, ...fromConfig };
|
|
5883
|
+
}
|
|
5777
5884
|
const fromInput = this.toolbar();
|
|
5778
5885
|
return Object.keys(fromInput).length === 0 ? base : { ...base, ...fromInput };
|
|
5779
5886
|
}, ...(ngDevMode ? [{ debugName: "finalToolbarConfig" }] : []));
|
|
@@ -5782,40 +5889,51 @@ class AngularTiptapEditorComponent {
|
|
|
5782
5889
|
this.finalBubbleMenuConfig = computed(() => {
|
|
5783
5890
|
const fromConfig = this.config().bubbleMenu;
|
|
5784
5891
|
const base = ATE_DEFAULT_BUBBLE_MENU_CONFIG;
|
|
5785
|
-
if (fromConfig)
|
|
5892
|
+
if (fromConfig) {
|
|
5786
5893
|
return { ...base, ...fromConfig };
|
|
5894
|
+
}
|
|
5787
5895
|
return Object.keys(this.bubbleMenu()).length === 0 ? base : { ...base, ...this.bubbleMenu() };
|
|
5788
5896
|
}, ...(ngDevMode ? [{ debugName: "finalBubbleMenuConfig" }] : []));
|
|
5789
5897
|
this.finalShowImageBubbleMenu = computed(() => this.config().showImageBubbleMenu ?? this.showImageBubbleMenu(), ...(ngDevMode ? [{ debugName: "finalShowImageBubbleMenu" }] : []));
|
|
5790
5898
|
this.finalImageBubbleMenuConfig = computed(() => {
|
|
5791
5899
|
const fromConfig = this.config().imageBubbleMenu;
|
|
5792
5900
|
const base = ATE_DEFAULT_IMAGE_BUBBLE_MENU_CONFIG;
|
|
5793
|
-
if (fromConfig)
|
|
5901
|
+
if (fromConfig) {
|
|
5794
5902
|
return { ...base, ...fromConfig };
|
|
5795
|
-
|
|
5903
|
+
}
|
|
5904
|
+
return Object.keys(this.imageBubbleMenu()).length === 0
|
|
5905
|
+
? base
|
|
5906
|
+
: { ...base, ...this.imageBubbleMenu() };
|
|
5796
5907
|
}, ...(ngDevMode ? [{ debugName: "finalImageBubbleMenuConfig" }] : []));
|
|
5797
5908
|
this.finalShowTableBubbleMenu = computed(() => this.config().showTableMenu ?? this.showTableBubbleMenu(), ...(ngDevMode ? [{ debugName: "finalShowTableBubbleMenu" }] : []));
|
|
5798
5909
|
this.finalTableBubbleMenuConfig = computed(() => {
|
|
5799
5910
|
const fromConfig = this.config().tableBubbleMenu;
|
|
5800
5911
|
const base = ATE_DEFAULT_TABLE_MENU_CONFIG;
|
|
5801
|
-
if (fromConfig)
|
|
5912
|
+
if (fromConfig) {
|
|
5802
5913
|
return { ...base, ...fromConfig };
|
|
5803
|
-
|
|
5914
|
+
}
|
|
5915
|
+
return Object.keys(this.tableBubbleMenu()).length === 0
|
|
5916
|
+
? base
|
|
5917
|
+
: { ...base, ...this.tableBubbleMenu() };
|
|
5804
5918
|
}, ...(ngDevMode ? [{ debugName: "finalTableBubbleMenuConfig" }] : []));
|
|
5805
5919
|
this.finalShowCellBubbleMenu = computed(() => this.config().showCellMenu ?? this.showCellBubbleMenu(), ...(ngDevMode ? [{ debugName: "finalShowCellBubbleMenu" }] : []));
|
|
5806
5920
|
this.finalCellBubbleMenuConfig = computed(() => {
|
|
5807
5921
|
const fromConfig = this.config().cellBubbleMenu;
|
|
5808
5922
|
const base = ATE_DEFAULT_CELL_MENU_CONFIG;
|
|
5809
|
-
if (fromConfig)
|
|
5923
|
+
if (fromConfig) {
|
|
5810
5924
|
return { ...base, ...fromConfig };
|
|
5811
|
-
|
|
5925
|
+
}
|
|
5926
|
+
return Object.keys(this.cellBubbleMenu()).length === 0
|
|
5927
|
+
? base
|
|
5928
|
+
: { ...base, ...this.cellBubbleMenu() };
|
|
5812
5929
|
}, ...(ngDevMode ? [{ debugName: "finalCellBubbleMenuConfig" }] : []));
|
|
5813
5930
|
this.finalEnableSlashCommands = computed(() => this.config().enableSlashCommands ?? this.enableSlashCommands(), ...(ngDevMode ? [{ debugName: "finalEnableSlashCommands" }] : []));
|
|
5814
5931
|
this.finalSlashCommandsConfig = computed(() => {
|
|
5815
5932
|
const fromConfig = this.config().slashCommands;
|
|
5816
5933
|
const customConfig = this.customSlashCommands();
|
|
5817
|
-
if (customConfig)
|
|
5934
|
+
if (customConfig) {
|
|
5818
5935
|
return customConfig;
|
|
5936
|
+
}
|
|
5819
5937
|
let baseConfig = this.slashCommands();
|
|
5820
5938
|
if (fromConfig) {
|
|
5821
5939
|
baseConfig = fromConfig;
|
|
@@ -5875,17 +5993,21 @@ class AngularTiptapEditorComponent {
|
|
|
5875
5993
|
untracked(() => {
|
|
5876
5994
|
const editor = this.editor();
|
|
5877
5995
|
const hasFormControl = !!this.ngControl?.control;
|
|
5878
|
-
if (!editor || content === undefined)
|
|
5996
|
+
if (!editor || content === undefined) {
|
|
5879
5997
|
return;
|
|
5998
|
+
}
|
|
5880
5999
|
// Anti-écho : on ignore ce qu'on vient d'émettre nous-mêmes
|
|
5881
|
-
if (content === this.lastEmittedHtml)
|
|
6000
|
+
if (content === this.lastEmittedHtml) {
|
|
5882
6001
|
return;
|
|
6002
|
+
}
|
|
5883
6003
|
// Double sécurité : on vérifie le contenu actuel de l'éditeur
|
|
5884
|
-
if (content === editor.getHTML())
|
|
6004
|
+
if (content === editor.getHTML()) {
|
|
5885
6005
|
return;
|
|
6006
|
+
}
|
|
5886
6007
|
// Do not overwrite content if we have a FormControl and content is empty
|
|
5887
|
-
if (hasFormControl && !content)
|
|
6008
|
+
if (hasFormControl && !content) {
|
|
5888
6009
|
return;
|
|
6010
|
+
}
|
|
5889
6011
|
editor.commands.setContent(content, false);
|
|
5890
6012
|
});
|
|
5891
6013
|
});
|
|
@@ -6017,7 +6139,9 @@ class AngularTiptapEditorComponent {
|
|
|
6017
6139
|
// Allow addition of custom extensions, but avoid duplicates by filtering by name
|
|
6018
6140
|
const customExtensions = this.tiptapExtensions();
|
|
6019
6141
|
if (customExtensions.length > 0) {
|
|
6020
|
-
const existingNames = new Set(extensions
|
|
6142
|
+
const existingNames = new Set(extensions
|
|
6143
|
+
.map(ext => ext?.name)
|
|
6144
|
+
.filter((name) => !!name));
|
|
6021
6145
|
const toAdd = customExtensions.filter(ext => {
|
|
6022
6146
|
const name = ext?.name;
|
|
6023
6147
|
return !name || !existingNames.has(name);
|
|
@@ -6082,7 +6206,8 @@ class AngularTiptapEditorComponent {
|
|
|
6082
6206
|
this.editableChange.emit(newEditable);
|
|
6083
6207
|
}
|
|
6084
6208
|
updateCharacterCount(editor) {
|
|
6085
|
-
if ((this.finalShowCharacterCount() || this.finalShowWordCount()) &&
|
|
6209
|
+
if ((this.finalShowCharacterCount() || this.finalShowWordCount()) &&
|
|
6210
|
+
editor.storage["characterCount"]) {
|
|
6086
6211
|
const storage = editor.storage["characterCount"];
|
|
6087
6212
|
this._characterCount.set(storage.characters());
|
|
6088
6213
|
this._wordCount.set(storage.words());
|
|
@@ -6183,8 +6308,9 @@ class AngularTiptapEditorComponent {
|
|
|
6183
6308
|
}
|
|
6184
6309
|
onEditorClick(event) {
|
|
6185
6310
|
const editor = this.editor();
|
|
6186
|
-
if (!editor || !this.finalEditable())
|
|
6311
|
+
if (!editor || !this.finalEditable()) {
|
|
6187
6312
|
return;
|
|
6313
|
+
}
|
|
6188
6314
|
// Verify if interaction is on the container element and not on the content
|
|
6189
6315
|
const target = event.target;
|
|
6190
6316
|
const editorElement = this.editorElement()?.nativeElement;
|
|
@@ -6290,7 +6416,10 @@ class AngularTiptapEditorComponent {
|
|
|
6290
6416
|
|
|
6291
6417
|
<!-- Counters -->
|
|
6292
6418
|
@if (
|
|
6293
|
-
finalEditable() &&
|
|
6419
|
+
finalEditable() &&
|
|
6420
|
+
!mergedDisabled() &&
|
|
6421
|
+
finalShowFooter() &&
|
|
6422
|
+
(finalShowCharacterCount() || finalShowWordCount())
|
|
6294
6423
|
) {
|
|
6295
6424
|
<div
|
|
6296
6425
|
class="character-count"
|
|
@@ -6314,7 +6443,7 @@ class AngularTiptapEditorComponent {
|
|
|
6314
6443
|
</div>
|
|
6315
6444
|
}
|
|
6316
6445
|
</div>
|
|
6317
|
-
`, isInline: true, styles: [":host{--ate-primary: #2563eb;--ate-primary-contrast: #ffffff;--ate-primary-light: color-mix(in srgb, var(--ate-primary), transparent 90%);--ate-primary-lighter: color-mix(in srgb, var(--ate-primary), transparent 95%);--ate-primary-light-alpha: color-mix(in srgb, var(--ate-primary), transparent 85%);--ate-surface: #ffffff;--ate-surface-secondary: #f8f9fa;--ate-surface-tertiary: #f1f5f9;--ate-text: #2d3748;--ate-text-secondary: #64748b;--ate-text-muted: #a0aec0;--ate-border: #e2e8f0;--ate-highlight-bg: #fef08a;--ate-highlight-color: #854d0e;--ate-button-hover: #f1f5f9;--ate-button-active: #e2e8f0;--ate-error-color: #c53030;--ate-error-bg: #fed7d7;--ate-error-border: #feb2b2;--ate-border-color: var(--ate-border);--ate-border-width: 2px;--ate-border-radius: 12px;--ate-focus-color: var(--ate-primary);--ate-background: var(--ate-surface);--ate-sub-border-radius: 8px;--ate-text-color: var(--ate-text);--ate-placeholder-color: var(--ate-text-muted);--ate-line-height: 1.6;--ate-content-padding: 16px;--ate-menu-bg: var(--ate-surface);--ate-menu-border-radius: var(--ate-border-radius);--ate-menu-border: var(--ate-border);--ate-menu-shadow: 0 10px 15px -3px rgba(0, 0, 0, .1), 0 4px 6px -2px rgba(0, 0, 0, .05);--ate-menu-padding: 6px;--ate-toolbar-padding: var(--ate-menu-padding);--ate-toolbar-background: var(--ate-surface-secondary);--ate-toolbar-border-color: var(--ate-border);--ate-toolbar-button-color: var(--ate-text-secondary);--ate-toolbar-button-hover-background: transparent;--ate-toolbar-button-active-background: var(--ate-primary-light);--ate-toolbar-button-active-color: var(--ate-primary);--ate-counter-color: var(--ate-text-secondary);--ate-counter-background: var(--ate-surface-secondary);--ate-counter-border-color: var(--ate-border);--ate-drag-background: #f0f8ff;--ate-drag-border-color: var(--ate-primary);--ate-blockquote-border-color: var(--ate-border);--ate-blockquote-background: var(--ate-surface-secondary);--ate-code-background: var(--ate-surface-secondary);--ate-code-color: var(--ate-code-color);--ate-code-border-color: var(--ate-border);--ate-code-block-background: #0f172a;--ate-code-block-color: #e2e8f0;--ate-code-block-border-color: var(--ate-border);--ate-image-border-radius: 16px;--ate-image-selected-color: var(--ate-primary);--ate-scrollbar-width: 10px;--ate-scrollbar-thumb: var(--ate-border);--ate-scrollbar-thumb-hover: var(--ate-text-muted);--ate-scrollbar-track: transparent;--ate-table-border-color: var(--ate-border);--ate-table-header-background: var(--ate-surface-secondary);--ate-table-header-color: var(--ate-text);--ate-table-cell-background: var(--ate-surface);--ate-table-cell-selected-background: var(--ate-primary-light);--ate-table-resize-handle-color: var(--ate-primary);--ate-table-row-hover-background: var(--ate-primary-lighter)}:host(.dark),:host([data-theme=\"dark\"]){--ate-primary: #3b82f6;--ate-primary-contrast: #ffffff;--ate-primary-light: color-mix(in srgb, var(--ate-primary), transparent 85%);--ate-primary-lighter: color-mix(in srgb, var(--ate-primary), transparent 92%);--ate-primary-light-alpha: color-mix(in srgb, var(--ate-primary), transparent 80%);--ate-surface: #020617;--ate-surface-secondary: #0f172a;--ate-surface-tertiary: #1e293b;--ate-text: #f8fafc;--ate-text-secondary: #94a3b8;--ate-text-muted: #64748b;--ate-border: #1e293b;--ate-highlight-bg: #854d0e;--ate-highlight-color: #fef08a;--ate-button-hover: #1e293b;--ate-button-active: #0f172a;--ate-menu-border: rgba(255, 255, 255, .1);--ate-menu-shadow: 0 20px 25px -5px rgba(0, 0, 0, .3), 0 10px 10px -5px rgba(0, 0, 0, .2);--ate-error-color: #f87171;--ate-error-bg: #450a0a;--ate-error-border: #7f1d1d;--ate-drag-background: var(--ate-surface-tertiary);--ate-drag-border-color: var(--ate-primary);--ate-blockquote-border-color: var(--ate-primary);--ate-toolbar-button-active-background: var(--ate-primary-light);--ate-toolbar-button-active-color: var(--ate-primary);--ate-button-hover: var(--ate-surface-tertiary);--ate-button-active: var(--ate-surface-secondary);--ate-scrollbar-thumb: var(--ate-surface-tertiary);--ate-scrollbar-thumb-hover: var(--ate-text-muted)}:host(.fill-container){display:block;height:100%}.ate-editor{border:var(--ate-border-width) solid var(--ate-border-color);border-radius:var(--ate-border-radius);background:var(--ate-background);overflow:visible;transition:border-color .2s ease;position:relative}:host(.floating-toolbar) .ate-editor{overflow:visible}:host(.fill-container) .ate-editor{display:flex;flex-direction:column;height:100%}:host(.fill-container) .ate-content-wrapper{flex:1;min-height:0}:host(.fill-container) .ate-content{flex:1;min-height:0;overflow-y:auto}.ate-editor:focus-within{border-color:var(--ate-focus-color)}.ate-content{min-height:var(--editor-min-height, 200px);height:var(--editor-height, auto);max-height:var(--editor-max-height, none);overflow-y:var(--editor-overflow, visible);outline:none;position:relative;scrollbar-width:thin;scrollbar-color:var(--ate-scrollbar-thumb) var(--ate-scrollbar-track)}:host(.is-disabled) .ate-content{cursor:not-allowed;opacity:.7;-webkit-user-select:none;user-select:none;pointer-events:none;background-color:var(--ate-surface-tertiary)}:host(.is-readonly) .ate-content{cursor:default;-webkit-user-select:text;user-select:text}:host(.is-readonly) .ate-content ::ng-deep .ate-link{cursor:pointer;pointer-events:auto}.ate-content::-webkit-scrollbar{width:var(--ate-scrollbar-width)}.ate-content-wrapper{position:relative;display:flex;flex-direction:column;min-height:0}.ate-content-wrapper .ate-content{flex:1}.ate-content::-webkit-scrollbar-track{background:var(--ate-scrollbar-track)}.ate-content::-webkit-scrollbar-thumb{background:var(--ate-scrollbar-thumb);border:3px solid transparent;background-clip:content-box;border-radius:10px}.ate-content::-webkit-scrollbar-thumb:hover{background:var(--ate-scrollbar-thumb-hover);background-clip:content-box}.ate-content.drag-over{background:var(--ate-drag-background);border:2px dashed var(--ate-drag-border-color)}.character-count{padding:6px 8px;font-size:12px;color:var(--ate-counter-color);text-align:right;border-top:1px solid var(--ate-counter-border-color);background:var(--ate-counter-background);transition:color .2s ease;border-bottom-left-radius:calc(var(--ate-border-radius) - var(--ate-border-width));border-bottom-right-radius:calc(var(--ate-border-radius) - var(--ate-border-width))}.character-count.limit-reached{color:var(--ate-error-color, #ef4444);font-weight:600}:host ::ng-deep .ProseMirror{padding:var(--ate-content-padding);outline:none;line-height:var(--ate-line-height);color:var(--ate-text-color);min-height:100%;height:100%;word-wrap:break-word;overflow-wrap:break-word}:host ::ng-deep .ProseMirror h1{font-size:2em;font-weight:700;margin-top:0;margin-bottom:.5em}:host ::ng-deep .ProseMirror h2{font-size:1.5em;font-weight:700;margin-top:1em;margin-bottom:.5em}:host ::ng-deep .ProseMirror h3{font-size:1.25em;font-weight:700;margin-top:1em;margin-bottom:.5em}:host ::ng-deep .ProseMirror p{margin:.5em 0}:host ::ng-deep .ProseMirror ul,:host ::ng-deep .ProseMirror ol{padding-left:2em;margin:.5em 0}:host ::ng-deep .ProseMirror blockquote{border-left:4px solid var(--ate-blockquote-border-color);margin:1em 0;background:var(--ate-blockquote-background);padding:.5em 1em;border-radius:0 4px 4px 0}:host ::ng-deep .ProseMirror code{background:var(--ate-code-background);color:var(--ate-code-color);border:1px solid var(--ate-code-border-color);padding:.15em .4em;border-radius:4px;font-family:JetBrains Mono,Fira Code,Monaco,Consolas,monospace;font-size:.85em;font-weight:500}:host ::ng-deep .ProseMirror pre{background:var(--ate-code-block-background);color:var(--ate-code-block-color);border:1px solid var(--ate-code-block-border-color);padding:1em;border-radius:var(--ate-border-radius);overflow-x:auto;margin:1em 0}:host ::ng-deep .ProseMirror pre code{background:none;color:inherit;border:none;padding:0}:host ::ng-deep .ProseMirror p.is-editor-empty:first-child:before{content:attr(data-placeholder);color:var(--ate-placeholder-color);pointer-events:none;float:left;height:0}:host ::ng-deep .ProseMirror[contenteditable=false]{pointer-events:none}:host ::ng-deep .ProseMirror[contenteditable=false] img{cursor:default;pointer-events:none}:host ::ng-deep .ProseMirror[contenteditable=false] img:hover{transform:none;box-shadow:0 2px 8px #0000001a}:host ::ng-deep .ProseMirror[contenteditable=false] img.ProseMirror-selectednode{outline:none}:host ::ng-deep .ProseMirror img{position:relative;display:inline-block;max-width:100%;height:auto;cursor:pointer;transition:all .2s ease;border:2px solid transparent;border-radius:var(--ate-image-border-radius)}:host ::ng-deep .ProseMirror img:hover{border-color:var(--ate-border-color);box-shadow:0 2px 4px #0000001a}:host ::ng-deep .ProseMirror img.ProseMirror-selectednode{border-color:var(--ate-image-selected-color);box-shadow:0 0 0 3px var(--ate-primary-light-alpha);transition:all .2s ease}:host ::ng-deep .ProseMirror .tiptap-image{max-width:100%;height:auto;border-radius:var(--ate-image-border-radius);box-shadow:0 4px 20px #00000014;margin:.5em 0;cursor:pointer;transition:all .3s cubic-bezier(.4,0,.2,1);display:block;filter:brightness(1) contrast(1)}:host ::ng-deep .ProseMirror .tiptap-image:hover{box-shadow:0 8px 30px #0000001f;filter:brightness(1.02) contrast(1.02)}:host ::ng-deep .ProseMirror .tiptap-image.ProseMirror-selectednode{outline:2px solid var(--ate-primary);outline-offset:2px;border-radius:var(--ate-image-border-radius);box-shadow:0 0 0 4px var(--ate-primary-light-alpha)}:host ::ng-deep .image-container{margin:.5em 0;text-align:center;border-radius:16px;overflow:hidden;transition:all .3s cubic-bezier(.4,0,.2,1)}:host ::ng-deep .image-container.image-align-left{text-align:left}:host ::ng-deep .image-container.image-align-center{text-align:center}:host ::ng-deep .image-container.image-align-right{text-align:right}:host ::ng-deep .image-container img{display:inline-block;max-width:100%;height:auto;border-radius:16px}:host ::ng-deep .resizable-image-container{position:relative;display:inline-block;margin:.5em 0}:host ::ng-deep .resize-controls{position:absolute;inset:0;pointer-events:none;z-index:1000}:host ::ng-deep .resize-handle{position:absolute;width:12px;height:12px;background:var(--ate-primary);border:2px solid var(--ate-surface);border-radius:50%;pointer-events:all;cursor:pointer;z-index:1001;transition:all .15s ease;box-shadow:0 2px 6px #0003}:host ::ng-deep .resize-handle:hover{background:var(--ate-primary);box-shadow:0 3px 8px #0000004d}:host ::ng-deep .resize-handle:active{background:var(--ate-primary)}:host ::ng-deep .resize-handle-n:hover,:host ::ng-deep .resize-handle-s:hover{transform:translate(-50%) scale(1.2)}:host ::ng-deep .resize-handle-w:hover,:host ::ng-deep .resize-handle-e:hover{transform:translateY(-50%) scale(1.2)}:host ::ng-deep .resize-handle-n:active,:host ::ng-deep .resize-handle-s:active{transform:translate(-50%) scale(.9)}:host ::ng-deep .resize-handle-w:active,:host ::ng-deep .resize-handle-e:active{transform:translateY(-50%) scale(.9)}:host ::ng-deep .resize-handle-nw:hover,:host ::ng-deep .resize-handle-ne:hover,:host ::ng-deep .resize-handle-sw:hover,:host ::ng-deep .resize-handle-se:hover{transform:scale(1.2)}:host ::ng-deep .resize-handle-nw:active,:host ::ng-deep .resize-handle-ne:active,:host ::ng-deep .resize-handle-sw:active,:host ::ng-deep .resize-handle-se:active{transform:scale(.9)}:host ::ng-deep .resize-handle-nw{top:0;left:-6px;cursor:nw-resize}:host ::ng-deep .resize-handle-n{top:0;left:50%;transform:translate(-50%);cursor:n-resize}:host ::ng-deep .resize-handle-ne{top:0;right:-6px;cursor:ne-resize}:host ::ng-deep .resize-handle-w{top:50%;left:-6px;transform:translateY(-50%);cursor:w-resize}:host ::ng-deep .resize-handle-e{top:50%;right:-6px;transform:translateY(-50%);cursor:e-resize}:host ::ng-deep .resize-handle-sw{bottom:0;left:-6px;cursor:sw-resize}:host ::ng-deep .resize-handle-s{bottom:0;left:50%;transform:translate(-50%);cursor:s-resize}:host ::ng-deep .resize-handle-se{bottom:0;right:-6px;cursor:se-resize}:host ::ng-deep body.resizing{-webkit-user-select:none;user-select:none;cursor:crosshair}:host ::ng-deep body.resizing .ProseMirror{pointer-events:none}:host ::ng-deep body.resizing .ProseMirror .tiptap-image{pointer-events:none}:host ::ng-deep .image-size-info{position:absolute;bottom:-20px;left:50%;transform:translate(-50%);background:#000c;color:#fff;padding:2px 6px;border-radius:3px;font-size:11px;white-space:nowrap;opacity:0;transition:opacity .2s ease}:host ::ng-deep .image-container:hover .image-size-info{opacity:1}:host ::ng-deep .ProseMirror table{border-collapse:separate;border-spacing:0;margin:0;table-layout:fixed;width:100%;border-radius:8px;overflow:hidden}:host ::ng-deep .ProseMirror table td,:host ::ng-deep .ProseMirror table th{border:none;border-right:1px solid var(--ate-table-border-color);border-bottom:1px solid var(--ate-table-border-color);box-sizing:border-box;min-width:1em;padding:8px 12px;position:relative;vertical-align:top;text-align:left}:host ::ng-deep .ProseMirror table td{background:var(--ate-table-cell-background)}:host ::ng-deep .ProseMirror table td:first-child,:host ::ng-deep .ProseMirror table th:first-child{border-left:1px solid var(--ate-table-border-color)}:host ::ng-deep .ProseMirror table tr:first-child td,:host ::ng-deep .ProseMirror table tr:first-child th{border-top:1px solid var(--ate-table-border-color)}:host ::ng-deep .ProseMirror table tr:first-child th:first-child{border-top-left-radius:8px}:host ::ng-deep .ProseMirror table tr:first-child th:last-child{border-top-right-radius:8px}:host ::ng-deep .ProseMirror table tr:last-child td:first-child{border-bottom-left-radius:8px}:host ::ng-deep .ProseMirror table tr:last-child td:last-child{border-bottom-right-radius:8px}:host ::ng-deep .ProseMirror table th{background:var(--ate-table-header-background);font-weight:600;color:var(--ate-table-header-color)}:host ::ng-deep .ProseMirror table .selectedCell:after{background:var(--ate-table-cell-selected-background);content:\"\";inset:0;pointer-events:none;position:absolute;z-index:2}:host ::ng-deep .ProseMirror table .column-resize-handle{position:absolute;right:-2px;top:0;bottom:0;width:4px;background-color:var(--ate-table-resize-handle-color);opacity:0;transition:opacity .2s ease}:host ::ng-deep .ProseMirror table:hover .column-resize-handle{opacity:1}:host ::ng-deep .ProseMirror table .column-resize-handle:hover{background-color:var(--ate-focus-color)}:host ::ng-deep .ProseMirror .tableWrapper{overflow-x:auto;margin:1em 0;border-radius:8px}:host ::ng-deep .ProseMirror .tableWrapper table{margin:0;border-radius:8px;min-width:600px;overflow:hidden}:host ::ng-deep .ProseMirror table p{margin:0}:host ::ng-deep .ProseMirror table tbody tr:hover td{background-color:var(--ate-table-row-hover-background)}\n"], dependencies: [{ kind: "component", type: AteToolbarComponent, selector: "ate-toolbar", inputs: ["editor", "config", "imageUpload", "floating"] }, { kind: "component", type: AteBubbleMenuComponent, selector: "ate-bubble-menu", inputs: ["config"] }, { kind: "component", type: AteImageBubbleMenuComponent, selector: "ate-image-bubble-menu", inputs: ["config", "imageUpload"] }, { kind: "component", type: AteTableBubbleMenuComponent, selector: "ate-table-bubble-menu", inputs: ["config"] }, { kind: "component", type: AteCellBubbleMenuComponent, selector: "ate-cell-bubble-menu", inputs: ["config"] }, { kind: "component", type: AteSlashCommandsComponent, selector: "ate-slash-commands", inputs: ["editor", "config"] }, { kind: "component", type: AteLinkBubbleMenuComponent, selector: "ate-link-bubble-menu", inputs: ["editor"] }, { kind: "component", type: AteColorBubbleMenuComponent, selector: "ate-color-bubble-menu", inputs: ["editor"] }, { kind: "component", type: AteEditToggleComponent, selector: "ate-edit-toggle", inputs: ["editable", "translations"], outputs: ["editToggle"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
6446
|
+
`, isInline: true, styles: [":host{--ate-primary: #2563eb;--ate-primary-contrast: #ffffff;--ate-primary-light: color-mix(in srgb, var(--ate-primary), transparent 90%);--ate-primary-lighter: color-mix(in srgb, var(--ate-primary), transparent 95%);--ate-primary-light-alpha: color-mix(in srgb, var(--ate-primary), transparent 85%);--ate-surface: #ffffff;--ate-surface-secondary: #f8f9fa;--ate-surface-tertiary: #f1f5f9;--ate-text: #2d3748;--ate-text-secondary: #64748b;--ate-text-muted: #a0aec0;--ate-border: #e2e8f0;--ate-highlight-bg: #fef08a;--ate-highlight-color: #854d0e;--ate-button-hover: #f1f5f9;--ate-button-active: #e2e8f0;--ate-error-color: #c53030;--ate-error-bg: #fed7d7;--ate-error-border: #feb2b2;--ate-border-color: var(--ate-border);--ate-border-width: 2px;--ate-border-radius: 12px;--ate-focus-color: var(--ate-primary);--ate-background: var(--ate-surface);--ate-sub-border-radius: 8px;--ate-text-color: var(--ate-text);--ate-placeholder-color: var(--ate-text-muted);--ate-line-height: 1.6;--ate-content-padding: 16px;--ate-menu-bg: var(--ate-surface);--ate-menu-border-radius: var(--ate-border-radius);--ate-menu-border: var(--ate-border);--ate-menu-shadow: 0 10px 15px -3px rgba(0, 0, 0, .1), 0 4px 6px -2px rgba(0, 0, 0, .05);--ate-menu-padding: 6px;--ate-toolbar-padding: var(--ate-menu-padding);--ate-toolbar-background: var(--ate-surface-secondary);--ate-toolbar-border-color: var(--ate-border);--ate-toolbar-button-color: var(--ate-text-secondary);--ate-toolbar-button-hover-background: transparent;--ate-toolbar-button-active-background: var(--ate-primary-light);--ate-toolbar-button-active-color: var(--ate-primary);--ate-counter-color: var(--ate-text-secondary);--ate-counter-background: var(--ate-surface-secondary);--ate-counter-border-color: var(--ate-border);--ate-drag-background: #f0f8ff;--ate-drag-border-color: var(--ate-primary);--ate-blockquote-border-color: var(--ate-border);--ate-blockquote-background: var(--ate-surface-secondary);--ate-code-background: var(--ate-surface-secondary);--ate-code-color: var(--ate-code-color);--ate-code-border-color: var(--ate-border);--ate-code-block-background: #0f172a;--ate-code-block-color: #e2e8f0;--ate-code-block-border-color: var(--ate-border);--ate-image-border-radius: 16px;--ate-image-selected-color: var(--ate-primary);--ate-scrollbar-width: 10px;--ate-scrollbar-thumb: var(--ate-border);--ate-scrollbar-thumb-hover: var(--ate-text-muted);--ate-scrollbar-track: transparent;--ate-table-border-color: var(--ate-border);--ate-table-header-background: var(--ate-surface-secondary);--ate-table-header-color: var(--ate-text);--ate-table-cell-background: var(--ate-surface);--ate-table-cell-selected-background: var(--ate-primary-light);--ate-table-resize-handle-color: var(--ate-primary);--ate-table-row-hover-background: var(--ate-primary-lighter)}:host(.dark),:host([data-theme=\"dark\"]){--ate-primary: #3b82f6;--ate-primary-contrast: #ffffff;--ate-primary-light: color-mix(in srgb, var(--ate-primary), transparent 85%);--ate-primary-lighter: color-mix(in srgb, var(--ate-primary), transparent 92%);--ate-primary-light-alpha: color-mix(in srgb, var(--ate-primary), transparent 80%);--ate-surface: #020617;--ate-surface-secondary: #0f172a;--ate-surface-tertiary: #1e293b;--ate-text: #f8fafc;--ate-text-secondary: #94a3b8;--ate-text-muted: #64748b;--ate-border: #1e293b;--ate-highlight-bg: #854d0e;--ate-highlight-color: #fef08a;--ate-button-hover: #1e293b;--ate-button-active: #0f172a;--ate-menu-border: rgba(255, 255, 255, .1);--ate-menu-shadow: 0 20px 25px -5px rgba(0, 0, 0, .3), 0 10px 10px -5px rgba(0, 0, 0, .2);--ate-error-color: #f87171;--ate-error-bg: #450a0a;--ate-error-border: #7f1d1d;--ate-drag-background: var(--ate-surface-tertiary);--ate-drag-border-color: var(--ate-primary);--ate-blockquote-border-color: var(--ate-primary);--ate-toolbar-button-active-background: var(--ate-primary-light);--ate-toolbar-button-active-color: var(--ate-primary);--ate-button-hover: var(--ate-surface-tertiary);--ate-button-active: var(--ate-surface-secondary);--ate-scrollbar-thumb: var(--ate-surface-tertiary);--ate-scrollbar-thumb-hover: var(--ate-text-muted)}:host(.fill-container){display:block;height:100%}.ate-editor{border:var(--ate-border-width) solid var(--ate-border-color);border-radius:var(--ate-border-radius);background:var(--ate-background);overflow:visible;transition:border-color .2s ease;position:relative}:host(.floating-toolbar) .ate-editor{overflow:visible}:host(.fill-container) .ate-editor{display:flex;flex-direction:column;height:100%}:host(.fill-container) .ate-content-wrapper{flex:1;min-height:0}:host(.fill-container) .ate-content{flex:1;min-height:0;overflow-y:auto}.ate-editor:focus-within{border-color:var(--ate-focus-color)}.ate-content{min-height:var(--editor-min-height, 200px);height:var(--editor-height, auto);max-height:var(--editor-max-height, none);overflow-y:var(--editor-overflow, visible);outline:none;position:relative;scrollbar-width:thin;scrollbar-color:var(--ate-scrollbar-thumb) var(--ate-scrollbar-track)}:host(.is-disabled) .ate-content{cursor:not-allowed;opacity:.7;-webkit-user-select:none;user-select:none;pointer-events:none;background-color:var(--ate-surface-tertiary)}:host(.is-readonly) .ate-content{cursor:default;-webkit-user-select:text;user-select:text}:host(.is-readonly) .ate-content ::ng-deep .ate-link{cursor:pointer;pointer-events:auto}.ate-content::-webkit-scrollbar{width:var(--ate-scrollbar-width)}.ate-content-wrapper{position:relative;display:flex;flex-direction:column;min-height:0}.ate-content-wrapper .ate-content{flex:1}.ate-content::-webkit-scrollbar-track{background:var(--ate-scrollbar-track)}.ate-content::-webkit-scrollbar-thumb{background:var(--ate-scrollbar-thumb);border:3px solid transparent;background-clip:content-box;border-radius:10px}.ate-content::-webkit-scrollbar-thumb:hover{background:var(--ate-scrollbar-thumb-hover);background-clip:content-box}.ate-content.drag-over{background:var(--ate-drag-background);border:2px dashed var(--ate-drag-border-color)}.character-count{padding:6px 8px;font-size:12px;color:var(--ate-counter-color);text-align:right;border-top:1px solid var(--ate-counter-border-color);background:var(--ate-counter-background);transition:color .2s ease;border-bottom-left-radius:calc(var(--ate-border-radius) - var(--ate-border-width));border-bottom-right-radius:calc(var(--ate-border-radius) - var(--ate-border-width))}.character-count.limit-reached{color:var(--ate-error-color, #ef4444);font-weight:600}:host ::ng-deep .ProseMirror{padding:var(--ate-content-padding);outline:none;line-height:var(--ate-line-height);color:var(--ate-text-color);min-height:100%;height:100%;word-wrap:break-word;overflow-wrap:break-word}:host ::ng-deep .ProseMirror h1{font-size:2em;font-weight:700;margin-top:0;margin-bottom:.5em}:host ::ng-deep .ProseMirror h2{font-size:1.5em;font-weight:700;margin-top:1em;margin-bottom:.5em}:host ::ng-deep .ProseMirror h3{font-size:1.25em;font-weight:700;margin-top:1em;margin-bottom:.5em}:host ::ng-deep .ProseMirror p{margin:.5em 0}:host ::ng-deep .ProseMirror ul,:host ::ng-deep .ProseMirror ol{padding-left:2em;margin:.5em 0}:host ::ng-deep .ProseMirror blockquote{border-left:4px solid var(--ate-blockquote-border-color);margin:1em 0;background:var(--ate-blockquote-background);padding:.5em 1em;border-radius:0 4px 4px 0}:host ::ng-deep .ProseMirror code{background:var(--ate-code-background);color:var(--ate-code-color);border:1px solid var(--ate-code-border-color);padding:.15em .4em;border-radius:4px;font-family:JetBrains Mono,Fira Code,Monaco,Consolas,monospace;font-size:.85em;font-weight:500}:host ::ng-deep .ProseMirror pre{background:var(--ate-code-block-background);color:var(--ate-code-block-color);border:1px solid var(--ate-code-block-border-color);padding:1em;border-radius:var(--ate-border-radius);overflow-x:auto;margin:1em 0}:host ::ng-deep .ProseMirror pre code{background:none;color:inherit;border:none;padding:0}:host ::ng-deep .ProseMirror p.is-editor-empty:first-child:before{content:attr(data-placeholder);color:var(--ate-placeholder-color);pointer-events:none;float:left;height:0}:host ::ng-deep .ProseMirror[contenteditable=false]{pointer-events:none}:host ::ng-deep .ProseMirror[contenteditable=false] img{cursor:default;pointer-events:none}:host ::ng-deep .ProseMirror[contenteditable=false] img:hover{transform:none;box-shadow:0 2px 8px #0000001a}:host ::ng-deep .ProseMirror[contenteditable=false] img.ProseMirror-selectednode{outline:none}:host ::ng-deep .ProseMirror img{position:relative;display:inline-block;max-width:100%;height:auto;cursor:pointer;transition:all .2s ease;border:2px solid transparent;border-radius:var(--ate-image-border-radius)}:host ::ng-deep .ProseMirror img:hover{border-color:var(--ate-border-color);box-shadow:0 2px 4px #0000001a}:host ::ng-deep .ProseMirror img.ProseMirror-selectednode{border-color:var(--ate-image-selected-color);box-shadow:0 0 0 3px var(--ate-primary-light-alpha);transition:all .2s ease}:host ::ng-deep .ProseMirror .tiptap-image{max-width:100%;height:auto;border-radius:var(--ate-image-border-radius);box-shadow:0 4px 20px #00000014;margin:.5em 0;cursor:pointer;transition:all .3s cubic-bezier(.4,0,.2,1);display:block;filter:brightness(1) contrast(1)}:host ::ng-deep .ProseMirror .tiptap-image:hover{box-shadow:0 8px 30px #0000001f;filter:brightness(1.02) contrast(1.02)}:host ::ng-deep .ProseMirror .tiptap-image.ProseMirror-selectednode{outline:2px solid var(--ate-primary);outline-offset:2px;border-radius:var(--ate-image-border-radius);box-shadow:0 0 0 4px var(--ate-primary-light-alpha)}:host ::ng-deep .image-container{margin:.5em 0;text-align:center;border-radius:16px;overflow:hidden;transition:all .3s cubic-bezier(.4,0,.2,1)}:host ::ng-deep .image-container.image-align-left{text-align:left}:host ::ng-deep .image-container.image-align-center{text-align:center}:host ::ng-deep .image-container.image-align-right{text-align:right}:host ::ng-deep .image-container img{display:inline-block;max-width:100%;height:auto;border-radius:16px}:host ::ng-deep .resizable-image-container{position:relative;display:inline-block;margin:.5em 0}:host ::ng-deep .resize-controls{position:absolute;inset:0;pointer-events:none;z-index:1000}:host ::ng-deep .resize-handle{position:absolute;width:12px;height:12px;background:var(--ate-primary);border:2px solid var(--ate-surface);border-radius:50%;pointer-events:all;cursor:pointer;z-index:1001;transition:all .15s ease;box-shadow:0 2px 6px #0003}:host ::ng-deep .resize-handle:hover{background:var(--ate-primary);box-shadow:0 3px 8px #0000004d}:host ::ng-deep .resize-handle:active{background:var(--ate-primary)}:host ::ng-deep .resize-handle-n:hover,:host ::ng-deep .resize-handle-s:hover{transform:translate(-50%) scale(1.2)}:host ::ng-deep .resize-handle-w:hover,:host ::ng-deep .resize-handle-e:hover{transform:translateY(-50%) scale(1.2)}:host ::ng-deep .resize-handle-n:active,:host ::ng-deep .resize-handle-s:active{transform:translate(-50%) scale(.9)}:host ::ng-deep .resize-handle-w:active,:host ::ng-deep .resize-handle-e:active{transform:translateY(-50%) scale(.9)}:host ::ng-deep .resize-handle-nw:hover,:host ::ng-deep .resize-handle-ne:hover,:host ::ng-deep .resize-handle-sw:hover,:host ::ng-deep .resize-handle-se:hover{transform:scale(1.2)}:host ::ng-deep .resize-handle-nw:active,:host ::ng-deep .resize-handle-ne:active,:host ::ng-deep .resize-handle-sw:active,:host ::ng-deep .resize-handle-se:active{transform:scale(.9)}:host ::ng-deep .resize-handle-nw{top:0;left:-6px;cursor:nw-resize}:host ::ng-deep .resize-handle-n{top:0;left:50%;transform:translate(-50%);cursor:n-resize}:host ::ng-deep .resize-handle-ne{top:0;right:-6px;cursor:ne-resize}:host ::ng-deep .resize-handle-w{top:50%;left:-6px;transform:translateY(-50%);cursor:w-resize}:host ::ng-deep .resize-handle-e{top:50%;right:-6px;transform:translateY(-50%);cursor:e-resize}:host ::ng-deep .resize-handle-sw{bottom:0;left:-6px;cursor:sw-resize}:host ::ng-deep .resize-handle-s{bottom:0;left:50%;transform:translate(-50%);cursor:s-resize}:host ::ng-deep .resize-handle-se{bottom:0;right:-6px;cursor:se-resize}:host ::ng-deep body.resizing{-webkit-user-select:none;user-select:none;cursor:crosshair}:host ::ng-deep body.resizing .ProseMirror{pointer-events:none}:host ::ng-deep body.resizing .ProseMirror .tiptap-image{pointer-events:none}:host ::ng-deep .image-size-info{position:absolute;bottom:-20px;left:50%;transform:translate(-50%);background:#000c;color:#fff;padding:2px 6px;border-radius:3px;font-size:11px;white-space:nowrap;opacity:0;transition:opacity .2s ease}:host ::ng-deep .image-container:hover .image-size-info{opacity:1}:host ::ng-deep .ProseMirror table{border-collapse:separate;border-spacing:0;margin:0;table-layout:fixed;width:100%;border-radius:8px;overflow:hidden}:host ::ng-deep .ProseMirror table td,:host ::ng-deep .ProseMirror table th{border:none;border-right:1px solid var(--ate-table-border-color);border-bottom:1px solid var(--ate-table-border-color);box-sizing:border-box;min-width:1em;padding:8px 12px;position:relative;vertical-align:top;text-align:left}:host ::ng-deep .ProseMirror table td{background:var(--ate-table-cell-background)}:host ::ng-deep .ProseMirror table td:first-child,:host ::ng-deep .ProseMirror table th:first-child{border-left:1px solid var(--ate-table-border-color)}:host ::ng-deep .ProseMirror table tr:first-child td,:host ::ng-deep .ProseMirror table tr:first-child th{border-top:1px solid var(--ate-table-border-color)}:host ::ng-deep .ProseMirror table tr:first-child th:first-child{border-top-left-radius:8px}:host ::ng-deep .ProseMirror table tr:first-child th:last-child{border-top-right-radius:8px}:host ::ng-deep .ProseMirror table tr:last-child td:first-child{border-bottom-left-radius:8px}:host ::ng-deep .ProseMirror table tr:last-child td:last-child{border-bottom-right-radius:8px}:host ::ng-deep .ProseMirror table th{background:var(--ate-table-header-background);font-weight:600;color:var(--ate-table-header-color)}:host ::ng-deep .ProseMirror table .selectedCell:after{background:var(--ate-table-cell-selected-background);content:\"\";inset:0;pointer-events:none;position:absolute;z-index:2}:host ::ng-deep .ProseMirror table .column-resize-handle{position:absolute;right:-2px;top:0;bottom:0;width:4px;background-color:var(--ate-table-resize-handle-color);opacity:0;transition:opacity .2s ease}:host ::ng-deep .ProseMirror table:hover .column-resize-handle{opacity:1}:host ::ng-deep .ProseMirror table .column-resize-handle:hover{background-color:var(--ate-focus-color)}:host ::ng-deep .ProseMirror .tableWrapper{overflow-x:auto;margin:1em 0;border-radius:8px}:host ::ng-deep .ProseMirror .tableWrapper table{margin:0;border-radius:8px;min-width:600px;overflow:hidden}:host ::ng-deep .ProseMirror table p{margin:0}:host ::ng-deep .ProseMirror table tbody tr:hover td{background-color:var(--ate-table-row-hover-background)}\n"], dependencies: [{ kind: "component", type: AteToolbarComponent, selector: "ate-toolbar", inputs: ["editor", "config", "imageUpload", "floating"] }, { kind: "component", type: AteBubbleMenuComponent, selector: "ate-bubble-menu", inputs: ["config"] }, { kind: "component", type: AteImageBubbleMenuComponent, selector: "ate-image-bubble-menu", inputs: ["config", "imageUpload"] }, { kind: "component", type: AteTableBubbleMenuComponent, selector: "ate-table-bubble-menu", inputs: ["config"] }, { kind: "component", type: AteCellBubbleMenuComponent, selector: "ate-cell-bubble-menu", inputs: ["config"] }, { kind: "component", type: AteSlashCommandsComponent, selector: "ate-slash-commands", inputs: ["editor", "config"] }, { kind: "component", type: AteLinkBubbleMenuComponent, selector: "ate-link-bubble-menu" }, { kind: "component", type: AteColorBubbleMenuComponent, selector: "ate-color-bubble-menu" }, { kind: "component", type: AteEditToggleComponent, selector: "ate-edit-toggle", inputs: ["editable", "translations"], outputs: ["editToggle"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
6318
6447
|
}
|
|
6319
6448
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: AngularTiptapEditorComponent, decorators: [{
|
|
6320
6449
|
type: Component,
|
|
@@ -6431,7 +6560,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
|
|
|
6431
6560
|
|
|
6432
6561
|
<!-- Counters -->
|
|
6433
6562
|
@if (
|
|
6434
|
-
finalEditable() &&
|
|
6563
|
+
finalEditable() &&
|
|
6564
|
+
!mergedDisabled() &&
|
|
6565
|
+
finalShowFooter() &&
|
|
6566
|
+
(finalShowCharacterCount() || finalShowWordCount())
|
|
6435
6567
|
) {
|
|
6436
6568
|
<div
|
|
6437
6569
|
class="character-count"
|