ngx-rich-text-pro 1.0.6 → 1.0.7
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.
|
@@ -1660,17 +1660,74 @@ class NgxRichTextEditorComponent {
|
|
|
1660
1660
|
}
|
|
1661
1661
|
if (range.collapsed) {
|
|
1662
1662
|
this.focusEditor();
|
|
1663
|
-
const
|
|
1663
|
+
const formatAncestor = this.getFormattingAncestor(range, root);
|
|
1664
|
+
const isAncestorTarget = formatAncestor !== null && this.getFormatType(formatAncestor) === targetCommand;
|
|
1665
|
+
const isTargetActive = this.commandService.queryState(targetCommand) || isAncestorTarget;
|
|
1664
1666
|
if (isTargetActive) {
|
|
1665
|
-
|
|
1667
|
+
// Toggle OFF targetCommand
|
|
1668
|
+
if (formatAncestor) {
|
|
1669
|
+
if ((formatAncestor.textContent || '').trim() === '') {
|
|
1670
|
+
// Empty formatting tag (e.g. <p><b><br></b></p> after Enter): unwrap it
|
|
1671
|
+
const parent = formatAncestor.parentNode;
|
|
1672
|
+
if (parent) {
|
|
1673
|
+
const firstChild = formatAncestor.firstChild;
|
|
1674
|
+
while (formatAncestor.firstChild) {
|
|
1675
|
+
parent.insertBefore(formatAncestor.firstChild, formatAncestor);
|
|
1676
|
+
}
|
|
1677
|
+
formatAncestor.remove();
|
|
1678
|
+
if (firstChild) {
|
|
1679
|
+
range.setStartBefore(firstChild);
|
|
1680
|
+
range.collapse(true);
|
|
1681
|
+
sel.removeAllRanges();
|
|
1682
|
+
sel.addRange(range);
|
|
1683
|
+
}
|
|
1684
|
+
}
|
|
1685
|
+
}
|
|
1686
|
+
else if (this.isCaretAtEndOfElement(range, formatAncestor)) {
|
|
1687
|
+
// At the end of formatting tag: move caret outside after it
|
|
1688
|
+
range.setStartAfter(formatAncestor);
|
|
1689
|
+
range.collapse(true);
|
|
1690
|
+
sel.removeAllRanges();
|
|
1691
|
+
sel.addRange(range);
|
|
1692
|
+
}
|
|
1693
|
+
else if (this.isCaretAtStartOfElement(range, formatAncestor)) {
|
|
1694
|
+
// At the start of formatting tag: move caret outside before it
|
|
1695
|
+
range.setStartBefore(formatAncestor);
|
|
1696
|
+
range.collapse(true);
|
|
1697
|
+
sel.removeAllRanges();
|
|
1698
|
+
sel.addRange(range);
|
|
1699
|
+
}
|
|
1700
|
+
}
|
|
1701
|
+
if (this.commandService.queryState(targetCommand)) {
|
|
1702
|
+
this.commandService.execute(targetCommand);
|
|
1703
|
+
}
|
|
1666
1704
|
}
|
|
1667
1705
|
else {
|
|
1706
|
+
// Toggle ON targetCommand
|
|
1707
|
+
if (formatAncestor && (formatAncestor.textContent || '').trim() === '') {
|
|
1708
|
+
const parent = formatAncestor.parentNode;
|
|
1709
|
+
if (parent) {
|
|
1710
|
+
const firstChild = formatAncestor.firstChild;
|
|
1711
|
+
while (formatAncestor.firstChild) {
|
|
1712
|
+
parent.insertBefore(formatAncestor.firstChild, formatAncestor);
|
|
1713
|
+
}
|
|
1714
|
+
formatAncestor.remove();
|
|
1715
|
+
if (firstChild) {
|
|
1716
|
+
range.setStartBefore(firstChild);
|
|
1717
|
+
range.collapse(true);
|
|
1718
|
+
sel.removeAllRanges();
|
|
1719
|
+
sel.addRange(range);
|
|
1720
|
+
}
|
|
1721
|
+
}
|
|
1722
|
+
}
|
|
1668
1723
|
for (const cmd of otherCommands) {
|
|
1669
1724
|
if (this.commandService.queryState(cmd)) {
|
|
1670
1725
|
this.commandService.execute(cmd);
|
|
1671
1726
|
}
|
|
1672
1727
|
}
|
|
1673
|
-
this.commandService.
|
|
1728
|
+
if (!this.commandService.queryState(targetCommand)) {
|
|
1729
|
+
this.commandService.execute(targetCommand);
|
|
1730
|
+
}
|
|
1674
1731
|
}
|
|
1675
1732
|
this.onEditorInput();
|
|
1676
1733
|
this.updateEditorState();
|
|
@@ -1735,6 +1792,28 @@ class NgxRichTextEditorComponent {
|
|
|
1735
1792
|
}
|
|
1736
1793
|
return null;
|
|
1737
1794
|
}
|
|
1795
|
+
isCaretAtEndOfElement(range, el) {
|
|
1796
|
+
try {
|
|
1797
|
+
const endRange = document.createRange();
|
|
1798
|
+
endRange.selectNodeContents(el);
|
|
1799
|
+
endRange.collapse(false);
|
|
1800
|
+
return range.compareBoundaryPoints(Range.START_TO_START, endRange) === 0;
|
|
1801
|
+
}
|
|
1802
|
+
catch {
|
|
1803
|
+
return false;
|
|
1804
|
+
}
|
|
1805
|
+
}
|
|
1806
|
+
isCaretAtStartOfElement(range, el) {
|
|
1807
|
+
try {
|
|
1808
|
+
const startRange = document.createRange();
|
|
1809
|
+
startRange.selectNodeContents(el);
|
|
1810
|
+
startRange.collapse(true);
|
|
1811
|
+
return range.compareBoundaryPoints(Range.START_TO_START, startRange) === 0;
|
|
1812
|
+
}
|
|
1813
|
+
catch {
|
|
1814
|
+
return false;
|
|
1815
|
+
}
|
|
1816
|
+
}
|
|
1738
1817
|
normalizeRangeBoundaries(range, root) {
|
|
1739
1818
|
if (range.collapsed)
|
|
1740
1819
|
return;
|
|
@@ -1892,6 +1971,61 @@ class NgxRichTextEditorComponent {
|
|
|
1892
1971
|
if (!parentNode)
|
|
1893
1972
|
return;
|
|
1894
1973
|
this.normalizeRangeBoundaries(range);
|
|
1974
|
+
// If the entire ancestor is selected, simply unwrap or re-tag it!
|
|
1975
|
+
const startRange = document.createRange();
|
|
1976
|
+
startRange.setStart(range.startContainer, range.startOffset);
|
|
1977
|
+
startRange.collapse(true);
|
|
1978
|
+
const endRange = document.createRange();
|
|
1979
|
+
endRange.setEnd(range.endContainer, range.endOffset);
|
|
1980
|
+
endRange.collapse(false);
|
|
1981
|
+
const coversAll = this.isCaretAtStartOfElement(startRange, ancestor) &&
|
|
1982
|
+
this.isCaretAtEndOfElement(endRange, ancestor);
|
|
1983
|
+
if (coversAll) {
|
|
1984
|
+
if (newTag) {
|
|
1985
|
+
const wrapper = document.createElement(newTag);
|
|
1986
|
+
while (ancestor.firstChild) {
|
|
1987
|
+
wrapper.appendChild(ancestor.firstChild);
|
|
1988
|
+
}
|
|
1989
|
+
parentNode.replaceChild(wrapper, ancestor);
|
|
1990
|
+
const sel = typeof window !== 'undefined' ? window.getSelection() : null;
|
|
1991
|
+
if (sel) {
|
|
1992
|
+
sel.removeAllRanges();
|
|
1993
|
+
const newRange = document.createRange();
|
|
1994
|
+
newRange.selectNodeContents(wrapper);
|
|
1995
|
+
sel.addRange(newRange);
|
|
1996
|
+
}
|
|
1997
|
+
}
|
|
1998
|
+
else {
|
|
1999
|
+
const nodes = [];
|
|
2000
|
+
while (ancestor.firstChild) {
|
|
2001
|
+
const child = ancestor.firstChild;
|
|
2002
|
+
parentNode.insertBefore(child, ancestor);
|
|
2003
|
+
nodes.push(child);
|
|
2004
|
+
}
|
|
2005
|
+
ancestor.remove();
|
|
2006
|
+
const sel = typeof window !== 'undefined' ? window.getSelection() : null;
|
|
2007
|
+
if (sel && nodes.length > 0) {
|
|
2008
|
+
sel.removeAllRanges();
|
|
2009
|
+
const newRange = document.createRange();
|
|
2010
|
+
const first = nodes[0];
|
|
2011
|
+
const last = nodes[nodes.length - 1];
|
|
2012
|
+
if (first.nodeType === Node.TEXT_NODE) {
|
|
2013
|
+
newRange.setStart(first, 0);
|
|
2014
|
+
}
|
|
2015
|
+
else {
|
|
2016
|
+
newRange.setStartBefore(first);
|
|
2017
|
+
}
|
|
2018
|
+
if (last.nodeType === Node.TEXT_NODE) {
|
|
2019
|
+
newRange.setEnd(last, last.textContent?.length || 0);
|
|
2020
|
+
}
|
|
2021
|
+
else {
|
|
2022
|
+
newRange.setEndAfter(last);
|
|
2023
|
+
}
|
|
2024
|
+
sel.addRange(newRange);
|
|
2025
|
+
}
|
|
2026
|
+
}
|
|
2027
|
+
return;
|
|
2028
|
+
}
|
|
1895
2029
|
if (range.startContainer === range.endContainer &&
|
|
1896
2030
|
range.startContainer.nodeType === Node.TEXT_NODE) {
|
|
1897
2031
|
const textNode = range.startContainer;
|
|
@@ -1951,6 +2085,14 @@ class NgxRichTextEditorComponent {
|
|
|
1951
2085
|
return;
|
|
1952
2086
|
}
|
|
1953
2087
|
// General multi-node fallback within ancestor:
|
|
2088
|
+
// 1. Extract tail from range.end to end of ancestor into cloneAfter FIRST
|
|
2089
|
+
const cloneAfter = ancestor.cloneNode(false);
|
|
2090
|
+
const tailRange = document.createRange();
|
|
2091
|
+
tailRange.selectNodeContents(ancestor);
|
|
2092
|
+
tailRange.setStart(range.endContainer, range.endOffset);
|
|
2093
|
+
const tailFragment = tailRange.extractContents();
|
|
2094
|
+
cloneAfter.appendChild(tailFragment);
|
|
2095
|
+
// 2. Now extract middle from range.start to range.end into fragment
|
|
1954
2096
|
const fragment = range.extractContents();
|
|
1955
2097
|
const formatEls = Array.from(fragment.querySelectorAll('b, strong, i, em, u, ins'));
|
|
1956
2098
|
for (const el of formatEls) {
|
|
@@ -1967,12 +2109,6 @@ class NgxRichTextEditorComponent {
|
|
|
1967
2109
|
el.style.textDecoration = '';
|
|
1968
2110
|
}
|
|
1969
2111
|
}
|
|
1970
|
-
const cloneAfter = ancestor.cloneNode(false);
|
|
1971
|
-
const tailRange = document.createRange();
|
|
1972
|
-
tailRange.selectNodeContents(ancestor);
|
|
1973
|
-
tailRange.setStart(range.startContainer, range.startOffset);
|
|
1974
|
-
const tailFragment = tailRange.extractContents();
|
|
1975
|
-
cloneAfter.appendChild(tailFragment);
|
|
1976
2112
|
const insertionRef = ancestor.nextSibling;
|
|
1977
2113
|
let selectedNodes = [];
|
|
1978
2114
|
if (newTag) {
|
|
@@ -2100,17 +2236,26 @@ class NgxRichTextEditorComponent {
|
|
|
2100
2236
|
if (textNodes.length > 0) {
|
|
2101
2237
|
const isTargetTag = (el) => {
|
|
2102
2238
|
const tag = el.tagName.toLowerCase();
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2239
|
+
const fontWeight = (el.style?.fontWeight || '').toLowerCase();
|
|
2240
|
+
const fontStyle = (el.style?.fontStyle || '').toLowerCase();
|
|
2241
|
+
const textDecoration = (el.style?.textDecoration || '').toLowerCase();
|
|
2242
|
+
if (targetCommand === 'bold') {
|
|
2243
|
+
return (tag === 'b' ||
|
|
2244
|
+
tag === 'strong' ||
|
|
2245
|
+
fontWeight === 'bold' ||
|
|
2246
|
+
parseInt(fontWeight, 10) >= 600);
|
|
2247
|
+
}
|
|
2248
|
+
if (targetCommand === 'italic') {
|
|
2249
|
+
return tag === 'i' || tag === 'em' || fontStyle === 'italic';
|
|
2250
|
+
}
|
|
2251
|
+
if (targetCommand === 'underline') {
|
|
2252
|
+
return tag === 'u' || tag === 'ins' || textDecoration.includes('underline');
|
|
2253
|
+
}
|
|
2109
2254
|
return false;
|
|
2110
2255
|
};
|
|
2111
2256
|
const nonEmptyTextNodes = textNodes.filter((t) => (t.textContent || '').trim().length > 0);
|
|
2112
2257
|
if (nonEmptyTextNodes.length > 0) {
|
|
2113
|
-
|
|
2258
|
+
return nonEmptyTextNodes.every((node) => {
|
|
2114
2259
|
let cur = node.parentNode;
|
|
2115
2260
|
while (cur && cur !== root) {
|
|
2116
2261
|
if (cur.nodeType === Node.ELEMENT_NODE && isTargetTag(cur)) {
|
|
@@ -2120,9 +2265,6 @@ class NgxRichTextEditorComponent {
|
|
|
2120
2265
|
}
|
|
2121
2266
|
return false;
|
|
2122
2267
|
});
|
|
2123
|
-
if (allFormatted) {
|
|
2124
|
-
return true;
|
|
2125
|
-
}
|
|
2126
2268
|
}
|
|
2127
2269
|
}
|
|
2128
2270
|
return this.commandService.queryState(targetCommand);
|
|
@@ -2135,28 +2277,26 @@ class NgxRichTextEditorComponent {
|
|
|
2135
2277
|
if (leafBlocks.length > 1) {
|
|
2136
2278
|
let firstWrapper = null;
|
|
2137
2279
|
let lastWrapper = null;
|
|
2138
|
-
for (
|
|
2280
|
+
for (let i = 0; i < leafBlocks.length; i++) {
|
|
2281
|
+
const block = leafBlocks[i];
|
|
2139
2282
|
const text = block.textContent || '';
|
|
2140
2283
|
if (text.trim() === '') {
|
|
2141
2284
|
continue;
|
|
2142
2285
|
}
|
|
2286
|
+
const isFirst = i === 0;
|
|
2287
|
+
const isLast = i === leafBlocks.length - 1;
|
|
2143
2288
|
const subRange = document.createRange();
|
|
2144
|
-
if (block.contains(range.startContainer)) {
|
|
2289
|
+
if (isFirst && block.contains(range.startContainer)) {
|
|
2145
2290
|
subRange.setStart(range.startContainer, range.startOffset);
|
|
2146
2291
|
}
|
|
2147
2292
|
else {
|
|
2148
|
-
|
|
2149
|
-
subRange.setStart(firstText, 0);
|
|
2293
|
+
subRange.setStart(block, 0);
|
|
2150
2294
|
}
|
|
2151
|
-
if (block.contains(range.endContainer)) {
|
|
2295
|
+
if (isLast && block.contains(range.endContainer)) {
|
|
2152
2296
|
subRange.setEnd(range.endContainer, range.endOffset);
|
|
2153
2297
|
}
|
|
2154
2298
|
else {
|
|
2155
|
-
|
|
2156
|
-
const len = lastText.nodeType === Node.TEXT_NODE
|
|
2157
|
-
? lastText.textContent?.length || 0
|
|
2158
|
-
: lastText.childNodes.length;
|
|
2159
|
-
subRange.setEnd(lastText, len);
|
|
2299
|
+
subRange.setEnd(block, block.childNodes.length);
|
|
2160
2300
|
}
|
|
2161
2301
|
if (!subRange.collapsed) {
|
|
2162
2302
|
const wrapper = this.applyExclusiveFormatToSingleRange(subRange, targetCommand);
|
|
@@ -2231,28 +2371,26 @@ class NgxRichTextEditorComponent {
|
|
|
2231
2371
|
if (leafBlocks.length > 1) {
|
|
2232
2372
|
let firstBlock = null;
|
|
2233
2373
|
let lastBlock = null;
|
|
2234
|
-
for (
|
|
2374
|
+
for (let i = 0; i < leafBlocks.length; i++) {
|
|
2375
|
+
const block = leafBlocks[i];
|
|
2235
2376
|
const text = block.textContent || '';
|
|
2236
2377
|
if (text.trim() === '') {
|
|
2237
2378
|
continue;
|
|
2238
2379
|
}
|
|
2380
|
+
const isFirst = i === 0;
|
|
2381
|
+
const isLast = i === leafBlocks.length - 1;
|
|
2239
2382
|
const subRange = document.createRange();
|
|
2240
|
-
if (block.contains(range.startContainer)) {
|
|
2383
|
+
if (isFirst && block.contains(range.startContainer)) {
|
|
2241
2384
|
subRange.setStart(range.startContainer, range.startOffset);
|
|
2242
2385
|
}
|
|
2243
2386
|
else {
|
|
2244
|
-
|
|
2245
|
-
subRange.setStart(firstText, 0);
|
|
2387
|
+
subRange.setStart(block, 0);
|
|
2246
2388
|
}
|
|
2247
|
-
if (block.contains(range.endContainer)) {
|
|
2389
|
+
if (isLast && block.contains(range.endContainer)) {
|
|
2248
2390
|
subRange.setEnd(range.endContainer, range.endOffset);
|
|
2249
2391
|
}
|
|
2250
2392
|
else {
|
|
2251
|
-
|
|
2252
|
-
const len = lastText.nodeType === Node.TEXT_NODE
|
|
2253
|
-
? lastText.textContent?.length || 0
|
|
2254
|
-
: lastText.childNodes.length;
|
|
2255
|
-
subRange.setEnd(lastText, len);
|
|
2393
|
+
subRange.setEnd(block, block.childNodes.length);
|
|
2256
2394
|
}
|
|
2257
2395
|
if (!subRange.collapsed) {
|
|
2258
2396
|
this.removeFormatFromSingleRange(subRange, targetCommand);
|
|
@@ -2329,30 +2467,31 @@ class NgxRichTextEditorComponent {
|
|
|
2329
2467
|
const tag = el.tagName.toLowerCase();
|
|
2330
2468
|
return ['b', 'strong', 'i', 'em', 'u', 'ins'].includes(tag);
|
|
2331
2469
|
};
|
|
2332
|
-
let
|
|
2333
|
-
while (
|
|
2334
|
-
if (
|
|
2335
|
-
|
|
2336
|
-
|
|
2470
|
+
let sNode = range.startContainer;
|
|
2471
|
+
while (sNode && sNode !== root) {
|
|
2472
|
+
if (sNode.nodeType === Node.ELEMENT_NODE && isFormattingTag(sNode)) {
|
|
2473
|
+
const sRange = document.createRange();
|
|
2474
|
+
sRange.setStart(range.startContainer, range.startOffset);
|
|
2475
|
+
sRange.collapse(true);
|
|
2476
|
+
if (this.isCaretAtStartOfElement(sRange, sNode)) {
|
|
2477
|
+
range.setStartBefore(sNode);
|
|
2337
2478
|
}
|
|
2338
2479
|
break;
|
|
2339
2480
|
}
|
|
2340
|
-
|
|
2341
|
-
break;
|
|
2342
|
-
startNode = startNode.parentNode;
|
|
2481
|
+
sNode = sNode.parentNode;
|
|
2343
2482
|
}
|
|
2344
|
-
let
|
|
2345
|
-
while (
|
|
2346
|
-
if (
|
|
2347
|
-
const
|
|
2348
|
-
|
|
2349
|
-
|
|
2483
|
+
let eNode = range.endContainer;
|
|
2484
|
+
while (eNode && eNode !== root) {
|
|
2485
|
+
if (eNode.nodeType === Node.ELEMENT_NODE && isFormattingTag(eNode)) {
|
|
2486
|
+
const eRange = document.createRange();
|
|
2487
|
+
eRange.setEnd(range.endContainer, range.endOffset);
|
|
2488
|
+
eRange.collapse(false);
|
|
2489
|
+
if (this.isCaretAtEndOfElement(eRange, eNode)) {
|
|
2490
|
+
range.setEndAfter(eNode);
|
|
2350
2491
|
}
|
|
2351
2492
|
break;
|
|
2352
2493
|
}
|
|
2353
|
-
|
|
2354
|
-
break;
|
|
2355
|
-
endNode = endNode.parentNode;
|
|
2494
|
+
eNode = eNode.parentNode;
|
|
2356
2495
|
}
|
|
2357
2496
|
}
|
|
2358
2497
|
sanitizeExclusiveFormatting(container) {
|
|
@@ -3071,32 +3210,38 @@ class NgxRichTextEditorComponent {
|
|
|
3071
3210
|
if (this.exclusiveInlineFormats ?? this.computedConfig.exclusiveInlineFormats) {
|
|
3072
3211
|
const sel = typeof window !== 'undefined' ? window.getSelection() : null;
|
|
3073
3212
|
const root = this.editorContentRef?.nativeElement;
|
|
3074
|
-
let formatAncestor = null;
|
|
3075
3213
|
if (sel && sel.rangeCount > 0 && root) {
|
|
3076
3214
|
const range = sel.getRangeAt(0);
|
|
3077
3215
|
if (root.contains(range.commonAncestorContainer)) {
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
underline = false;
|
|
3092
|
-
}
|
|
3093
|
-
else {
|
|
3094
|
-
if (underline) {
|
|
3095
|
-
bold = false;
|
|
3096
|
-
italic = false;
|
|
3216
|
+
if (!range.collapsed) {
|
|
3217
|
+
const formatAncestor = this.getFormattingAncestor(range, root);
|
|
3218
|
+
if (formatAncestor) {
|
|
3219
|
+
const type = this.getFormatType(formatAncestor);
|
|
3220
|
+
bold = type === 'bold';
|
|
3221
|
+
italic = type === 'italic';
|
|
3222
|
+
underline = type === 'underline';
|
|
3223
|
+
}
|
|
3224
|
+
else {
|
|
3225
|
+
bold = this.isFormatActiveOnRange(range, 'bold', root);
|
|
3226
|
+
italic = !bold && this.isFormatActiveOnRange(range, 'italic', root);
|
|
3227
|
+
underline = !bold && !italic && this.isFormatActiveOnRange(range, 'underline', root);
|
|
3228
|
+
}
|
|
3097
3229
|
}
|
|
3098
|
-
else
|
|
3099
|
-
|
|
3230
|
+
else {
|
|
3231
|
+
const formatAncestor = this.getFormattingAncestor(range, root);
|
|
3232
|
+
if (formatAncestor) {
|
|
3233
|
+
const type = this.getFormatType(formatAncestor);
|
|
3234
|
+
bold = type === 'bold';
|
|
3235
|
+
italic = type === 'italic';
|
|
3236
|
+
underline = type === 'underline';
|
|
3237
|
+
}
|
|
3238
|
+
if (underline) {
|
|
3239
|
+
bold = false;
|
|
3240
|
+
italic = false;
|
|
3241
|
+
}
|
|
3242
|
+
else if (italic) {
|
|
3243
|
+
bold = false;
|
|
3244
|
+
}
|
|
3100
3245
|
}
|
|
3101
3246
|
}
|
|
3102
3247
|
}
|
|
@@ -3633,7 +3778,7 @@ class NgxRichTextEditorComponent {
|
|
|
3633
3778
|
useExisting: forwardRef(() => NgxRichTextEditorComponent),
|
|
3634
3779
|
multi: true,
|
|
3635
3780
|
},
|
|
3636
|
-
], viewQueries: [{ propertyName: "editorContentRef", first: true, predicate: ["editorContent"], descendants: true }, { propertyName: "sourceTextareaRef", first: true, predicate: ["sourceTextarea"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div\n class=\"ngx-editor-container\"\n [ngClass]=\"customClass\"\n [class.is-focused]=\"isFocused\"\n [class.is-disabled]=\"disabled\"\n [style.min-height]=\"computedConfig.minHeight\"\n [style.max-height]=\"computedConfig.maxHeight\"\n [style.font-family]=\"fontFamily || computedConfig.fontFamily || null\"\n [style.font-size]=\"fontSize || computedConfig.fontSize || null\"\n [ngStyle]=\"parsedEditorStyle\"\n>\n <!-- Toolbar -->\n @if (computedConfig.showToolbar && !readonly) {\n <!-- Array-configured Toolbar (if tools input is provided) -->\n @if (computedConfig.tools && computedConfig.tools.length > 0) {\n <ngx-editor-toolbar\n [editor]=\"this\"\n [tools]=\"computedConfig.tools\"\n [fontFamilies]=\"resolvedFontFamilies\"\n ></ngx-editor-toolbar>\n } @else {\n <!-- Default Full Toolbar -->\n <div class=\"ngx-editor-toolbar\" role=\"toolbar\" aria-label=\"Editor Toolbar\">\n <!-- History -->\n @if (computedConfig.toolbar?.history) {\n <div class=\"ngx-editor-toolbar-group\">\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n title=\"Undo (Ctrl+Z)\"\n aria-label=\"Undo\"\n (mousedown)=\"$event.preventDefault(); undo()\"\n >\n <ngx-editor-icon name=\"undo\"></ngx-editor-icon>\n </button>\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n title=\"Redo (Ctrl+Y)\"\n aria-label=\"Redo\"\n (mousedown)=\"$event.preventDefault(); redo()\"\n >\n <ngx-editor-icon name=\"redo\"></ngx-editor-icon>\n </button>\n </div>\n }\n\n <!-- Format Block -->\n @if (computedConfig.toolbar?.format) {\n <div class=\"ngx-editor-toolbar-group\">\n <select\n class=\"ngx-editor-select\"\n title=\"Paragraph Format\"\n aria-label=\"Paragraph Format\"\n [value]=\"activeBlockTag\"\n (change)=\"onFormatBlockChange($event)\"\n >\n <option value=\"p\">Normal text</option>\n <option value=\"h1\">Heading 1</option>\n <option value=\"h2\">Heading 2</option>\n <option value=\"h3\">Heading 3</option>\n <option value=\"h4\">Heading 4</option>\n <option value=\"blockquote\">Quote</option>\n <option value=\"pre\">Code Block</option>\n </select>\n </div>\n }\n\n <!-- Font Family -->\n @if (computedConfig.toolbar?.fontName) {\n <div class=\"ngx-editor-toolbar-group\">\n <select\n class=\"ngx-editor-select\"\n title=\"Font Family\"\n aria-label=\"Font Family\"\n (change)=\"onFontNameChange($event)\"\n >\n <option value=\"\">Font</option>\n @for (font of resolvedFontFamilies; track font.value) {\n <option [value]=\"font.value\">{{ font.label }}</option>\n }\n </select>\n </div>\n }\n\n <!-- Font Size -->\n @if (computedConfig.toolbar?.fontSize) {\n <div class=\"ngx-editor-toolbar-group\">\n <select\n class=\"ngx-editor-select\"\n title=\"Font Size\"\n aria-label=\"Font Size\"\n (change)=\"onFontSizeChange($event)\"\n >\n <option value=\"\">Size</option>\n <option value=\"1\">Small (10pt)</option>\n <option value=\"3\">Normal (12pt)</option>\n <option value=\"5\">Large (18pt)</option>\n <option value=\"7\">Huge (36pt)</option>\n </select>\n </div>\n }\n\n <!-- Inline Styles: Bold, Italic, Underline, Strike, Sub, Sup, Code -->\n @if (computedConfig.toolbar?.inline) {\n <div class=\"ngx-editor-toolbar-group\">\n @if (computedConfig.toolbar?.inline?.bold !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n [class.is-active]=\"activeStates.bold\"\n title=\"Bold (Ctrl+B)\"\n aria-label=\"Bold\"\n (mousedown)=\"$event.preventDefault(); bold()\"\n >\n <ngx-editor-icon name=\"bold\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.inline?.italic !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n [class.is-active]=\"activeStates.italic\"\n title=\"Italic (Ctrl+I)\"\n aria-label=\"Italic\"\n (mousedown)=\"$event.preventDefault(); italic()\"\n >\n <ngx-editor-icon name=\"italic\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.inline?.underline !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n [class.is-active]=\"activeStates.underline\"\n title=\"Underline (Ctrl+U)\"\n aria-label=\"Underline\"\n (mousedown)=\"$event.preventDefault(); underline()\"\n >\n <ngx-editor-icon name=\"underline\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.inline?.strikeThrough !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n [class.is-active]=\"activeStates.strikeThrough\"\n title=\"Strikethrough\"\n aria-label=\"Strikethrough\"\n (mousedown)=\"$event.preventDefault(); strike()\"\n >\n <ngx-editor-icon name=\"strike\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.inline?.subscript !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n [class.is-active]=\"activeStates.subscript\"\n title=\"Subscript\"\n aria-label=\"Subscript\"\n (mousedown)=\"$event.preventDefault(); subscript()\"\n >\n <ngx-editor-icon name=\"subscript\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.inline?.superscript !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n [class.is-active]=\"activeStates.superscript\"\n title=\"Superscript\"\n aria-label=\"Superscript\"\n (mousedown)=\"$event.preventDefault(); superscript()\"\n >\n <ngx-editor-icon name=\"superscript\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.inline?.code !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n title=\"Inline Code\"\n aria-label=\"Inline Code\"\n (mousedown)=\"$event.preventDefault(); wrapSelectionWithCode()\"\n >\n <ngx-editor-icon name=\"code\"></ngx-editor-icon>\n </button>\n }\n </div>\n }\n\n <!-- Colors -->\n @if (computedConfig.toolbar?.color) {\n <div class=\"ngx-editor-toolbar-group\" style=\"position: relative;\">\n @if (computedConfig.toolbar?.color?.textColor !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn ngx-editor-color-btn\"\n title=\"Text Color\"\n aria-label=\"Text Color\"\n (click)=\"toggleColorPicker('text')\"\n >\n <ngx-editor-icon name=\"textColor\"></ngx-editor-icon>\n </button>\n <div\n class=\"color-bar\"\n [style.background-color]=\"selectedTextColor\"\n ></div>\n }\n\n @if (computedConfig.toolbar?.color?.backgroundColor !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn ngx-editor-color-btn\"\n title=\"Highlight Color\"\n aria-label=\"Highlight Color\"\n (click)=\"toggleColorPicker('background')\"\n >\n <ngx-editor-icon name=\"bgColor\"></ngx-editor-icon>\n </button>\n <div\n class=\"color-bar\"\n [style.background-color]=\"selectedBgColor\"\n ></div>\n }\n\n <!-- Color Popover -->\n @if (activeColorPicker) {\n <div class=\"ngx-color-popover\" (mousedown)=\"$event.stopPropagation()\">\n <div class=\"color-grid\">\n @for (color of computedConfig.colorPalette; track color) {\n <button\n type=\"button\"\n class=\"color-swatch\"\n [style.background-color]=\"color\"\n (click)=\"applyColor(color)\"\n ></button>\n }\n </div>\n <div class=\"custom-color-picker\">\n <input\n type=\"color\"\n (change)=\"onCustomColorChange($event)\"\n />\n <span>Custom color</span>\n </div>\n </div>\n }\n </div>\n }\n\n <!-- Alignment -->\n @if (computedConfig.toolbar?.alignment) {\n <div class=\"ngx-editor-toolbar-group\">\n @if (computedConfig.toolbar?.alignment?.left !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n [class.is-active]=\"activeStates.justifyLeft\"\n title=\"Align Left\"\n aria-label=\"Align Left\"\n (mousedown)=\"$event.preventDefault(); alignLeft()\"\n >\n <ngx-editor-icon name=\"alignLeft\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.alignment?.center !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n [class.is-active]=\"activeStates.justifyCenter\"\n title=\"Align Center\"\n aria-label=\"Align Center\"\n (mousedown)=\"$event.preventDefault(); alignCenter()\"\n >\n <ngx-editor-icon name=\"alignCenter\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.alignment?.right !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n [class.is-active]=\"activeStates.justifyRight\"\n title=\"Align Right\"\n aria-label=\"Align Right\"\n (mousedown)=\"$event.preventDefault(); alignRight()\"\n >\n <ngx-editor-icon name=\"alignRight\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.alignment?.justify !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n [class.is-active]=\"activeStates.justifyFull\"\n title=\"Justify\"\n aria-label=\"Justify\"\n (mousedown)=\"$event.preventDefault(); alignJustify()\"\n >\n <ngx-editor-icon name=\"alignJustify\"></ngx-editor-icon>\n </button>\n }\n </div>\n }\n\n <!-- Lists & Indentation -->\n @if (computedConfig.toolbar?.list) {\n <div class=\"ngx-editor-toolbar-group\">\n @if (computedConfig.toolbar?.list?.unordered !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n [class.is-active]=\"activeStates.insertUnorderedList\"\n title=\"Bullet List\"\n aria-label=\"Bullet List\"\n (mousedown)=\"$event.preventDefault(); insertUnorderedList()\"\n >\n <ngx-editor-icon name=\"unorderedList\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.list?.ordered !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n [class.is-active]=\"activeStates.insertOrderedList\"\n title=\"Numbered List\"\n aria-label=\"Numbered List\"\n (mousedown)=\"$event.preventDefault(); insertOrderedList()\"\n >\n <ngx-editor-icon name=\"orderedList\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.list?.indent !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n title=\"Increase Indent\"\n aria-label=\"Increase Indent\"\n (mousedown)=\"$event.preventDefault(); indent()\"\n >\n <ngx-editor-icon name=\"indent\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.list?.outdent !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n title=\"Decrease Indent\"\n aria-label=\"Decrease Indent\"\n (mousedown)=\"$event.preventDefault(); outdent()\"\n >\n <ngx-editor-icon name=\"outdent\"></ngx-editor-icon>\n </button>\n }\n </div>\n }\n\n <!-- Insert Options -->\n @if (computedConfig.toolbar?.insert) {\n <div class=\"ngx-editor-toolbar-group\">\n @if (computedConfig.toolbar?.insert?.link !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n [class.is-active]=\"activeStates.link\"\n title=\"Insert Link (Ctrl+K)\"\n aria-label=\"Insert Link\"\n (click)=\"openLinkModal()\"\n >\n <ngx-editor-icon name=\"link\"></ngx-editor-icon>\n </button>\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n title=\"Remove Link\"\n aria-label=\"Remove Link\"\n (mousedown)=\"$event.preventDefault(); unlink()\"\n >\n <ngx-editor-icon name=\"unlink\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.insert?.image !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n title=\"Insert Image\"\n aria-label=\"Insert Image\"\n (click)=\"openImageModal()\"\n >\n <ngx-editor-icon name=\"image\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.insert?.horizontalRule !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n title=\"Horizontal Line\"\n aria-label=\"Horizontal Line\"\n (mousedown)=\"$event.preventDefault(); insertHorizontalRule()\"\n >\n <ngx-editor-icon name=\"horizontalRule\"></ngx-editor-icon>\n </button>\n }\n </div>\n }\n\n <!-- Extra Actions -->\n @if (computedConfig.toolbar?.extra) {\n <div class=\"ngx-editor-toolbar-group\">\n @if (computedConfig.toolbar?.extra?.clearFormat !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n title=\"Clear Formatting\"\n aria-label=\"Clear Formatting\"\n (mousedown)=\"$event.preventDefault(); clearFormatting()\"\n >\n <ngx-editor-icon name=\"clearFormat\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.extra?.sourceView !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n [class.is-active]=\"isSourceView\"\n title=\"View HTML Source\"\n aria-label=\"View HTML Source\"\n (click)=\"toggleSourceView()\"\n >\n <ngx-editor-icon name=\"sourceView\"></ngx-editor-icon>\n </button>\n }\n </div>\n }\n </div>\n }\n }\n\n <!-- Editor Content Area -->\n <div class=\"ngx-editor-content-wrapper\">\n <!-- Visual contenteditable -->\n <div\n #editorContent\n class=\"ngx-editor-content\"\n [style.display]=\"isSourceView ? 'none' : 'block'\"\n [style.height]=\"computedConfig.height\"\n [style.font-family]=\"fontFamily || computedConfig.fontFamily || null\"\n [style.font-size]=\"fontSize || computedConfig.fontSize || null\"\n [ngStyle]=\"parsedContentStyle\"\n [attr.contenteditable]=\"!disabled && !readonly\"\n [attr.spellcheck]=\"computedConfig.spellcheck\"\n [attr.data-placeholder]=\"computedConfig.placeholder\"\n [attr.data-empty]=\"isEmpty\"\n (input)=\"onEditorInput()\"\n (focus)=\"onEditorFocus($event)\"\n (blur)=\"onEditorBlur($event)\"\n (keydown)=\"onEditorKeydown($event)\"\n (keyup)=\"updateEditorState()\"\n (mouseup)=\"updateEditorState()\"\n (paste)=\"onEditorPaste($event)\"\n ></div>\n\n <!-- Raw HTML Source Code View -->\n @if (isSourceView) {\n <textarea\n #sourceTextarea\n class=\"ngx-editor-source\"\n [style.height]=\"computedConfig.height\"\n [disabled]=\"disabled || readonly\"\n [value]=\"content\"\n (input)=\"onSourceInput($event)\"\n ></textarea>\n }\n </div>\n\n <!-- Status Bar -->\n @if (computedConfig.showStatusBar) {\n <div class=\"ngx-editor-statusbar\">\n <div class=\"statusbar-left\">\n @if (activeBlockTag) {\n <span class=\"status-tag\"><{{ activeBlockTag }}></span>\n }\n @if (isSourceView) {\n <span class=\"status-badge\">HTML SOURCE MODE</span>\n }\n </div>\n <div class=\"statusbar-right\">\n @if (computedConfig.showWordCount) {\n <span>Words: <strong>{{ stats.words }}</strong></span>\n }\n @if (computedConfig.maxCharacters) {\n <span [class.limit-reached]=\"stats.chars > computedConfig.maxCharacters\">\n {{ stats.chars }}/{{ computedConfig.maxCharacters }} Characters\n </span>\n } @else if (computedConfig.showCharCount) {\n <span>Characters: <strong>{{ stats.chars }}</strong></span>\n }\n </div>\n </div>\n }\n\n <!-- Link Modal -->\n @if (showLinkModal) {\n <div class=\"ngx-editor-modal-backdrop\" (click)=\"closeLinkModal()\">\n <div class=\"ngx-editor-modal\" (click)=\"$event.stopPropagation()\">\n <div class=\"modal-header\">\n <h3>Insert Link</h3>\n <button type=\"button\" class=\"ngx-editor-btn\" (click)=\"closeLinkModal()\" aria-label=\"Close\">\n <ngx-editor-icon name=\"close\"></ngx-editor-icon>\n </button>\n </div>\n <div class=\"modal-body\">\n <div class=\"form-group\">\n <label>Link Text</label>\n <input\n type=\"text\"\n placeholder=\"Display text\"\n [(ngModel)]=\"linkModel.text\"\n />\n </div>\n <div class=\"form-group\">\n <label>Target URL</label>\n <input\n type=\"url\"\n placeholder=\"https://example.com\"\n [(ngModel)]=\"linkModel.url\"\n (keydown.enter)=\"insertLinkFromModal()\"\n />\n </div>\n <div class=\"form-group\">\n <label class=\"checkbox-label\">\n <input type=\"checkbox\" [(ngModel)]=\"linkModel.newTab\" />\n Open in a new tab\n </label>\n </div>\n </div>\n <div class=\"modal-footer\">\n <button type=\"button\" class=\"btn-secondary\" (click)=\"closeLinkModal()\">Cancel</button>\n <button\n type=\"button\"\n class=\"btn-primary\"\n [disabled]=\"!linkModel.url\"\n (click)=\"insertLinkFromModal()\"\n >\n Insert Link\n </button>\n </div>\n </div>\n </div>\n }\n\n <!-- Image Modal -->\n @if (showImageModal) {\n <div class=\"ngx-editor-modal-backdrop\" (click)=\"closeImageModal()\">\n <div class=\"ngx-editor-modal\" (click)=\"$event.stopPropagation()\">\n <div class=\"modal-header\">\n <h3>Insert Image</h3>\n <button type=\"button\" class=\"ngx-editor-btn\" (click)=\"closeImageModal()\" aria-label=\"Close\">\n <ngx-editor-icon name=\"close\"></ngx-editor-icon>\n </button>\n </div>\n <div class=\"modal-body\">\n <div class=\"form-group\">\n <label>Image URL</label>\n <input\n type=\"url\"\n placeholder=\"https://example.com/image.png\"\n [(ngModel)]=\"imageModel.url\"\n />\n </div>\n <div class=\"form-group\">\n <label>Or Upload File</label>\n <input\n type=\"file\"\n accept=\"image/*\"\n (change)=\"onImageFileUpload($event)\"\n />\n </div>\n <div class=\"form-group\">\n <label>Alt Description</label>\n <input\n type=\"text\"\n placeholder=\"Image description\"\n [(ngModel)]=\"imageModel.alt\"\n />\n </div>\n </div>\n <div class=\"modal-footer\">\n <button type=\"button\" class=\"btn-secondary\" (click)=\"closeImageModal()\">Cancel</button>\n <button\n type=\"button\"\n class=\"btn-primary\"\n [disabled]=\"!imageModel.url\"\n (click)=\"insertImageFromModal()\"\n >\n Insert Image\n </button>\n </div>\n </div>\n </div>\n }\n</div>\n", styles: [":host{display:block;font-family:var(--ngx-editor-font-family, inherit);box-sizing:border-box;--ngx-editor-font-family: inherit;--ngx-editor-font-size: 15px;--ngx-editor-line-height: 1.65;--ngx-editor-border: #e2e8f0;--ngx-editor-border-focus: #3b82f6;--ngx-editor-bg: #ffffff;--ngx-editor-toolbar-bg: #f8fafc;--ngx-editor-toolbar-btn-hover: #e2e8f0;--ngx-editor-toolbar-btn-active: #dbeafe;--ngx-editor-toolbar-btn-active-color: #1d4ed8;--ngx-editor-text: #1e293b;--ngx-editor-muted: #64748b;--ngx-editor-radius: 8px;--ngx-editor-shadow: 0 1px 3px 0 rgba(0, 0, 0, .1), 0 1px 2px -1px rgba(0, 0, 0, .1);--ngx-editor-shadow-focus: 0 0 0 3px rgba(59, 130, 246, .25);--ngx-editor-hashtag-color: #0069FF}.ngx-editor-container{display:flex;flex-direction:column;background-color:var(--ngx-editor-bg);border:1px solid var(--ngx-editor-border);border-radius:var(--ngx-editor-radius);box-shadow:var(--ngx-editor-shadow);overflow:hidden;transition:border-color .2s ease,box-shadow .2s ease;position:relative}.ngx-editor-container.is-focused{border-color:var(--ngx-editor-border-focus);box-shadow:var(--ngx-editor-shadow-focus)}.ngx-editor-container.is-disabled{background-color:#f1f5f9;opacity:.75;pointer-events:none;-webkit-user-select:none;user-select:none}.ngx-editor-toolbar{display:flex;flex-wrap:wrap;align-items:center;gap:4px;padding:6px 8px;background-color:var(--ngx-editor-toolbar-bg);border-bottom:1px solid var(--ngx-editor-border);-webkit-user-select:none;user-select:none;z-index:10}.ngx-editor-toolbar-group{display:flex;align-items:center;gap:2px}.ngx-editor-toolbar-group:not(:last-child):after{content:\"\";display:inline-block;width:1px;height:18px;background-color:var(--ngx-editor-border);margin:0 4px}.ngx-editor-btn{display:inline-flex;align-items:center;justify-content:center;width:32px;height:32px;padding:0;border:1px solid transparent;border-radius:4px;background:transparent;color:var(--ngx-editor-text);cursor:pointer;transition:background-color .15s ease,color .15s ease,border-color .15s ease;outline:none}.ngx-editor-btn:hover:not(:disabled){background-color:var(--ngx-editor-toolbar-btn-hover)}.ngx-editor-btn:active:not(:disabled),.ngx-editor-btn.is-active{background-color:var(--ngx-editor-toolbar-btn-active);color:var(--ngx-editor-toolbar-btn-active-color);border-color:#bfdbfe}.ngx-editor-btn:disabled{opacity:.35;cursor:not-allowed}.ngx-editor-btn svg{display:block;pointer-events:none}.ngx-editor-select{height:32px;padding:0 8px;font-size:13px;color:var(--ngx-editor-text);background-color:var(--ngx-editor-bg);border:1px solid var(--ngx-editor-border);border-radius:4px;outline:none;cursor:pointer;transition:border-color .15s ease}.ngx-editor-select:hover{border-color:#cbd5e1}.ngx-editor-select:focus{border-color:var(--ngx-editor-border-focus)}.ngx-editor-color-btn{position:relative}.ngx-editor-color-btn .color-bar{position:absolute;bottom:4px;left:6px;right:6px;height:3px;border-radius:1px}.ngx-color-popover{position:absolute;top:100%;left:0;margin-top:4px;background:#fff;border:1px solid var(--ngx-editor-border);border-radius:6px;box-shadow:0 10px 15px -3px #0000001a,0 4px 6px -4px #0000001a;padding:8px;z-index:50;width:176px}.ngx-color-popover .color-grid{display:grid;grid-template-columns:repeat(6,1fr);gap:4px;margin-bottom:8px}.ngx-color-popover .color-swatch{width:22px;height:22px;border-radius:3px;border:1px solid rgba(0,0,0,.15);cursor:pointer;padding:0;transition:transform .1s ease}.ngx-color-popover .color-swatch:hover{transform:scale(1.15);z-index:2}.ngx-color-popover .custom-color-picker{display:flex;align-items:center;gap:8px;font-size:12px;color:var(--ngx-editor-muted)}.ngx-color-popover .custom-color-picker input[type=color]{width:28px;height:24px;border:1px solid var(--ngx-editor-border);border-radius:3px;padding:0;cursor:pointer;background:none}.ngx-editor-content-wrapper{position:relative;flex:1;display:flex;flex-direction:column}.ngx-editor-content{flex:1;padding:16px;outline:none;font-family:var(--ngx-editor-font-family, inherit);font-size:var(--ngx-editor-font-size, 15px);line-height:var(--ngx-editor-line-height, 1.65);color:var(--ngx-editor-text);overflow-y:auto;min-height:200px;word-break:break-word}.ngx-editor-content p{margin:0;min-height:1.65em}.ngx-editor-content b,.ngx-editor-content strong{font-weight:700!important}.ngx-editor-content i,.ngx-editor-content em{font-style:italic!important}.ngx-editor-content u,.ngx-editor-content .publish-editor-underline{text-decoration:underline!important;text-decoration-thickness:1px;text-underline-offset:.12em}.ngx-editor-content h1{font-size:2em;font-weight:700;margin:.67em 0;line-height:1.25}.ngx-editor-content h2{font-size:1.6em;font-weight:600;margin:.75em 0;line-height:1.3}.ngx-editor-content h3{font-size:1.3em;font-weight:600;margin:.83em 0}.ngx-editor-content h4{font-size:1.1em;font-weight:600;margin:1em 0}.ngx-editor-content ul,.ngx-editor-content ol{margin:0 0 1em 1.5em;padding:0}.ngx-editor-content li{margin-bottom:.25em}.ngx-editor-content blockquote{margin:1em 0;padding:.5em 1em;border-left:4px solid var(--ngx-editor-border-focus);background-color:#f8fafc;color:var(--ngx-editor-muted);font-style:italic}.ngx-editor-content pre{background-color:#1e293b;color:#f8fafc;padding:12px;border-radius:6px;font-family:Consolas,Monaco,Courier New,monospace;font-size:13px;overflow-x:auto;margin:1em 0}.ngx-editor-content code{background-color:#f1f5f9;color:#e11d48;padding:2px 5px;border-radius:4px;font-family:Consolas,Monaco,Courier New,monospace;font-size:.9em}.ngx-editor-content a{color:#2563eb;text-decoration:underline;text-underline-offset:2px;cursor:pointer}.ngx-editor-content .ngx-editor-hashtag,.ngx-editor-content .post-content-hashtag{color:var(--ngx-editor-hashtag-color, #0069FF)!important;font-weight:500;display:inline;cursor:text}.ngx-editor-content hr{border:none;border-top:1px solid var(--ngx-editor-border);margin:1.5em 0}.ngx-editor-content img{max-width:100%;height:auto;border-radius:4px}.ngx-editor-content[data-empty=true]:before{content:attr(data-placeholder);position:absolute;color:#94a3b8;pointer-events:none;-webkit-user-select:none;user-select:none}.ngx-editor-content:has(ol,ul,li,table,img,hr,blockquote,pre):before{display:none!important;content:none!important}.ngx-editor-source{width:100%;flex:1;min-height:200px;padding:16px;border:none;outline:none;font-family:Consolas,Monaco,Courier New,monospace;font-size:13.5px;line-height:1.5;background-color:#0f172a;color:#38bdf8;resize:vertical;box-sizing:border-box}.ngx-editor-statusbar{display:flex;align-items:center;justify-content:space-between;padding:6px 12px;background-color:var(--ngx-editor-toolbar-bg);border-top:1px solid var(--ngx-editor-border);font-size:12px;color:var(--ngx-editor-muted)}.ngx-editor-statusbar .statusbar-left,.ngx-editor-statusbar .statusbar-right{display:flex;align-items:center;gap:12px}.ngx-editor-statusbar .status-tag{background-color:#e2e8f0;padding:2px 6px;border-radius:4px;font-weight:600;text-transform:uppercase;font-size:10px}.ngx-editor-statusbar .status-badge{background-color:#fee2e2;color:#ef4444;padding:2px 6px;border-radius:4px;font-weight:500;font-size:11px}.ngx-editor-modal-backdrop{position:absolute;inset:0;background:#0f172a66;-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px);display:flex;align-items:center;justify-content:center;z-index:100;animation:fadeIn .15s ease-out}.ngx-editor-modal{background:#fff;border-radius:8px;box-shadow:0 20px 25px -5px #0000001a,0 8px 10px -6px #0000001a;width:90%;max-width:400px;padding:20px;box-sizing:border-box}.ngx-editor-modal .modal-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:16px}.ngx-editor-modal .modal-header h3{margin:0;font-size:16px;font-weight:600;color:var(--ngx-editor-text)}.ngx-editor-modal .modal-body{display:flex;flex-direction:column;gap:12px}.ngx-editor-modal .form-group{display:flex;flex-direction:column;gap:4px}.ngx-editor-modal .form-group label{font-size:13px;font-weight:500;color:var(--ngx-editor-text)}.ngx-editor-modal .form-group input[type=text],.ngx-editor-modal .form-group input[type=url]{padding:8px 12px;border:1px solid var(--ngx-editor-border);border-radius:6px;font-size:14px;outline:none;transition:border-color .15s ease}.ngx-editor-modal .form-group input[type=text]:focus,.ngx-editor-modal .form-group input[type=url]:focus{border-color:var(--ngx-editor-border-focus)}.ngx-editor-modal .form-group .checkbox-label{display:flex;align-items:center;gap:8px;cursor:pointer;font-size:13px;color:var(--ngx-editor-text);-webkit-user-select:none;user-select:none}.ngx-editor-modal .modal-footer{display:flex;justify-content:flex-end;gap:8px;margin-top:20px}.ngx-editor-modal .modal-footer button{padding:7px 14px;border-radius:6px;font-size:13px;font-weight:500;cursor:pointer;transition:all .15s ease;outline:none}.ngx-editor-modal .modal-footer .btn-secondary{background:#f1f5f9;border:1px solid var(--ngx-editor-border);color:var(--ngx-editor-text)}.ngx-editor-modal .modal-footer .btn-secondary:hover{background:#e2e8f0}.ngx-editor-modal .modal-footer .btn-primary{background:#2563eb;border:1px solid #2563eb;color:#fff}.ngx-editor-modal .modal-footer .btn-primary:hover{background:#1d4ed8}.ngx-editor-modal .modal-footer .btn-primary:disabled{opacity:.5;cursor:not-allowed}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i3.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i3.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i4.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i4.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i4.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i4.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i4.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i4.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: NgxEditorIconComponent, selector: "ngx-editor-icon", inputs: ["name"] }, { kind: "component", type: NgxEditorToolbarComponent, selector: "ngx-editor-toolbar", inputs: ["editor", "tools", "fontFamilies"], outputs: ["actionTriggered"] }] });
|
|
3781
|
+
], viewQueries: [{ propertyName: "editorContentRef", first: true, predicate: ["editorContent"], descendants: true }, { propertyName: "sourceTextareaRef", first: true, predicate: ["sourceTextarea"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div\n class=\"ngx-editor-container\"\n [ngClass]=\"customClass\"\n [class.is-focused]=\"isFocused\"\n [class.is-disabled]=\"disabled\"\n [style.min-height]=\"computedConfig.minHeight\"\n [style.max-height]=\"computedConfig.maxHeight\"\n [style.font-family]=\"fontFamily || computedConfig.fontFamily || null\"\n [style.font-size]=\"fontSize || computedConfig.fontSize || null\"\n [ngStyle]=\"parsedEditorStyle\"\n>\n <!-- Toolbar -->\n @if (computedConfig.showToolbar && !readonly) {\n <!-- Array-configured Toolbar (if tools input is provided) -->\n @if (computedConfig.tools && computedConfig.tools.length > 0) {\n <ngx-editor-toolbar\n [editor]=\"this\"\n [tools]=\"computedConfig.tools\"\n [fontFamilies]=\"resolvedFontFamilies\"\n ></ngx-editor-toolbar>\n } @else {\n <!-- Default Full Toolbar -->\n <div class=\"ngx-editor-toolbar\" role=\"toolbar\" aria-label=\"Editor Toolbar\">\n <!-- History -->\n @if (computedConfig.toolbar?.history) {\n <div class=\"ngx-editor-toolbar-group\">\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n title=\"Undo (Ctrl+Z)\"\n aria-label=\"Undo\"\n (mousedown)=\"$event.preventDefault(); undo()\"\n >\n <ngx-editor-icon name=\"undo\"></ngx-editor-icon>\n </button>\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n title=\"Redo (Ctrl+Y)\"\n aria-label=\"Redo\"\n (mousedown)=\"$event.preventDefault(); redo()\"\n >\n <ngx-editor-icon name=\"redo\"></ngx-editor-icon>\n </button>\n </div>\n }\n\n <!-- Format Block -->\n @if (computedConfig.toolbar?.format) {\n <div class=\"ngx-editor-toolbar-group\">\n <select\n class=\"ngx-editor-select\"\n title=\"Paragraph Format\"\n aria-label=\"Paragraph Format\"\n [value]=\"activeBlockTag\"\n (change)=\"onFormatBlockChange($event)\"\n >\n <option value=\"p\">Normal text</option>\n <option value=\"h1\">Heading 1</option>\n <option value=\"h2\">Heading 2</option>\n <option value=\"h3\">Heading 3</option>\n <option value=\"h4\">Heading 4</option>\n <option value=\"blockquote\">Quote</option>\n <option value=\"pre\">Code Block</option>\n </select>\n </div>\n }\n\n <!-- Font Family -->\n @if (computedConfig.toolbar?.fontName) {\n <div class=\"ngx-editor-toolbar-group\">\n <select\n class=\"ngx-editor-select\"\n title=\"Font Family\"\n aria-label=\"Font Family\"\n (change)=\"onFontNameChange($event)\"\n >\n <option value=\"\">Font</option>\n @for (font of resolvedFontFamilies; track font.value) {\n <option [value]=\"font.value\">{{ font.label }}</option>\n }\n </select>\n </div>\n }\n\n <!-- Font Size -->\n @if (computedConfig.toolbar?.fontSize) {\n <div class=\"ngx-editor-toolbar-group\">\n <select\n class=\"ngx-editor-select\"\n title=\"Font Size\"\n aria-label=\"Font Size\"\n (change)=\"onFontSizeChange($event)\"\n >\n <option value=\"\">Size</option>\n <option value=\"1\">Small (10pt)</option>\n <option value=\"3\">Normal (12pt)</option>\n <option value=\"5\">Large (18pt)</option>\n <option value=\"7\">Huge (36pt)</option>\n </select>\n </div>\n }\n\n <!-- Inline Styles: Bold, Italic, Underline, Strike, Sub, Sup, Code -->\n @if (computedConfig.toolbar?.inline) {\n <div class=\"ngx-editor-toolbar-group\">\n @if (computedConfig.toolbar?.inline?.bold !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n [class.is-active]=\"activeStates.bold\"\n title=\"Bold (Ctrl+B)\"\n aria-label=\"Bold\"\n (mousedown)=\"$event.preventDefault(); bold()\"\n >\n <ngx-editor-icon name=\"bold\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.inline?.italic !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n [class.is-active]=\"activeStates.italic\"\n title=\"Italic (Ctrl+I)\"\n aria-label=\"Italic\"\n (mousedown)=\"$event.preventDefault(); italic()\"\n >\n <ngx-editor-icon name=\"italic\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.inline?.underline !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n [class.is-active]=\"activeStates.underline\"\n title=\"Underline (Ctrl+U)\"\n aria-label=\"Underline\"\n (mousedown)=\"$event.preventDefault(); underline()\"\n >\n <ngx-editor-icon name=\"underline\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.inline?.strikeThrough !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n [class.is-active]=\"activeStates.strikeThrough\"\n title=\"Strikethrough\"\n aria-label=\"Strikethrough\"\n (mousedown)=\"$event.preventDefault(); strike()\"\n >\n <ngx-editor-icon name=\"strike\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.inline?.subscript !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n [class.is-active]=\"activeStates.subscript\"\n title=\"Subscript\"\n aria-label=\"Subscript\"\n (mousedown)=\"$event.preventDefault(); subscript()\"\n >\n <ngx-editor-icon name=\"subscript\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.inline?.superscript !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n [class.is-active]=\"activeStates.superscript\"\n title=\"Superscript\"\n aria-label=\"Superscript\"\n (mousedown)=\"$event.preventDefault(); superscript()\"\n >\n <ngx-editor-icon name=\"superscript\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.inline?.code !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n title=\"Inline Code\"\n aria-label=\"Inline Code\"\n (mousedown)=\"$event.preventDefault(); wrapSelectionWithCode()\"\n >\n <ngx-editor-icon name=\"code\"></ngx-editor-icon>\n </button>\n }\n </div>\n }\n\n <!-- Colors -->\n @if (computedConfig.toolbar?.color) {\n <div class=\"ngx-editor-toolbar-group\" style=\"position: relative;\">\n @if (computedConfig.toolbar?.color?.textColor !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn ngx-editor-color-btn\"\n title=\"Text Color\"\n aria-label=\"Text Color\"\n (click)=\"toggleColorPicker('text')\"\n >\n <ngx-editor-icon name=\"textColor\"></ngx-editor-icon>\n </button>\n <div\n class=\"color-bar\"\n [style.background-color]=\"selectedTextColor\"\n ></div>\n }\n\n @if (computedConfig.toolbar?.color?.backgroundColor !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn ngx-editor-color-btn\"\n title=\"Highlight Color\"\n aria-label=\"Highlight Color\"\n (click)=\"toggleColorPicker('background')\"\n >\n <ngx-editor-icon name=\"bgColor\"></ngx-editor-icon>\n </button>\n <div\n class=\"color-bar\"\n [style.background-color]=\"selectedBgColor\"\n ></div>\n }\n\n <!-- Color Popover -->\n @if (activeColorPicker) {\n <div class=\"ngx-color-popover\" (mousedown)=\"$event.stopPropagation()\">\n <div class=\"color-grid\">\n @for (color of computedConfig.colorPalette; track color) {\n <button\n type=\"button\"\n class=\"color-swatch\"\n [style.background-color]=\"color\"\n (click)=\"applyColor(color)\"\n ></button>\n }\n </div>\n <div class=\"custom-color-picker\">\n <input\n type=\"color\"\n (change)=\"onCustomColorChange($event)\"\n />\n <span>Custom color</span>\n </div>\n </div>\n }\n </div>\n }\n\n <!-- Alignment -->\n @if (computedConfig.toolbar?.alignment) {\n <div class=\"ngx-editor-toolbar-group\">\n @if (computedConfig.toolbar?.alignment?.left !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n [class.is-active]=\"activeStates.justifyLeft\"\n title=\"Align Left\"\n aria-label=\"Align Left\"\n (mousedown)=\"$event.preventDefault(); alignLeft()\"\n >\n <ngx-editor-icon name=\"alignLeft\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.alignment?.center !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n [class.is-active]=\"activeStates.justifyCenter\"\n title=\"Align Center\"\n aria-label=\"Align Center\"\n (mousedown)=\"$event.preventDefault(); alignCenter()\"\n >\n <ngx-editor-icon name=\"alignCenter\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.alignment?.right !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n [class.is-active]=\"activeStates.justifyRight\"\n title=\"Align Right\"\n aria-label=\"Align Right\"\n (mousedown)=\"$event.preventDefault(); alignRight()\"\n >\n <ngx-editor-icon name=\"alignRight\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.alignment?.justify !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n [class.is-active]=\"activeStates.justifyFull\"\n title=\"Justify\"\n aria-label=\"Justify\"\n (mousedown)=\"$event.preventDefault(); alignJustify()\"\n >\n <ngx-editor-icon name=\"alignJustify\"></ngx-editor-icon>\n </button>\n }\n </div>\n }\n\n <!-- Lists & Indentation -->\n @if (computedConfig.toolbar?.list) {\n <div class=\"ngx-editor-toolbar-group\">\n @if (computedConfig.toolbar?.list?.unordered !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n [class.is-active]=\"activeStates.insertUnorderedList\"\n title=\"Bullet List\"\n aria-label=\"Bullet List\"\n (mousedown)=\"$event.preventDefault(); insertUnorderedList()\"\n >\n <ngx-editor-icon name=\"unorderedList\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.list?.ordered !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n [class.is-active]=\"activeStates.insertOrderedList\"\n title=\"Numbered List\"\n aria-label=\"Numbered List\"\n (mousedown)=\"$event.preventDefault(); insertOrderedList()\"\n >\n <ngx-editor-icon name=\"orderedList\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.list?.indent !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n title=\"Increase Indent\"\n aria-label=\"Increase Indent\"\n (mousedown)=\"$event.preventDefault(); indent()\"\n >\n <ngx-editor-icon name=\"indent\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.list?.outdent !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n title=\"Decrease Indent\"\n aria-label=\"Decrease Indent\"\n (mousedown)=\"$event.preventDefault(); outdent()\"\n >\n <ngx-editor-icon name=\"outdent\"></ngx-editor-icon>\n </button>\n }\n </div>\n }\n\n <!-- Insert Options -->\n @if (computedConfig.toolbar?.insert) {\n <div class=\"ngx-editor-toolbar-group\">\n @if (computedConfig.toolbar?.insert?.link !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n [class.is-active]=\"activeStates.link\"\n title=\"Insert Link (Ctrl+K)\"\n aria-label=\"Insert Link\"\n (click)=\"openLinkModal()\"\n >\n <ngx-editor-icon name=\"link\"></ngx-editor-icon>\n </button>\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n title=\"Remove Link\"\n aria-label=\"Remove Link\"\n (mousedown)=\"$event.preventDefault(); unlink()\"\n >\n <ngx-editor-icon name=\"unlink\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.insert?.image !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n title=\"Insert Image\"\n aria-label=\"Insert Image\"\n (click)=\"openImageModal()\"\n >\n <ngx-editor-icon name=\"image\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.insert?.horizontalRule !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n title=\"Horizontal Line\"\n aria-label=\"Horizontal Line\"\n (mousedown)=\"$event.preventDefault(); insertHorizontalRule()\"\n >\n <ngx-editor-icon name=\"horizontalRule\"></ngx-editor-icon>\n </button>\n }\n </div>\n }\n\n <!-- Extra Actions -->\n @if (computedConfig.toolbar?.extra) {\n <div class=\"ngx-editor-toolbar-group\">\n @if (computedConfig.toolbar?.extra?.clearFormat !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n title=\"Clear Formatting\"\n aria-label=\"Clear Formatting\"\n (mousedown)=\"$event.preventDefault(); clearFormatting()\"\n >\n <ngx-editor-icon name=\"clearFormat\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.extra?.sourceView !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n [class.is-active]=\"isSourceView\"\n title=\"View HTML Source\"\n aria-label=\"View HTML Source\"\n (click)=\"toggleSourceView()\"\n >\n <ngx-editor-icon name=\"sourceView\"></ngx-editor-icon>\n </button>\n }\n </div>\n }\n </div>\n }\n }\n\n <!-- Editor Content Area -->\n <div class=\"ngx-editor-content-wrapper\">\n <!-- Visual contenteditable -->\n <div\n #editorContent\n class=\"ngx-editor-content\"\n [style.display]=\"isSourceView ? 'none' : 'block'\"\n [style.height]=\"computedConfig.height\"\n [style.font-family]=\"fontFamily || computedConfig.fontFamily || null\"\n [style.font-size]=\"fontSize || computedConfig.fontSize || null\"\n [ngStyle]=\"parsedContentStyle\"\n [attr.contenteditable]=\"!disabled && !readonly\"\n [attr.spellcheck]=\"computedConfig.spellcheck\"\n [attr.data-placeholder]=\"computedConfig.placeholder\"\n [attr.data-empty]=\"isEmpty\"\n (input)=\"onEditorInput()\"\n (focus)=\"onEditorFocus($event)\"\n (blur)=\"onEditorBlur($event)\"\n (keydown)=\"onEditorKeydown($event)\"\n (keyup)=\"updateEditorState()\"\n (mouseup)=\"updateEditorState()\"\n (paste)=\"onEditorPaste($event)\"\n ></div>\n\n <!-- Raw HTML Source Code View -->\n @if (isSourceView) {\n <textarea\n #sourceTextarea\n class=\"ngx-editor-source\"\n [style.height]=\"computedConfig.height\"\n [disabled]=\"disabled || readonly\"\n [value]=\"content\"\n (input)=\"onSourceInput($event)\"\n ></textarea>\n }\n </div>\n\n <!-- Status Bar -->\n @if (computedConfig.showStatusBar) {\n <div class=\"ngx-editor-statusbar\">\n <div class=\"statusbar-left\">\n @if (activeBlockTag) {\n <span class=\"status-tag\"><{{ activeBlockTag }}></span>\n }\n @if (isSourceView) {\n <span class=\"status-badge\">HTML SOURCE MODE</span>\n }\n </div>\n <div class=\"statusbar-right\">\n @if (computedConfig.showWordCount) {\n <span>Words: <strong>{{ stats.words }}</strong></span>\n }\n @if (computedConfig.maxCharacters) {\n <span [class.limit-reached]=\"stats.chars > computedConfig.maxCharacters\">\n {{ stats.chars }}/{{ computedConfig.maxCharacters }} Characters\n </span>\n } @else if (computedConfig.showCharCount) {\n <span>Characters: <strong>{{ stats.chars }}</strong></span>\n }\n </div>\n </div>\n }\n\n <!-- Link Modal -->\n @if (showLinkModal) {\n <div class=\"ngx-editor-modal-backdrop\" (click)=\"closeLinkModal()\">\n <div class=\"ngx-editor-modal\" (click)=\"$event.stopPropagation()\">\n <div class=\"modal-header\">\n <h3>Insert Link</h3>\n <button type=\"button\" class=\"ngx-editor-btn\" (click)=\"closeLinkModal()\" aria-label=\"Close\">\n <ngx-editor-icon name=\"close\"></ngx-editor-icon>\n </button>\n </div>\n <div class=\"modal-body\">\n <div class=\"form-group\">\n <label>Link Text</label>\n <input\n type=\"text\"\n placeholder=\"Display text\"\n [(ngModel)]=\"linkModel.text\"\n />\n </div>\n <div class=\"form-group\">\n <label>Target URL</label>\n <input\n type=\"url\"\n placeholder=\"https://example.com\"\n [(ngModel)]=\"linkModel.url\"\n (keydown.enter)=\"insertLinkFromModal()\"\n />\n </div>\n <div class=\"form-group\">\n <label class=\"checkbox-label\">\n <input type=\"checkbox\" [(ngModel)]=\"linkModel.newTab\" />\n Open in a new tab\n </label>\n </div>\n </div>\n <div class=\"modal-footer\">\n <button type=\"button\" class=\"btn-secondary\" (click)=\"closeLinkModal()\">Cancel</button>\n <button\n type=\"button\"\n class=\"btn-primary\"\n [disabled]=\"!linkModel.url\"\n (click)=\"insertLinkFromModal()\"\n >\n Insert Link\n </button>\n </div>\n </div>\n </div>\n }\n\n <!-- Image Modal -->\n @if (showImageModal) {\n <div class=\"ngx-editor-modal-backdrop\" (click)=\"closeImageModal()\">\n <div class=\"ngx-editor-modal\" (click)=\"$event.stopPropagation()\">\n <div class=\"modal-header\">\n <h3>Insert Image</h3>\n <button type=\"button\" class=\"ngx-editor-btn\" (click)=\"closeImageModal()\" aria-label=\"Close\">\n <ngx-editor-icon name=\"close\"></ngx-editor-icon>\n </button>\n </div>\n <div class=\"modal-body\">\n <div class=\"form-group\">\n <label>Image URL</label>\n <input\n type=\"url\"\n placeholder=\"https://example.com/image.png\"\n [(ngModel)]=\"imageModel.url\"\n />\n </div>\n <div class=\"form-group\">\n <label>Or Upload File</label>\n <input\n type=\"file\"\n accept=\"image/*\"\n (change)=\"onImageFileUpload($event)\"\n />\n </div>\n <div class=\"form-group\">\n <label>Alt Description</label>\n <input\n type=\"text\"\n placeholder=\"Image description\"\n [(ngModel)]=\"imageModel.alt\"\n />\n </div>\n </div>\n <div class=\"modal-footer\">\n <button type=\"button\" class=\"btn-secondary\" (click)=\"closeImageModal()\">Cancel</button>\n <button\n type=\"button\"\n class=\"btn-primary\"\n [disabled]=\"!imageModel.url\"\n (click)=\"insertImageFromModal()\"\n >\n Insert Image\n </button>\n </div>\n </div>\n </div>\n }\n</div>\n", styles: [":host{display:block;font-family:var(--ngx-editor-font-family, inherit);box-sizing:border-box;--ngx-editor-font-family: inherit;--ngx-editor-font-size: 15px;--ngx-editor-line-height: 1.65;--ngx-editor-border: #e2e8f0;--ngx-editor-border-focus: #3b82f6;--ngx-editor-bg: #ffffff;--ngx-editor-toolbar-bg: #f8fafc;--ngx-editor-toolbar-btn-hover: #e2e8f0;--ngx-editor-toolbar-btn-active: #dbeafe;--ngx-editor-toolbar-btn-active-color: #1d4ed8;--ngx-editor-text: inherit;--ngx-editor-muted: #64748b;--ngx-editor-radius: 8px;--ngx-editor-shadow: 0 1px 3px 0 rgba(0, 0, 0, .1), 0 1px 2px -1px rgba(0, 0, 0, .1);--ngx-editor-shadow-focus: 0 0 0 3px rgba(59, 130, 246, .25);--ngx-editor-hashtag-color: #0069FF}.ngx-editor-container{display:flex;flex-direction:column;background-color:var(--ngx-editor-bg);border:1px solid var(--ngx-editor-border);border-radius:var(--ngx-editor-radius);box-shadow:var(--ngx-editor-shadow);overflow:hidden;transition:border-color .2s ease,box-shadow .2s ease;position:relative}.ngx-editor-container.is-focused{border-color:var(--ngx-editor-border-focus);box-shadow:var(--ngx-editor-shadow-focus)}.ngx-editor-container.is-disabled{background-color:#f1f5f9;opacity:.75;pointer-events:none;-webkit-user-select:none;user-select:none}.ngx-editor-toolbar{display:flex;flex-wrap:wrap;align-items:center;gap:4px;padding:6px 8px;background-color:var(--ngx-editor-toolbar-bg);border-bottom:1px solid var(--ngx-editor-border);-webkit-user-select:none;user-select:none;z-index:10}.ngx-editor-toolbar-group{display:flex;align-items:center;gap:2px}.ngx-editor-toolbar-group:not(:last-child):after{content:\"\";display:inline-block;width:1px;height:18px;background-color:var(--ngx-editor-border);margin:0 4px}.ngx-editor-btn{display:inline-flex;align-items:center;justify-content:center;width:32px;height:32px;padding:0;border:1px solid transparent;border-radius:4px;background:transparent;color:var(--ngx-editor-text);cursor:pointer;transition:background-color .15s ease,color .15s ease,border-color .15s ease;outline:none}.ngx-editor-btn:hover:not(:disabled){background-color:var(--ngx-editor-toolbar-btn-hover)}.ngx-editor-btn:active:not(:disabled),.ngx-editor-btn.is-active{background-color:var(--ngx-editor-toolbar-btn-active);color:var(--ngx-editor-toolbar-btn-active-color);border-color:#bfdbfe}.ngx-editor-btn:disabled{opacity:.35;cursor:not-allowed}.ngx-editor-btn svg{display:block;pointer-events:none}.ngx-editor-select{height:32px;padding:0 8px;font-size:13px;color:var(--ngx-editor-text);background-color:var(--ngx-editor-bg);border:1px solid var(--ngx-editor-border);border-radius:4px;outline:none;cursor:pointer;transition:border-color .15s ease}.ngx-editor-select:hover{border-color:#cbd5e1}.ngx-editor-select:focus{border-color:var(--ngx-editor-border-focus)}.ngx-editor-color-btn{position:relative}.ngx-editor-color-btn .color-bar{position:absolute;bottom:4px;left:6px;right:6px;height:3px;border-radius:1px}.ngx-color-popover{position:absolute;top:100%;left:0;margin-top:4px;background:#fff;border:1px solid var(--ngx-editor-border);border-radius:6px;box-shadow:0 10px 15px -3px #0000001a,0 4px 6px -4px #0000001a;padding:8px;z-index:50;width:176px}.ngx-color-popover .color-grid{display:grid;grid-template-columns:repeat(6,1fr);gap:4px;margin-bottom:8px}.ngx-color-popover .color-swatch{width:22px;height:22px;border-radius:3px;border:1px solid rgba(0,0,0,.15);cursor:pointer;padding:0;transition:transform .1s ease}.ngx-color-popover .color-swatch:hover{transform:scale(1.15);z-index:2}.ngx-color-popover .custom-color-picker{display:flex;align-items:center;gap:8px;font-size:12px;color:var(--ngx-editor-muted)}.ngx-color-popover .custom-color-picker input[type=color]{width:28px;height:24px;border:1px solid var(--ngx-editor-border);border-radius:3px;padding:0;cursor:pointer;background:none}.ngx-editor-content-wrapper{position:relative;flex:1;display:flex;flex-direction:column}.ngx-editor-content{flex:1;padding:16px;outline:none;font-family:var(--ngx-editor-font-family, inherit);font-size:var(--ngx-editor-font-size, 15px);line-height:var(--ngx-editor-line-height, 1.65);color:var(--ngx-editor-text, inherit);overflow-y:auto;min-height:200px;word-break:break-word}.ngx-editor-content::selection,.ngx-editor-content *::selection{color:inherit;background:#70b5f959}.ngx-editor-content p{margin:0;min-height:1.65em}.ngx-editor-content b,.ngx-editor-content strong{font-weight:700!important}.ngx-editor-content i,.ngx-editor-content em{font-style:italic!important}.ngx-editor-content u,.ngx-editor-content .publish-editor-underline{text-decoration:underline!important;text-decoration-thickness:1px;text-underline-offset:.12em}.ngx-editor-content h1{font-size:2em;font-weight:700;margin:.67em 0;line-height:1.25}.ngx-editor-content h2{font-size:1.6em;font-weight:600;margin:.75em 0;line-height:1.3}.ngx-editor-content h3{font-size:1.3em;font-weight:600;margin:.83em 0}.ngx-editor-content h4{font-size:1.1em;font-weight:600;margin:1em 0}.ngx-editor-content ul,.ngx-editor-content ol{margin:0 0 1em 1.5em;padding:0}.ngx-editor-content li{margin-bottom:.25em}.ngx-editor-content blockquote{margin:1em 0;padding:.5em 1em;border-left:4px solid var(--ngx-editor-border-focus);background-color:#f8fafc;color:var(--ngx-editor-muted);font-style:italic}.ngx-editor-content pre{background-color:#1e293b;color:#f8fafc;padding:12px;border-radius:6px;font-family:Consolas,Monaco,Courier New,monospace;font-size:13px;overflow-x:auto;margin:1em 0}.ngx-editor-content code{background-color:#f1f5f9;color:#e11d48;padding:2px 5px;border-radius:4px;font-family:Consolas,Monaco,Courier New,monospace;font-size:.9em}.ngx-editor-content a{color:#2563eb;text-decoration:underline;text-underline-offset:2px;cursor:pointer}.ngx-editor-content .ngx-editor-hashtag,.ngx-editor-content .post-content-hashtag{color:var(--ngx-editor-hashtag-color, #0069FF)!important;font-weight:500;display:inline;cursor:text}.ngx-editor-content hr{border:none;border-top:1px solid var(--ngx-editor-border);margin:1.5em 0}.ngx-editor-content img{max-width:100%;height:auto;border-radius:4px}.ngx-editor-content[data-empty=true]:before{content:attr(data-placeholder);position:absolute;color:#94a3b8;pointer-events:none;-webkit-user-select:none;user-select:none}.ngx-editor-content:has(ol,ul,li,table,img,hr,blockquote,pre):before{display:none!important;content:none!important}.ngx-editor-source{width:100%;flex:1;min-height:200px;padding:16px;border:none;outline:none;font-family:Consolas,Monaco,Courier New,monospace;font-size:13.5px;line-height:1.5;background-color:#0f172a;color:#38bdf8;resize:vertical;box-sizing:border-box}.ngx-editor-statusbar{display:flex;align-items:center;justify-content:space-between;padding:6px 12px;background-color:var(--ngx-editor-toolbar-bg);border-top:1px solid var(--ngx-editor-border);font-size:12px;color:var(--ngx-editor-muted)}.ngx-editor-statusbar .statusbar-left,.ngx-editor-statusbar .statusbar-right{display:flex;align-items:center;gap:12px}.ngx-editor-statusbar .status-tag{background-color:#e2e8f0;padding:2px 6px;border-radius:4px;font-weight:600;text-transform:uppercase;font-size:10px}.ngx-editor-statusbar .status-badge{background-color:#fee2e2;color:#ef4444;padding:2px 6px;border-radius:4px;font-weight:500;font-size:11px}.ngx-editor-modal-backdrop{position:absolute;inset:0;background:#0f172a66;-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px);display:flex;align-items:center;justify-content:center;z-index:100;animation:fadeIn .15s ease-out}.ngx-editor-modal{background:#fff;border-radius:8px;box-shadow:0 20px 25px -5px #0000001a,0 8px 10px -6px #0000001a;width:90%;max-width:400px;padding:20px;box-sizing:border-box}.ngx-editor-modal .modal-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:16px}.ngx-editor-modal .modal-header h3{margin:0;font-size:16px;font-weight:600;color:var(--ngx-editor-text)}.ngx-editor-modal .modal-body{display:flex;flex-direction:column;gap:12px}.ngx-editor-modal .form-group{display:flex;flex-direction:column;gap:4px}.ngx-editor-modal .form-group label{font-size:13px;font-weight:500;color:var(--ngx-editor-text)}.ngx-editor-modal .form-group input[type=text],.ngx-editor-modal .form-group input[type=url]{padding:8px 12px;border:1px solid var(--ngx-editor-border);border-radius:6px;font-size:14px;outline:none;transition:border-color .15s ease}.ngx-editor-modal .form-group input[type=text]:focus,.ngx-editor-modal .form-group input[type=url]:focus{border-color:var(--ngx-editor-border-focus)}.ngx-editor-modal .form-group .checkbox-label{display:flex;align-items:center;gap:8px;cursor:pointer;font-size:13px;color:var(--ngx-editor-text);-webkit-user-select:none;user-select:none}.ngx-editor-modal .modal-footer{display:flex;justify-content:flex-end;gap:8px;margin-top:20px}.ngx-editor-modal .modal-footer button{padding:7px 14px;border-radius:6px;font-size:13px;font-weight:500;cursor:pointer;transition:all .15s ease;outline:none}.ngx-editor-modal .modal-footer .btn-secondary{background:#f1f5f9;border:1px solid var(--ngx-editor-border);color:var(--ngx-editor-text)}.ngx-editor-modal .modal-footer .btn-secondary:hover{background:#e2e8f0}.ngx-editor-modal .modal-footer .btn-primary{background:#2563eb;border:1px solid #2563eb;color:#fff}.ngx-editor-modal .modal-footer .btn-primary:hover{background:#1d4ed8}.ngx-editor-modal .modal-footer .btn-primary:disabled{opacity:.5;cursor:not-allowed}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i3.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i3.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i4.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i4.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i4.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i4.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i4.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i4.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: NgxEditorIconComponent, selector: "ngx-editor-icon", inputs: ["name"] }, { kind: "component", type: NgxEditorToolbarComponent, selector: "ngx-editor-toolbar", inputs: ["editor", "tools", "fontFamilies"], outputs: ["actionTriggered"] }] });
|
|
3637
3782
|
}
|
|
3638
3783
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: NgxRichTextEditorComponent, decorators: [{
|
|
3639
3784
|
type: Component,
|
|
@@ -3643,7 +3788,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
|
|
|
3643
3788
|
useExisting: forwardRef(() => NgxRichTextEditorComponent),
|
|
3644
3789
|
multi: true,
|
|
3645
3790
|
},
|
|
3646
|
-
], template: "<div\n class=\"ngx-editor-container\"\n [ngClass]=\"customClass\"\n [class.is-focused]=\"isFocused\"\n [class.is-disabled]=\"disabled\"\n [style.min-height]=\"computedConfig.minHeight\"\n [style.max-height]=\"computedConfig.maxHeight\"\n [style.font-family]=\"fontFamily || computedConfig.fontFamily || null\"\n [style.font-size]=\"fontSize || computedConfig.fontSize || null\"\n [ngStyle]=\"parsedEditorStyle\"\n>\n <!-- Toolbar -->\n @if (computedConfig.showToolbar && !readonly) {\n <!-- Array-configured Toolbar (if tools input is provided) -->\n @if (computedConfig.tools && computedConfig.tools.length > 0) {\n <ngx-editor-toolbar\n [editor]=\"this\"\n [tools]=\"computedConfig.tools\"\n [fontFamilies]=\"resolvedFontFamilies\"\n ></ngx-editor-toolbar>\n } @else {\n <!-- Default Full Toolbar -->\n <div class=\"ngx-editor-toolbar\" role=\"toolbar\" aria-label=\"Editor Toolbar\">\n <!-- History -->\n @if (computedConfig.toolbar?.history) {\n <div class=\"ngx-editor-toolbar-group\">\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n title=\"Undo (Ctrl+Z)\"\n aria-label=\"Undo\"\n (mousedown)=\"$event.preventDefault(); undo()\"\n >\n <ngx-editor-icon name=\"undo\"></ngx-editor-icon>\n </button>\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n title=\"Redo (Ctrl+Y)\"\n aria-label=\"Redo\"\n (mousedown)=\"$event.preventDefault(); redo()\"\n >\n <ngx-editor-icon name=\"redo\"></ngx-editor-icon>\n </button>\n </div>\n }\n\n <!-- Format Block -->\n @if (computedConfig.toolbar?.format) {\n <div class=\"ngx-editor-toolbar-group\">\n <select\n class=\"ngx-editor-select\"\n title=\"Paragraph Format\"\n aria-label=\"Paragraph Format\"\n [value]=\"activeBlockTag\"\n (change)=\"onFormatBlockChange($event)\"\n >\n <option value=\"p\">Normal text</option>\n <option value=\"h1\">Heading 1</option>\n <option value=\"h2\">Heading 2</option>\n <option value=\"h3\">Heading 3</option>\n <option value=\"h4\">Heading 4</option>\n <option value=\"blockquote\">Quote</option>\n <option value=\"pre\">Code Block</option>\n </select>\n </div>\n }\n\n <!-- Font Family -->\n @if (computedConfig.toolbar?.fontName) {\n <div class=\"ngx-editor-toolbar-group\">\n <select\n class=\"ngx-editor-select\"\n title=\"Font Family\"\n aria-label=\"Font Family\"\n (change)=\"onFontNameChange($event)\"\n >\n <option value=\"\">Font</option>\n @for (font of resolvedFontFamilies; track font.value) {\n <option [value]=\"font.value\">{{ font.label }}</option>\n }\n </select>\n </div>\n }\n\n <!-- Font Size -->\n @if (computedConfig.toolbar?.fontSize) {\n <div class=\"ngx-editor-toolbar-group\">\n <select\n class=\"ngx-editor-select\"\n title=\"Font Size\"\n aria-label=\"Font Size\"\n (change)=\"onFontSizeChange($event)\"\n >\n <option value=\"\">Size</option>\n <option value=\"1\">Small (10pt)</option>\n <option value=\"3\">Normal (12pt)</option>\n <option value=\"5\">Large (18pt)</option>\n <option value=\"7\">Huge (36pt)</option>\n </select>\n </div>\n }\n\n <!-- Inline Styles: Bold, Italic, Underline, Strike, Sub, Sup, Code -->\n @if (computedConfig.toolbar?.inline) {\n <div class=\"ngx-editor-toolbar-group\">\n @if (computedConfig.toolbar?.inline?.bold !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n [class.is-active]=\"activeStates.bold\"\n title=\"Bold (Ctrl+B)\"\n aria-label=\"Bold\"\n (mousedown)=\"$event.preventDefault(); bold()\"\n >\n <ngx-editor-icon name=\"bold\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.inline?.italic !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n [class.is-active]=\"activeStates.italic\"\n title=\"Italic (Ctrl+I)\"\n aria-label=\"Italic\"\n (mousedown)=\"$event.preventDefault(); italic()\"\n >\n <ngx-editor-icon name=\"italic\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.inline?.underline !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n [class.is-active]=\"activeStates.underline\"\n title=\"Underline (Ctrl+U)\"\n aria-label=\"Underline\"\n (mousedown)=\"$event.preventDefault(); underline()\"\n >\n <ngx-editor-icon name=\"underline\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.inline?.strikeThrough !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n [class.is-active]=\"activeStates.strikeThrough\"\n title=\"Strikethrough\"\n aria-label=\"Strikethrough\"\n (mousedown)=\"$event.preventDefault(); strike()\"\n >\n <ngx-editor-icon name=\"strike\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.inline?.subscript !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n [class.is-active]=\"activeStates.subscript\"\n title=\"Subscript\"\n aria-label=\"Subscript\"\n (mousedown)=\"$event.preventDefault(); subscript()\"\n >\n <ngx-editor-icon name=\"subscript\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.inline?.superscript !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n [class.is-active]=\"activeStates.superscript\"\n title=\"Superscript\"\n aria-label=\"Superscript\"\n (mousedown)=\"$event.preventDefault(); superscript()\"\n >\n <ngx-editor-icon name=\"superscript\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.inline?.code !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n title=\"Inline Code\"\n aria-label=\"Inline Code\"\n (mousedown)=\"$event.preventDefault(); wrapSelectionWithCode()\"\n >\n <ngx-editor-icon name=\"code\"></ngx-editor-icon>\n </button>\n }\n </div>\n }\n\n <!-- Colors -->\n @if (computedConfig.toolbar?.color) {\n <div class=\"ngx-editor-toolbar-group\" style=\"position: relative;\">\n @if (computedConfig.toolbar?.color?.textColor !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn ngx-editor-color-btn\"\n title=\"Text Color\"\n aria-label=\"Text Color\"\n (click)=\"toggleColorPicker('text')\"\n >\n <ngx-editor-icon name=\"textColor\"></ngx-editor-icon>\n </button>\n <div\n class=\"color-bar\"\n [style.background-color]=\"selectedTextColor\"\n ></div>\n }\n\n @if (computedConfig.toolbar?.color?.backgroundColor !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn ngx-editor-color-btn\"\n title=\"Highlight Color\"\n aria-label=\"Highlight Color\"\n (click)=\"toggleColorPicker('background')\"\n >\n <ngx-editor-icon name=\"bgColor\"></ngx-editor-icon>\n </button>\n <div\n class=\"color-bar\"\n [style.background-color]=\"selectedBgColor\"\n ></div>\n }\n\n <!-- Color Popover -->\n @if (activeColorPicker) {\n <div class=\"ngx-color-popover\" (mousedown)=\"$event.stopPropagation()\">\n <div class=\"color-grid\">\n @for (color of computedConfig.colorPalette; track color) {\n <button\n type=\"button\"\n class=\"color-swatch\"\n [style.background-color]=\"color\"\n (click)=\"applyColor(color)\"\n ></button>\n }\n </div>\n <div class=\"custom-color-picker\">\n <input\n type=\"color\"\n (change)=\"onCustomColorChange($event)\"\n />\n <span>Custom color</span>\n </div>\n </div>\n }\n </div>\n }\n\n <!-- Alignment -->\n @if (computedConfig.toolbar?.alignment) {\n <div class=\"ngx-editor-toolbar-group\">\n @if (computedConfig.toolbar?.alignment?.left !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n [class.is-active]=\"activeStates.justifyLeft\"\n title=\"Align Left\"\n aria-label=\"Align Left\"\n (mousedown)=\"$event.preventDefault(); alignLeft()\"\n >\n <ngx-editor-icon name=\"alignLeft\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.alignment?.center !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n [class.is-active]=\"activeStates.justifyCenter\"\n title=\"Align Center\"\n aria-label=\"Align Center\"\n (mousedown)=\"$event.preventDefault(); alignCenter()\"\n >\n <ngx-editor-icon name=\"alignCenter\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.alignment?.right !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n [class.is-active]=\"activeStates.justifyRight\"\n title=\"Align Right\"\n aria-label=\"Align Right\"\n (mousedown)=\"$event.preventDefault(); alignRight()\"\n >\n <ngx-editor-icon name=\"alignRight\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.alignment?.justify !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n [class.is-active]=\"activeStates.justifyFull\"\n title=\"Justify\"\n aria-label=\"Justify\"\n (mousedown)=\"$event.preventDefault(); alignJustify()\"\n >\n <ngx-editor-icon name=\"alignJustify\"></ngx-editor-icon>\n </button>\n }\n </div>\n }\n\n <!-- Lists & Indentation -->\n @if (computedConfig.toolbar?.list) {\n <div class=\"ngx-editor-toolbar-group\">\n @if (computedConfig.toolbar?.list?.unordered !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n [class.is-active]=\"activeStates.insertUnorderedList\"\n title=\"Bullet List\"\n aria-label=\"Bullet List\"\n (mousedown)=\"$event.preventDefault(); insertUnorderedList()\"\n >\n <ngx-editor-icon name=\"unorderedList\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.list?.ordered !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n [class.is-active]=\"activeStates.insertOrderedList\"\n title=\"Numbered List\"\n aria-label=\"Numbered List\"\n (mousedown)=\"$event.preventDefault(); insertOrderedList()\"\n >\n <ngx-editor-icon name=\"orderedList\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.list?.indent !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n title=\"Increase Indent\"\n aria-label=\"Increase Indent\"\n (mousedown)=\"$event.preventDefault(); indent()\"\n >\n <ngx-editor-icon name=\"indent\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.list?.outdent !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n title=\"Decrease Indent\"\n aria-label=\"Decrease Indent\"\n (mousedown)=\"$event.preventDefault(); outdent()\"\n >\n <ngx-editor-icon name=\"outdent\"></ngx-editor-icon>\n </button>\n }\n </div>\n }\n\n <!-- Insert Options -->\n @if (computedConfig.toolbar?.insert) {\n <div class=\"ngx-editor-toolbar-group\">\n @if (computedConfig.toolbar?.insert?.link !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n [class.is-active]=\"activeStates.link\"\n title=\"Insert Link (Ctrl+K)\"\n aria-label=\"Insert Link\"\n (click)=\"openLinkModal()\"\n >\n <ngx-editor-icon name=\"link\"></ngx-editor-icon>\n </button>\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n title=\"Remove Link\"\n aria-label=\"Remove Link\"\n (mousedown)=\"$event.preventDefault(); unlink()\"\n >\n <ngx-editor-icon name=\"unlink\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.insert?.image !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n title=\"Insert Image\"\n aria-label=\"Insert Image\"\n (click)=\"openImageModal()\"\n >\n <ngx-editor-icon name=\"image\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.insert?.horizontalRule !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n title=\"Horizontal Line\"\n aria-label=\"Horizontal Line\"\n (mousedown)=\"$event.preventDefault(); insertHorizontalRule()\"\n >\n <ngx-editor-icon name=\"horizontalRule\"></ngx-editor-icon>\n </button>\n }\n </div>\n }\n\n <!-- Extra Actions -->\n @if (computedConfig.toolbar?.extra) {\n <div class=\"ngx-editor-toolbar-group\">\n @if (computedConfig.toolbar?.extra?.clearFormat !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n title=\"Clear Formatting\"\n aria-label=\"Clear Formatting\"\n (mousedown)=\"$event.preventDefault(); clearFormatting()\"\n >\n <ngx-editor-icon name=\"clearFormat\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.extra?.sourceView !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n [class.is-active]=\"isSourceView\"\n title=\"View HTML Source\"\n aria-label=\"View HTML Source\"\n (click)=\"toggleSourceView()\"\n >\n <ngx-editor-icon name=\"sourceView\"></ngx-editor-icon>\n </button>\n }\n </div>\n }\n </div>\n }\n }\n\n <!-- Editor Content Area -->\n <div class=\"ngx-editor-content-wrapper\">\n <!-- Visual contenteditable -->\n <div\n #editorContent\n class=\"ngx-editor-content\"\n [style.display]=\"isSourceView ? 'none' : 'block'\"\n [style.height]=\"computedConfig.height\"\n [style.font-family]=\"fontFamily || computedConfig.fontFamily || null\"\n [style.font-size]=\"fontSize || computedConfig.fontSize || null\"\n [ngStyle]=\"parsedContentStyle\"\n [attr.contenteditable]=\"!disabled && !readonly\"\n [attr.spellcheck]=\"computedConfig.spellcheck\"\n [attr.data-placeholder]=\"computedConfig.placeholder\"\n [attr.data-empty]=\"isEmpty\"\n (input)=\"onEditorInput()\"\n (focus)=\"onEditorFocus($event)\"\n (blur)=\"onEditorBlur($event)\"\n (keydown)=\"onEditorKeydown($event)\"\n (keyup)=\"updateEditorState()\"\n (mouseup)=\"updateEditorState()\"\n (paste)=\"onEditorPaste($event)\"\n ></div>\n\n <!-- Raw HTML Source Code View -->\n @if (isSourceView) {\n <textarea\n #sourceTextarea\n class=\"ngx-editor-source\"\n [style.height]=\"computedConfig.height\"\n [disabled]=\"disabled || readonly\"\n [value]=\"content\"\n (input)=\"onSourceInput($event)\"\n ></textarea>\n }\n </div>\n\n <!-- Status Bar -->\n @if (computedConfig.showStatusBar) {\n <div class=\"ngx-editor-statusbar\">\n <div class=\"statusbar-left\">\n @if (activeBlockTag) {\n <span class=\"status-tag\"><{{ activeBlockTag }}></span>\n }\n @if (isSourceView) {\n <span class=\"status-badge\">HTML SOURCE MODE</span>\n }\n </div>\n <div class=\"statusbar-right\">\n @if (computedConfig.showWordCount) {\n <span>Words: <strong>{{ stats.words }}</strong></span>\n }\n @if (computedConfig.maxCharacters) {\n <span [class.limit-reached]=\"stats.chars > computedConfig.maxCharacters\">\n {{ stats.chars }}/{{ computedConfig.maxCharacters }} Characters\n </span>\n } @else if (computedConfig.showCharCount) {\n <span>Characters: <strong>{{ stats.chars }}</strong></span>\n }\n </div>\n </div>\n }\n\n <!-- Link Modal -->\n @if (showLinkModal) {\n <div class=\"ngx-editor-modal-backdrop\" (click)=\"closeLinkModal()\">\n <div class=\"ngx-editor-modal\" (click)=\"$event.stopPropagation()\">\n <div class=\"modal-header\">\n <h3>Insert Link</h3>\n <button type=\"button\" class=\"ngx-editor-btn\" (click)=\"closeLinkModal()\" aria-label=\"Close\">\n <ngx-editor-icon name=\"close\"></ngx-editor-icon>\n </button>\n </div>\n <div class=\"modal-body\">\n <div class=\"form-group\">\n <label>Link Text</label>\n <input\n type=\"text\"\n placeholder=\"Display text\"\n [(ngModel)]=\"linkModel.text\"\n />\n </div>\n <div class=\"form-group\">\n <label>Target URL</label>\n <input\n type=\"url\"\n placeholder=\"https://example.com\"\n [(ngModel)]=\"linkModel.url\"\n (keydown.enter)=\"insertLinkFromModal()\"\n />\n </div>\n <div class=\"form-group\">\n <label class=\"checkbox-label\">\n <input type=\"checkbox\" [(ngModel)]=\"linkModel.newTab\" />\n Open in a new tab\n </label>\n </div>\n </div>\n <div class=\"modal-footer\">\n <button type=\"button\" class=\"btn-secondary\" (click)=\"closeLinkModal()\">Cancel</button>\n <button\n type=\"button\"\n class=\"btn-primary\"\n [disabled]=\"!linkModel.url\"\n (click)=\"insertLinkFromModal()\"\n >\n Insert Link\n </button>\n </div>\n </div>\n </div>\n }\n\n <!-- Image Modal -->\n @if (showImageModal) {\n <div class=\"ngx-editor-modal-backdrop\" (click)=\"closeImageModal()\">\n <div class=\"ngx-editor-modal\" (click)=\"$event.stopPropagation()\">\n <div class=\"modal-header\">\n <h3>Insert Image</h3>\n <button type=\"button\" class=\"ngx-editor-btn\" (click)=\"closeImageModal()\" aria-label=\"Close\">\n <ngx-editor-icon name=\"close\"></ngx-editor-icon>\n </button>\n </div>\n <div class=\"modal-body\">\n <div class=\"form-group\">\n <label>Image URL</label>\n <input\n type=\"url\"\n placeholder=\"https://example.com/image.png\"\n [(ngModel)]=\"imageModel.url\"\n />\n </div>\n <div class=\"form-group\">\n <label>Or Upload File</label>\n <input\n type=\"file\"\n accept=\"image/*\"\n (change)=\"onImageFileUpload($event)\"\n />\n </div>\n <div class=\"form-group\">\n <label>Alt Description</label>\n <input\n type=\"text\"\n placeholder=\"Image description\"\n [(ngModel)]=\"imageModel.alt\"\n />\n </div>\n </div>\n <div class=\"modal-footer\">\n <button type=\"button\" class=\"btn-secondary\" (click)=\"closeImageModal()\">Cancel</button>\n <button\n type=\"button\"\n class=\"btn-primary\"\n [disabled]=\"!imageModel.url\"\n (click)=\"insertImageFromModal()\"\n >\n Insert Image\n </button>\n </div>\n </div>\n </div>\n }\n</div>\n", styles: [":host{display:block;font-family:var(--ngx-editor-font-family, inherit);box-sizing:border-box;--ngx-editor-font-family: inherit;--ngx-editor-font-size: 15px;--ngx-editor-line-height: 1.65;--ngx-editor-border: #e2e8f0;--ngx-editor-border-focus: #3b82f6;--ngx-editor-bg: #ffffff;--ngx-editor-toolbar-bg: #f8fafc;--ngx-editor-toolbar-btn-hover: #e2e8f0;--ngx-editor-toolbar-btn-active: #dbeafe;--ngx-editor-toolbar-btn-active-color: #1d4ed8;--ngx-editor-text: #1e293b;--ngx-editor-muted: #64748b;--ngx-editor-radius: 8px;--ngx-editor-shadow: 0 1px 3px 0 rgba(0, 0, 0, .1), 0 1px 2px -1px rgba(0, 0, 0, .1);--ngx-editor-shadow-focus: 0 0 0 3px rgba(59, 130, 246, .25);--ngx-editor-hashtag-color: #0069FF}.ngx-editor-container{display:flex;flex-direction:column;background-color:var(--ngx-editor-bg);border:1px solid var(--ngx-editor-border);border-radius:var(--ngx-editor-radius);box-shadow:var(--ngx-editor-shadow);overflow:hidden;transition:border-color .2s ease,box-shadow .2s ease;position:relative}.ngx-editor-container.is-focused{border-color:var(--ngx-editor-border-focus);box-shadow:var(--ngx-editor-shadow-focus)}.ngx-editor-container.is-disabled{background-color:#f1f5f9;opacity:.75;pointer-events:none;-webkit-user-select:none;user-select:none}.ngx-editor-toolbar{display:flex;flex-wrap:wrap;align-items:center;gap:4px;padding:6px 8px;background-color:var(--ngx-editor-toolbar-bg);border-bottom:1px solid var(--ngx-editor-border);-webkit-user-select:none;user-select:none;z-index:10}.ngx-editor-toolbar-group{display:flex;align-items:center;gap:2px}.ngx-editor-toolbar-group:not(:last-child):after{content:\"\";display:inline-block;width:1px;height:18px;background-color:var(--ngx-editor-border);margin:0 4px}.ngx-editor-btn{display:inline-flex;align-items:center;justify-content:center;width:32px;height:32px;padding:0;border:1px solid transparent;border-radius:4px;background:transparent;color:var(--ngx-editor-text);cursor:pointer;transition:background-color .15s ease,color .15s ease,border-color .15s ease;outline:none}.ngx-editor-btn:hover:not(:disabled){background-color:var(--ngx-editor-toolbar-btn-hover)}.ngx-editor-btn:active:not(:disabled),.ngx-editor-btn.is-active{background-color:var(--ngx-editor-toolbar-btn-active);color:var(--ngx-editor-toolbar-btn-active-color);border-color:#bfdbfe}.ngx-editor-btn:disabled{opacity:.35;cursor:not-allowed}.ngx-editor-btn svg{display:block;pointer-events:none}.ngx-editor-select{height:32px;padding:0 8px;font-size:13px;color:var(--ngx-editor-text);background-color:var(--ngx-editor-bg);border:1px solid var(--ngx-editor-border);border-radius:4px;outline:none;cursor:pointer;transition:border-color .15s ease}.ngx-editor-select:hover{border-color:#cbd5e1}.ngx-editor-select:focus{border-color:var(--ngx-editor-border-focus)}.ngx-editor-color-btn{position:relative}.ngx-editor-color-btn .color-bar{position:absolute;bottom:4px;left:6px;right:6px;height:3px;border-radius:1px}.ngx-color-popover{position:absolute;top:100%;left:0;margin-top:4px;background:#fff;border:1px solid var(--ngx-editor-border);border-radius:6px;box-shadow:0 10px 15px -3px #0000001a,0 4px 6px -4px #0000001a;padding:8px;z-index:50;width:176px}.ngx-color-popover .color-grid{display:grid;grid-template-columns:repeat(6,1fr);gap:4px;margin-bottom:8px}.ngx-color-popover .color-swatch{width:22px;height:22px;border-radius:3px;border:1px solid rgba(0,0,0,.15);cursor:pointer;padding:0;transition:transform .1s ease}.ngx-color-popover .color-swatch:hover{transform:scale(1.15);z-index:2}.ngx-color-popover .custom-color-picker{display:flex;align-items:center;gap:8px;font-size:12px;color:var(--ngx-editor-muted)}.ngx-color-popover .custom-color-picker input[type=color]{width:28px;height:24px;border:1px solid var(--ngx-editor-border);border-radius:3px;padding:0;cursor:pointer;background:none}.ngx-editor-content-wrapper{position:relative;flex:1;display:flex;flex-direction:column}.ngx-editor-content{flex:1;padding:16px;outline:none;font-family:var(--ngx-editor-font-family, inherit);font-size:var(--ngx-editor-font-size, 15px);line-height:var(--ngx-editor-line-height, 1.65);color:var(--ngx-editor-text);overflow-y:auto;min-height:200px;word-break:break-word}.ngx-editor-content p{margin:0;min-height:1.65em}.ngx-editor-content b,.ngx-editor-content strong{font-weight:700!important}.ngx-editor-content i,.ngx-editor-content em{font-style:italic!important}.ngx-editor-content u,.ngx-editor-content .publish-editor-underline{text-decoration:underline!important;text-decoration-thickness:1px;text-underline-offset:.12em}.ngx-editor-content h1{font-size:2em;font-weight:700;margin:.67em 0;line-height:1.25}.ngx-editor-content h2{font-size:1.6em;font-weight:600;margin:.75em 0;line-height:1.3}.ngx-editor-content h3{font-size:1.3em;font-weight:600;margin:.83em 0}.ngx-editor-content h4{font-size:1.1em;font-weight:600;margin:1em 0}.ngx-editor-content ul,.ngx-editor-content ol{margin:0 0 1em 1.5em;padding:0}.ngx-editor-content li{margin-bottom:.25em}.ngx-editor-content blockquote{margin:1em 0;padding:.5em 1em;border-left:4px solid var(--ngx-editor-border-focus);background-color:#f8fafc;color:var(--ngx-editor-muted);font-style:italic}.ngx-editor-content pre{background-color:#1e293b;color:#f8fafc;padding:12px;border-radius:6px;font-family:Consolas,Monaco,Courier New,monospace;font-size:13px;overflow-x:auto;margin:1em 0}.ngx-editor-content code{background-color:#f1f5f9;color:#e11d48;padding:2px 5px;border-radius:4px;font-family:Consolas,Monaco,Courier New,monospace;font-size:.9em}.ngx-editor-content a{color:#2563eb;text-decoration:underline;text-underline-offset:2px;cursor:pointer}.ngx-editor-content .ngx-editor-hashtag,.ngx-editor-content .post-content-hashtag{color:var(--ngx-editor-hashtag-color, #0069FF)!important;font-weight:500;display:inline;cursor:text}.ngx-editor-content hr{border:none;border-top:1px solid var(--ngx-editor-border);margin:1.5em 0}.ngx-editor-content img{max-width:100%;height:auto;border-radius:4px}.ngx-editor-content[data-empty=true]:before{content:attr(data-placeholder);position:absolute;color:#94a3b8;pointer-events:none;-webkit-user-select:none;user-select:none}.ngx-editor-content:has(ol,ul,li,table,img,hr,blockquote,pre):before{display:none!important;content:none!important}.ngx-editor-source{width:100%;flex:1;min-height:200px;padding:16px;border:none;outline:none;font-family:Consolas,Monaco,Courier New,monospace;font-size:13.5px;line-height:1.5;background-color:#0f172a;color:#38bdf8;resize:vertical;box-sizing:border-box}.ngx-editor-statusbar{display:flex;align-items:center;justify-content:space-between;padding:6px 12px;background-color:var(--ngx-editor-toolbar-bg);border-top:1px solid var(--ngx-editor-border);font-size:12px;color:var(--ngx-editor-muted)}.ngx-editor-statusbar .statusbar-left,.ngx-editor-statusbar .statusbar-right{display:flex;align-items:center;gap:12px}.ngx-editor-statusbar .status-tag{background-color:#e2e8f0;padding:2px 6px;border-radius:4px;font-weight:600;text-transform:uppercase;font-size:10px}.ngx-editor-statusbar .status-badge{background-color:#fee2e2;color:#ef4444;padding:2px 6px;border-radius:4px;font-weight:500;font-size:11px}.ngx-editor-modal-backdrop{position:absolute;inset:0;background:#0f172a66;-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px);display:flex;align-items:center;justify-content:center;z-index:100;animation:fadeIn .15s ease-out}.ngx-editor-modal{background:#fff;border-radius:8px;box-shadow:0 20px 25px -5px #0000001a,0 8px 10px -6px #0000001a;width:90%;max-width:400px;padding:20px;box-sizing:border-box}.ngx-editor-modal .modal-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:16px}.ngx-editor-modal .modal-header h3{margin:0;font-size:16px;font-weight:600;color:var(--ngx-editor-text)}.ngx-editor-modal .modal-body{display:flex;flex-direction:column;gap:12px}.ngx-editor-modal .form-group{display:flex;flex-direction:column;gap:4px}.ngx-editor-modal .form-group label{font-size:13px;font-weight:500;color:var(--ngx-editor-text)}.ngx-editor-modal .form-group input[type=text],.ngx-editor-modal .form-group input[type=url]{padding:8px 12px;border:1px solid var(--ngx-editor-border);border-radius:6px;font-size:14px;outline:none;transition:border-color .15s ease}.ngx-editor-modal .form-group input[type=text]:focus,.ngx-editor-modal .form-group input[type=url]:focus{border-color:var(--ngx-editor-border-focus)}.ngx-editor-modal .form-group .checkbox-label{display:flex;align-items:center;gap:8px;cursor:pointer;font-size:13px;color:var(--ngx-editor-text);-webkit-user-select:none;user-select:none}.ngx-editor-modal .modal-footer{display:flex;justify-content:flex-end;gap:8px;margin-top:20px}.ngx-editor-modal .modal-footer button{padding:7px 14px;border-radius:6px;font-size:13px;font-weight:500;cursor:pointer;transition:all .15s ease;outline:none}.ngx-editor-modal .modal-footer .btn-secondary{background:#f1f5f9;border:1px solid var(--ngx-editor-border);color:var(--ngx-editor-text)}.ngx-editor-modal .modal-footer .btn-secondary:hover{background:#e2e8f0}.ngx-editor-modal .modal-footer .btn-primary{background:#2563eb;border:1px solid #2563eb;color:#fff}.ngx-editor-modal .modal-footer .btn-primary:hover{background:#1d4ed8}.ngx-editor-modal .modal-footer .btn-primary:disabled{opacity:.5;cursor:not-allowed}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}\n"] }]
|
|
3791
|
+
], template: "<div\n class=\"ngx-editor-container\"\n [ngClass]=\"customClass\"\n [class.is-focused]=\"isFocused\"\n [class.is-disabled]=\"disabled\"\n [style.min-height]=\"computedConfig.minHeight\"\n [style.max-height]=\"computedConfig.maxHeight\"\n [style.font-family]=\"fontFamily || computedConfig.fontFamily || null\"\n [style.font-size]=\"fontSize || computedConfig.fontSize || null\"\n [ngStyle]=\"parsedEditorStyle\"\n>\n <!-- Toolbar -->\n @if (computedConfig.showToolbar && !readonly) {\n <!-- Array-configured Toolbar (if tools input is provided) -->\n @if (computedConfig.tools && computedConfig.tools.length > 0) {\n <ngx-editor-toolbar\n [editor]=\"this\"\n [tools]=\"computedConfig.tools\"\n [fontFamilies]=\"resolvedFontFamilies\"\n ></ngx-editor-toolbar>\n } @else {\n <!-- Default Full Toolbar -->\n <div class=\"ngx-editor-toolbar\" role=\"toolbar\" aria-label=\"Editor Toolbar\">\n <!-- History -->\n @if (computedConfig.toolbar?.history) {\n <div class=\"ngx-editor-toolbar-group\">\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n title=\"Undo (Ctrl+Z)\"\n aria-label=\"Undo\"\n (mousedown)=\"$event.preventDefault(); undo()\"\n >\n <ngx-editor-icon name=\"undo\"></ngx-editor-icon>\n </button>\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n title=\"Redo (Ctrl+Y)\"\n aria-label=\"Redo\"\n (mousedown)=\"$event.preventDefault(); redo()\"\n >\n <ngx-editor-icon name=\"redo\"></ngx-editor-icon>\n </button>\n </div>\n }\n\n <!-- Format Block -->\n @if (computedConfig.toolbar?.format) {\n <div class=\"ngx-editor-toolbar-group\">\n <select\n class=\"ngx-editor-select\"\n title=\"Paragraph Format\"\n aria-label=\"Paragraph Format\"\n [value]=\"activeBlockTag\"\n (change)=\"onFormatBlockChange($event)\"\n >\n <option value=\"p\">Normal text</option>\n <option value=\"h1\">Heading 1</option>\n <option value=\"h2\">Heading 2</option>\n <option value=\"h3\">Heading 3</option>\n <option value=\"h4\">Heading 4</option>\n <option value=\"blockquote\">Quote</option>\n <option value=\"pre\">Code Block</option>\n </select>\n </div>\n }\n\n <!-- Font Family -->\n @if (computedConfig.toolbar?.fontName) {\n <div class=\"ngx-editor-toolbar-group\">\n <select\n class=\"ngx-editor-select\"\n title=\"Font Family\"\n aria-label=\"Font Family\"\n (change)=\"onFontNameChange($event)\"\n >\n <option value=\"\">Font</option>\n @for (font of resolvedFontFamilies; track font.value) {\n <option [value]=\"font.value\">{{ font.label }}</option>\n }\n </select>\n </div>\n }\n\n <!-- Font Size -->\n @if (computedConfig.toolbar?.fontSize) {\n <div class=\"ngx-editor-toolbar-group\">\n <select\n class=\"ngx-editor-select\"\n title=\"Font Size\"\n aria-label=\"Font Size\"\n (change)=\"onFontSizeChange($event)\"\n >\n <option value=\"\">Size</option>\n <option value=\"1\">Small (10pt)</option>\n <option value=\"3\">Normal (12pt)</option>\n <option value=\"5\">Large (18pt)</option>\n <option value=\"7\">Huge (36pt)</option>\n </select>\n </div>\n }\n\n <!-- Inline Styles: Bold, Italic, Underline, Strike, Sub, Sup, Code -->\n @if (computedConfig.toolbar?.inline) {\n <div class=\"ngx-editor-toolbar-group\">\n @if (computedConfig.toolbar?.inline?.bold !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n [class.is-active]=\"activeStates.bold\"\n title=\"Bold (Ctrl+B)\"\n aria-label=\"Bold\"\n (mousedown)=\"$event.preventDefault(); bold()\"\n >\n <ngx-editor-icon name=\"bold\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.inline?.italic !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n [class.is-active]=\"activeStates.italic\"\n title=\"Italic (Ctrl+I)\"\n aria-label=\"Italic\"\n (mousedown)=\"$event.preventDefault(); italic()\"\n >\n <ngx-editor-icon name=\"italic\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.inline?.underline !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n [class.is-active]=\"activeStates.underline\"\n title=\"Underline (Ctrl+U)\"\n aria-label=\"Underline\"\n (mousedown)=\"$event.preventDefault(); underline()\"\n >\n <ngx-editor-icon name=\"underline\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.inline?.strikeThrough !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n [class.is-active]=\"activeStates.strikeThrough\"\n title=\"Strikethrough\"\n aria-label=\"Strikethrough\"\n (mousedown)=\"$event.preventDefault(); strike()\"\n >\n <ngx-editor-icon name=\"strike\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.inline?.subscript !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n [class.is-active]=\"activeStates.subscript\"\n title=\"Subscript\"\n aria-label=\"Subscript\"\n (mousedown)=\"$event.preventDefault(); subscript()\"\n >\n <ngx-editor-icon name=\"subscript\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.inline?.superscript !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n [class.is-active]=\"activeStates.superscript\"\n title=\"Superscript\"\n aria-label=\"Superscript\"\n (mousedown)=\"$event.preventDefault(); superscript()\"\n >\n <ngx-editor-icon name=\"superscript\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.inline?.code !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n title=\"Inline Code\"\n aria-label=\"Inline Code\"\n (mousedown)=\"$event.preventDefault(); wrapSelectionWithCode()\"\n >\n <ngx-editor-icon name=\"code\"></ngx-editor-icon>\n </button>\n }\n </div>\n }\n\n <!-- Colors -->\n @if (computedConfig.toolbar?.color) {\n <div class=\"ngx-editor-toolbar-group\" style=\"position: relative;\">\n @if (computedConfig.toolbar?.color?.textColor !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn ngx-editor-color-btn\"\n title=\"Text Color\"\n aria-label=\"Text Color\"\n (click)=\"toggleColorPicker('text')\"\n >\n <ngx-editor-icon name=\"textColor\"></ngx-editor-icon>\n </button>\n <div\n class=\"color-bar\"\n [style.background-color]=\"selectedTextColor\"\n ></div>\n }\n\n @if (computedConfig.toolbar?.color?.backgroundColor !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn ngx-editor-color-btn\"\n title=\"Highlight Color\"\n aria-label=\"Highlight Color\"\n (click)=\"toggleColorPicker('background')\"\n >\n <ngx-editor-icon name=\"bgColor\"></ngx-editor-icon>\n </button>\n <div\n class=\"color-bar\"\n [style.background-color]=\"selectedBgColor\"\n ></div>\n }\n\n <!-- Color Popover -->\n @if (activeColorPicker) {\n <div class=\"ngx-color-popover\" (mousedown)=\"$event.stopPropagation()\">\n <div class=\"color-grid\">\n @for (color of computedConfig.colorPalette; track color) {\n <button\n type=\"button\"\n class=\"color-swatch\"\n [style.background-color]=\"color\"\n (click)=\"applyColor(color)\"\n ></button>\n }\n </div>\n <div class=\"custom-color-picker\">\n <input\n type=\"color\"\n (change)=\"onCustomColorChange($event)\"\n />\n <span>Custom color</span>\n </div>\n </div>\n }\n </div>\n }\n\n <!-- Alignment -->\n @if (computedConfig.toolbar?.alignment) {\n <div class=\"ngx-editor-toolbar-group\">\n @if (computedConfig.toolbar?.alignment?.left !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n [class.is-active]=\"activeStates.justifyLeft\"\n title=\"Align Left\"\n aria-label=\"Align Left\"\n (mousedown)=\"$event.preventDefault(); alignLeft()\"\n >\n <ngx-editor-icon name=\"alignLeft\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.alignment?.center !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n [class.is-active]=\"activeStates.justifyCenter\"\n title=\"Align Center\"\n aria-label=\"Align Center\"\n (mousedown)=\"$event.preventDefault(); alignCenter()\"\n >\n <ngx-editor-icon name=\"alignCenter\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.alignment?.right !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n [class.is-active]=\"activeStates.justifyRight\"\n title=\"Align Right\"\n aria-label=\"Align Right\"\n (mousedown)=\"$event.preventDefault(); alignRight()\"\n >\n <ngx-editor-icon name=\"alignRight\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.alignment?.justify !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n [class.is-active]=\"activeStates.justifyFull\"\n title=\"Justify\"\n aria-label=\"Justify\"\n (mousedown)=\"$event.preventDefault(); alignJustify()\"\n >\n <ngx-editor-icon name=\"alignJustify\"></ngx-editor-icon>\n </button>\n }\n </div>\n }\n\n <!-- Lists & Indentation -->\n @if (computedConfig.toolbar?.list) {\n <div class=\"ngx-editor-toolbar-group\">\n @if (computedConfig.toolbar?.list?.unordered !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n [class.is-active]=\"activeStates.insertUnorderedList\"\n title=\"Bullet List\"\n aria-label=\"Bullet List\"\n (mousedown)=\"$event.preventDefault(); insertUnorderedList()\"\n >\n <ngx-editor-icon name=\"unorderedList\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.list?.ordered !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n [class.is-active]=\"activeStates.insertOrderedList\"\n title=\"Numbered List\"\n aria-label=\"Numbered List\"\n (mousedown)=\"$event.preventDefault(); insertOrderedList()\"\n >\n <ngx-editor-icon name=\"orderedList\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.list?.indent !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n title=\"Increase Indent\"\n aria-label=\"Increase Indent\"\n (mousedown)=\"$event.preventDefault(); indent()\"\n >\n <ngx-editor-icon name=\"indent\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.list?.outdent !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n title=\"Decrease Indent\"\n aria-label=\"Decrease Indent\"\n (mousedown)=\"$event.preventDefault(); outdent()\"\n >\n <ngx-editor-icon name=\"outdent\"></ngx-editor-icon>\n </button>\n }\n </div>\n }\n\n <!-- Insert Options -->\n @if (computedConfig.toolbar?.insert) {\n <div class=\"ngx-editor-toolbar-group\">\n @if (computedConfig.toolbar?.insert?.link !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n [class.is-active]=\"activeStates.link\"\n title=\"Insert Link (Ctrl+K)\"\n aria-label=\"Insert Link\"\n (click)=\"openLinkModal()\"\n >\n <ngx-editor-icon name=\"link\"></ngx-editor-icon>\n </button>\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n title=\"Remove Link\"\n aria-label=\"Remove Link\"\n (mousedown)=\"$event.preventDefault(); unlink()\"\n >\n <ngx-editor-icon name=\"unlink\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.insert?.image !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n title=\"Insert Image\"\n aria-label=\"Insert Image\"\n (click)=\"openImageModal()\"\n >\n <ngx-editor-icon name=\"image\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.insert?.horizontalRule !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n title=\"Horizontal Line\"\n aria-label=\"Horizontal Line\"\n (mousedown)=\"$event.preventDefault(); insertHorizontalRule()\"\n >\n <ngx-editor-icon name=\"horizontalRule\"></ngx-editor-icon>\n </button>\n }\n </div>\n }\n\n <!-- Extra Actions -->\n @if (computedConfig.toolbar?.extra) {\n <div class=\"ngx-editor-toolbar-group\">\n @if (computedConfig.toolbar?.extra?.clearFormat !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n title=\"Clear Formatting\"\n aria-label=\"Clear Formatting\"\n (mousedown)=\"$event.preventDefault(); clearFormatting()\"\n >\n <ngx-editor-icon name=\"clearFormat\"></ngx-editor-icon>\n </button>\n }\n @if (computedConfig.toolbar?.extra?.sourceView !== false) {\n <button\n type=\"button\"\n class=\"ngx-editor-btn\"\n [class.is-active]=\"isSourceView\"\n title=\"View HTML Source\"\n aria-label=\"View HTML Source\"\n (click)=\"toggleSourceView()\"\n >\n <ngx-editor-icon name=\"sourceView\"></ngx-editor-icon>\n </button>\n }\n </div>\n }\n </div>\n }\n }\n\n <!-- Editor Content Area -->\n <div class=\"ngx-editor-content-wrapper\">\n <!-- Visual contenteditable -->\n <div\n #editorContent\n class=\"ngx-editor-content\"\n [style.display]=\"isSourceView ? 'none' : 'block'\"\n [style.height]=\"computedConfig.height\"\n [style.font-family]=\"fontFamily || computedConfig.fontFamily || null\"\n [style.font-size]=\"fontSize || computedConfig.fontSize || null\"\n [ngStyle]=\"parsedContentStyle\"\n [attr.contenteditable]=\"!disabled && !readonly\"\n [attr.spellcheck]=\"computedConfig.spellcheck\"\n [attr.data-placeholder]=\"computedConfig.placeholder\"\n [attr.data-empty]=\"isEmpty\"\n (input)=\"onEditorInput()\"\n (focus)=\"onEditorFocus($event)\"\n (blur)=\"onEditorBlur($event)\"\n (keydown)=\"onEditorKeydown($event)\"\n (keyup)=\"updateEditorState()\"\n (mouseup)=\"updateEditorState()\"\n (paste)=\"onEditorPaste($event)\"\n ></div>\n\n <!-- Raw HTML Source Code View -->\n @if (isSourceView) {\n <textarea\n #sourceTextarea\n class=\"ngx-editor-source\"\n [style.height]=\"computedConfig.height\"\n [disabled]=\"disabled || readonly\"\n [value]=\"content\"\n (input)=\"onSourceInput($event)\"\n ></textarea>\n }\n </div>\n\n <!-- Status Bar -->\n @if (computedConfig.showStatusBar) {\n <div class=\"ngx-editor-statusbar\">\n <div class=\"statusbar-left\">\n @if (activeBlockTag) {\n <span class=\"status-tag\"><{{ activeBlockTag }}></span>\n }\n @if (isSourceView) {\n <span class=\"status-badge\">HTML SOURCE MODE</span>\n }\n </div>\n <div class=\"statusbar-right\">\n @if (computedConfig.showWordCount) {\n <span>Words: <strong>{{ stats.words }}</strong></span>\n }\n @if (computedConfig.maxCharacters) {\n <span [class.limit-reached]=\"stats.chars > computedConfig.maxCharacters\">\n {{ stats.chars }}/{{ computedConfig.maxCharacters }} Characters\n </span>\n } @else if (computedConfig.showCharCount) {\n <span>Characters: <strong>{{ stats.chars }}</strong></span>\n }\n </div>\n </div>\n }\n\n <!-- Link Modal -->\n @if (showLinkModal) {\n <div class=\"ngx-editor-modal-backdrop\" (click)=\"closeLinkModal()\">\n <div class=\"ngx-editor-modal\" (click)=\"$event.stopPropagation()\">\n <div class=\"modal-header\">\n <h3>Insert Link</h3>\n <button type=\"button\" class=\"ngx-editor-btn\" (click)=\"closeLinkModal()\" aria-label=\"Close\">\n <ngx-editor-icon name=\"close\"></ngx-editor-icon>\n </button>\n </div>\n <div class=\"modal-body\">\n <div class=\"form-group\">\n <label>Link Text</label>\n <input\n type=\"text\"\n placeholder=\"Display text\"\n [(ngModel)]=\"linkModel.text\"\n />\n </div>\n <div class=\"form-group\">\n <label>Target URL</label>\n <input\n type=\"url\"\n placeholder=\"https://example.com\"\n [(ngModel)]=\"linkModel.url\"\n (keydown.enter)=\"insertLinkFromModal()\"\n />\n </div>\n <div class=\"form-group\">\n <label class=\"checkbox-label\">\n <input type=\"checkbox\" [(ngModel)]=\"linkModel.newTab\" />\n Open in a new tab\n </label>\n </div>\n </div>\n <div class=\"modal-footer\">\n <button type=\"button\" class=\"btn-secondary\" (click)=\"closeLinkModal()\">Cancel</button>\n <button\n type=\"button\"\n class=\"btn-primary\"\n [disabled]=\"!linkModel.url\"\n (click)=\"insertLinkFromModal()\"\n >\n Insert Link\n </button>\n </div>\n </div>\n </div>\n }\n\n <!-- Image Modal -->\n @if (showImageModal) {\n <div class=\"ngx-editor-modal-backdrop\" (click)=\"closeImageModal()\">\n <div class=\"ngx-editor-modal\" (click)=\"$event.stopPropagation()\">\n <div class=\"modal-header\">\n <h3>Insert Image</h3>\n <button type=\"button\" class=\"ngx-editor-btn\" (click)=\"closeImageModal()\" aria-label=\"Close\">\n <ngx-editor-icon name=\"close\"></ngx-editor-icon>\n </button>\n </div>\n <div class=\"modal-body\">\n <div class=\"form-group\">\n <label>Image URL</label>\n <input\n type=\"url\"\n placeholder=\"https://example.com/image.png\"\n [(ngModel)]=\"imageModel.url\"\n />\n </div>\n <div class=\"form-group\">\n <label>Or Upload File</label>\n <input\n type=\"file\"\n accept=\"image/*\"\n (change)=\"onImageFileUpload($event)\"\n />\n </div>\n <div class=\"form-group\">\n <label>Alt Description</label>\n <input\n type=\"text\"\n placeholder=\"Image description\"\n [(ngModel)]=\"imageModel.alt\"\n />\n </div>\n </div>\n <div class=\"modal-footer\">\n <button type=\"button\" class=\"btn-secondary\" (click)=\"closeImageModal()\">Cancel</button>\n <button\n type=\"button\"\n class=\"btn-primary\"\n [disabled]=\"!imageModel.url\"\n (click)=\"insertImageFromModal()\"\n >\n Insert Image\n </button>\n </div>\n </div>\n </div>\n }\n</div>\n", styles: [":host{display:block;font-family:var(--ngx-editor-font-family, inherit);box-sizing:border-box;--ngx-editor-font-family: inherit;--ngx-editor-font-size: 15px;--ngx-editor-line-height: 1.65;--ngx-editor-border: #e2e8f0;--ngx-editor-border-focus: #3b82f6;--ngx-editor-bg: #ffffff;--ngx-editor-toolbar-bg: #f8fafc;--ngx-editor-toolbar-btn-hover: #e2e8f0;--ngx-editor-toolbar-btn-active: #dbeafe;--ngx-editor-toolbar-btn-active-color: #1d4ed8;--ngx-editor-text: inherit;--ngx-editor-muted: #64748b;--ngx-editor-radius: 8px;--ngx-editor-shadow: 0 1px 3px 0 rgba(0, 0, 0, .1), 0 1px 2px -1px rgba(0, 0, 0, .1);--ngx-editor-shadow-focus: 0 0 0 3px rgba(59, 130, 246, .25);--ngx-editor-hashtag-color: #0069FF}.ngx-editor-container{display:flex;flex-direction:column;background-color:var(--ngx-editor-bg);border:1px solid var(--ngx-editor-border);border-radius:var(--ngx-editor-radius);box-shadow:var(--ngx-editor-shadow);overflow:hidden;transition:border-color .2s ease,box-shadow .2s ease;position:relative}.ngx-editor-container.is-focused{border-color:var(--ngx-editor-border-focus);box-shadow:var(--ngx-editor-shadow-focus)}.ngx-editor-container.is-disabled{background-color:#f1f5f9;opacity:.75;pointer-events:none;-webkit-user-select:none;user-select:none}.ngx-editor-toolbar{display:flex;flex-wrap:wrap;align-items:center;gap:4px;padding:6px 8px;background-color:var(--ngx-editor-toolbar-bg);border-bottom:1px solid var(--ngx-editor-border);-webkit-user-select:none;user-select:none;z-index:10}.ngx-editor-toolbar-group{display:flex;align-items:center;gap:2px}.ngx-editor-toolbar-group:not(:last-child):after{content:\"\";display:inline-block;width:1px;height:18px;background-color:var(--ngx-editor-border);margin:0 4px}.ngx-editor-btn{display:inline-flex;align-items:center;justify-content:center;width:32px;height:32px;padding:0;border:1px solid transparent;border-radius:4px;background:transparent;color:var(--ngx-editor-text);cursor:pointer;transition:background-color .15s ease,color .15s ease,border-color .15s ease;outline:none}.ngx-editor-btn:hover:not(:disabled){background-color:var(--ngx-editor-toolbar-btn-hover)}.ngx-editor-btn:active:not(:disabled),.ngx-editor-btn.is-active{background-color:var(--ngx-editor-toolbar-btn-active);color:var(--ngx-editor-toolbar-btn-active-color);border-color:#bfdbfe}.ngx-editor-btn:disabled{opacity:.35;cursor:not-allowed}.ngx-editor-btn svg{display:block;pointer-events:none}.ngx-editor-select{height:32px;padding:0 8px;font-size:13px;color:var(--ngx-editor-text);background-color:var(--ngx-editor-bg);border:1px solid var(--ngx-editor-border);border-radius:4px;outline:none;cursor:pointer;transition:border-color .15s ease}.ngx-editor-select:hover{border-color:#cbd5e1}.ngx-editor-select:focus{border-color:var(--ngx-editor-border-focus)}.ngx-editor-color-btn{position:relative}.ngx-editor-color-btn .color-bar{position:absolute;bottom:4px;left:6px;right:6px;height:3px;border-radius:1px}.ngx-color-popover{position:absolute;top:100%;left:0;margin-top:4px;background:#fff;border:1px solid var(--ngx-editor-border);border-radius:6px;box-shadow:0 10px 15px -3px #0000001a,0 4px 6px -4px #0000001a;padding:8px;z-index:50;width:176px}.ngx-color-popover .color-grid{display:grid;grid-template-columns:repeat(6,1fr);gap:4px;margin-bottom:8px}.ngx-color-popover .color-swatch{width:22px;height:22px;border-radius:3px;border:1px solid rgba(0,0,0,.15);cursor:pointer;padding:0;transition:transform .1s ease}.ngx-color-popover .color-swatch:hover{transform:scale(1.15);z-index:2}.ngx-color-popover .custom-color-picker{display:flex;align-items:center;gap:8px;font-size:12px;color:var(--ngx-editor-muted)}.ngx-color-popover .custom-color-picker input[type=color]{width:28px;height:24px;border:1px solid var(--ngx-editor-border);border-radius:3px;padding:0;cursor:pointer;background:none}.ngx-editor-content-wrapper{position:relative;flex:1;display:flex;flex-direction:column}.ngx-editor-content{flex:1;padding:16px;outline:none;font-family:var(--ngx-editor-font-family, inherit);font-size:var(--ngx-editor-font-size, 15px);line-height:var(--ngx-editor-line-height, 1.65);color:var(--ngx-editor-text, inherit);overflow-y:auto;min-height:200px;word-break:break-word}.ngx-editor-content::selection,.ngx-editor-content *::selection{color:inherit;background:#70b5f959}.ngx-editor-content p{margin:0;min-height:1.65em}.ngx-editor-content b,.ngx-editor-content strong{font-weight:700!important}.ngx-editor-content i,.ngx-editor-content em{font-style:italic!important}.ngx-editor-content u,.ngx-editor-content .publish-editor-underline{text-decoration:underline!important;text-decoration-thickness:1px;text-underline-offset:.12em}.ngx-editor-content h1{font-size:2em;font-weight:700;margin:.67em 0;line-height:1.25}.ngx-editor-content h2{font-size:1.6em;font-weight:600;margin:.75em 0;line-height:1.3}.ngx-editor-content h3{font-size:1.3em;font-weight:600;margin:.83em 0}.ngx-editor-content h4{font-size:1.1em;font-weight:600;margin:1em 0}.ngx-editor-content ul,.ngx-editor-content ol{margin:0 0 1em 1.5em;padding:0}.ngx-editor-content li{margin-bottom:.25em}.ngx-editor-content blockquote{margin:1em 0;padding:.5em 1em;border-left:4px solid var(--ngx-editor-border-focus);background-color:#f8fafc;color:var(--ngx-editor-muted);font-style:italic}.ngx-editor-content pre{background-color:#1e293b;color:#f8fafc;padding:12px;border-radius:6px;font-family:Consolas,Monaco,Courier New,monospace;font-size:13px;overflow-x:auto;margin:1em 0}.ngx-editor-content code{background-color:#f1f5f9;color:#e11d48;padding:2px 5px;border-radius:4px;font-family:Consolas,Monaco,Courier New,monospace;font-size:.9em}.ngx-editor-content a{color:#2563eb;text-decoration:underline;text-underline-offset:2px;cursor:pointer}.ngx-editor-content .ngx-editor-hashtag,.ngx-editor-content .post-content-hashtag{color:var(--ngx-editor-hashtag-color, #0069FF)!important;font-weight:500;display:inline;cursor:text}.ngx-editor-content hr{border:none;border-top:1px solid var(--ngx-editor-border);margin:1.5em 0}.ngx-editor-content img{max-width:100%;height:auto;border-radius:4px}.ngx-editor-content[data-empty=true]:before{content:attr(data-placeholder);position:absolute;color:#94a3b8;pointer-events:none;-webkit-user-select:none;user-select:none}.ngx-editor-content:has(ol,ul,li,table,img,hr,blockquote,pre):before{display:none!important;content:none!important}.ngx-editor-source{width:100%;flex:1;min-height:200px;padding:16px;border:none;outline:none;font-family:Consolas,Monaco,Courier New,monospace;font-size:13.5px;line-height:1.5;background-color:#0f172a;color:#38bdf8;resize:vertical;box-sizing:border-box}.ngx-editor-statusbar{display:flex;align-items:center;justify-content:space-between;padding:6px 12px;background-color:var(--ngx-editor-toolbar-bg);border-top:1px solid var(--ngx-editor-border);font-size:12px;color:var(--ngx-editor-muted)}.ngx-editor-statusbar .statusbar-left,.ngx-editor-statusbar .statusbar-right{display:flex;align-items:center;gap:12px}.ngx-editor-statusbar .status-tag{background-color:#e2e8f0;padding:2px 6px;border-radius:4px;font-weight:600;text-transform:uppercase;font-size:10px}.ngx-editor-statusbar .status-badge{background-color:#fee2e2;color:#ef4444;padding:2px 6px;border-radius:4px;font-weight:500;font-size:11px}.ngx-editor-modal-backdrop{position:absolute;inset:0;background:#0f172a66;-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px);display:flex;align-items:center;justify-content:center;z-index:100;animation:fadeIn .15s ease-out}.ngx-editor-modal{background:#fff;border-radius:8px;box-shadow:0 20px 25px -5px #0000001a,0 8px 10px -6px #0000001a;width:90%;max-width:400px;padding:20px;box-sizing:border-box}.ngx-editor-modal .modal-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:16px}.ngx-editor-modal .modal-header h3{margin:0;font-size:16px;font-weight:600;color:var(--ngx-editor-text)}.ngx-editor-modal .modal-body{display:flex;flex-direction:column;gap:12px}.ngx-editor-modal .form-group{display:flex;flex-direction:column;gap:4px}.ngx-editor-modal .form-group label{font-size:13px;font-weight:500;color:var(--ngx-editor-text)}.ngx-editor-modal .form-group input[type=text],.ngx-editor-modal .form-group input[type=url]{padding:8px 12px;border:1px solid var(--ngx-editor-border);border-radius:6px;font-size:14px;outline:none;transition:border-color .15s ease}.ngx-editor-modal .form-group input[type=text]:focus,.ngx-editor-modal .form-group input[type=url]:focus{border-color:var(--ngx-editor-border-focus)}.ngx-editor-modal .form-group .checkbox-label{display:flex;align-items:center;gap:8px;cursor:pointer;font-size:13px;color:var(--ngx-editor-text);-webkit-user-select:none;user-select:none}.ngx-editor-modal .modal-footer{display:flex;justify-content:flex-end;gap:8px;margin-top:20px}.ngx-editor-modal .modal-footer button{padding:7px 14px;border-radius:6px;font-size:13px;font-weight:500;cursor:pointer;transition:all .15s ease;outline:none}.ngx-editor-modal .modal-footer .btn-secondary{background:#f1f5f9;border:1px solid var(--ngx-editor-border);color:var(--ngx-editor-text)}.ngx-editor-modal .modal-footer .btn-secondary:hover{background:#e2e8f0}.ngx-editor-modal .modal-footer .btn-primary{background:#2563eb;border:1px solid #2563eb;color:#fff}.ngx-editor-modal .modal-footer .btn-primary:hover{background:#1d4ed8}.ngx-editor-modal .modal-footer .btn-primary:disabled{opacity:.5;cursor:not-allowed}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}\n"] }]
|
|
3647
3792
|
}], ctorParameters: () => [{ type: EditorCommandService }, { type: i2.DomSanitizer }, { type: i0.ElementRef }, { type: i0.ChangeDetectorRef }], propDecorators: { editorContentRef: [{
|
|
3648
3793
|
type: ViewChild,
|
|
3649
3794
|
args: ['editorContent', { static: false }]
|