suneditor 3.0.3 → 3.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/suneditor.min.css +1 -1
- package/dist/suneditor.min.js +1 -1
- package/package.json +1 -1
- package/src/assets/suneditor.css +34 -4
- package/src/core/config/optionProvider.js +1 -1
- package/src/core/editor.js +2 -0
- package/src/core/event/eventOrchestrator.js +3 -0
- package/src/core/logic/dom/char.js +27 -4
- package/src/core/logic/panel/viewer.js +9 -5
- package/src/core/schema/frameContext.js +6 -0
- package/src/core/schema/options.js +9 -0
- package/src/core/section/constructor.js +32 -2
- package/src/helper/markdown.js +18 -3
- package/src/modules/contract/Controller.js +1 -2
- package/src/modules/contract/Figure.js +1 -1
- package/src/plugins/command/codeBlock.js +1 -1
- package/src/plugins/command/list_bulleted.js +1 -1
- package/src/plugins/command/list_numbered.js +1 -1
- package/src/plugins/input/fontSize.js +4 -4
- package/types/core/logic/dom/char.d.ts +10 -1
- package/types/core/schema/frameContext.d.ts +10 -0
- package/types/core/schema/options.d.ts +20 -0
- package/types/core/section/constructor.d.ts +3 -1
package/package.json
CHANGED
package/src/assets/suneditor.css
CHANGED
|
@@ -1066,11 +1066,11 @@
|
|
|
1066
1066
|
}
|
|
1067
1067
|
|
|
1068
1068
|
.sun-editor .se-btn-select.se-btn-tool-font {
|
|
1069
|
-
width:
|
|
1069
|
+
width: 128px;
|
|
1070
1070
|
}
|
|
1071
1071
|
|
|
1072
1072
|
.sun-editor .se-btn-select.se-btn-tool-format {
|
|
1073
|
-
width:
|
|
1073
|
+
width: 96px;
|
|
1074
1074
|
}
|
|
1075
1075
|
|
|
1076
1076
|
.sun-editor .se-btn-select.se-btn-tool-font-size .se-txt {
|
|
@@ -1880,7 +1880,27 @@
|
|
|
1880
1880
|
background: transparent;
|
|
1881
1881
|
}
|
|
1882
1882
|
|
|
1883
|
+
/** status bar - wordCounter */
|
|
1884
|
+
.sun-editor .se-status-bar .se-word-counter-wrapper {
|
|
1885
|
+
position: relative;
|
|
1886
|
+
width: auto;
|
|
1887
|
+
height: auto;
|
|
1888
|
+
margin: 0;
|
|
1889
|
+
padding: 0;
|
|
1890
|
+
color: var(--se-statusbar-font-color);
|
|
1891
|
+
font-size: var(--se-statusbar-font-size);
|
|
1892
|
+
background: transparent;
|
|
1893
|
+
}
|
|
1894
|
+
|
|
1895
|
+
.sun-editor .se-status-bar .se-word-counter-wrapper .se-word-label {
|
|
1896
|
+
margin-right: 4px;
|
|
1897
|
+
}
|
|
1898
|
+
|
|
1883
1899
|
/** status bar - charCounter */
|
|
1900
|
+
.sun-editor .se-status-bar .se-char-counter-wrapper.se-with-word-counter {
|
|
1901
|
+
margin-left: 0.6em;
|
|
1902
|
+
}
|
|
1903
|
+
|
|
1884
1904
|
.sun-editor .se-status-bar .se-char-counter-wrapper {
|
|
1885
1905
|
flex: none;
|
|
1886
1906
|
position: relative;
|
|
@@ -1890,7 +1910,7 @@
|
|
|
1890
1910
|
margin: 0;
|
|
1891
1911
|
padding: 0;
|
|
1892
1912
|
color: var(--se-statusbar-font-color);
|
|
1893
|
-
font-size: var(--se-
|
|
1913
|
+
font-size: var(--se-statusbar-font-size);
|
|
1894
1914
|
background: transparent;
|
|
1895
1915
|
}
|
|
1896
1916
|
|
|
@@ -3884,10 +3904,20 @@
|
|
|
3884
3904
|
}
|
|
3885
3905
|
|
|
3886
3906
|
/* statusbar */
|
|
3887
|
-
.sun-editor.se-rtl .se-status-bar
|
|
3907
|
+
.sun-editor.se-rtl .se-status-bar {
|
|
3888
3908
|
direction: rtl;
|
|
3889
3909
|
}
|
|
3890
3910
|
|
|
3911
|
+
.sun-editor.se-rtl .se-status-bar .se-word-counter-wrapper .se-word-label {
|
|
3912
|
+
margin-right: 0;
|
|
3913
|
+
margin-left: 4px;
|
|
3914
|
+
}
|
|
3915
|
+
|
|
3916
|
+
.sun-editor.se-rtl .se-status-bar .se-char-counter-wrapper.se-with-word-counter {
|
|
3917
|
+
margin-left: 0;
|
|
3918
|
+
margin-right: 0.6em;
|
|
3919
|
+
}
|
|
3920
|
+
|
|
3891
3921
|
/* button--- */
|
|
3892
3922
|
/* button - select text */
|
|
3893
3923
|
.sun-editor.se-rtl .se-btn-select .se-txt {
|
|
@@ -331,7 +331,7 @@ export default class OptionProvider {
|
|
|
331
331
|
}
|
|
332
332
|
|
|
333
333
|
#GetResetDiffKey(key) {
|
|
334
|
-
if (/^statusbar|^charCounter/.test(key)) return 'statusbar-changed';
|
|
334
|
+
if (/^statusbar|^charCounter|^wordCounter/.test(key)) return 'statusbar-changed';
|
|
335
335
|
return key;
|
|
336
336
|
}
|
|
337
337
|
|
package/src/core/editor.js
CHANGED
|
@@ -204,6 +204,8 @@ class Editor {
|
|
|
204
204
|
|
|
205
205
|
// char counter
|
|
206
206
|
if (e.has('charCounter')) e.get('charCounter').textContent = String(this.$.char.getLength());
|
|
207
|
+
// word counter
|
|
208
|
+
if (e.has('wordCounter')) e.get('wordCounter').textContent = String(this.$.char.getWordCount());
|
|
207
209
|
|
|
208
210
|
// document type init
|
|
209
211
|
if (this.$.options.get('type') === 'document') {
|
|
@@ -669,6 +669,9 @@ class EventOrchestrator extends KernelInjector {
|
|
|
669
669
|
if (this.#store.mode.isInline || this.#store.mode.isBalloonAlways) this.#toolbar.show();
|
|
670
670
|
if (this.#store.mode.isSubBalloonAlways) this.$.subToolbar.show();
|
|
671
671
|
|
|
672
|
+
// sticky
|
|
673
|
+
this.#toolbar._resetSticky();
|
|
674
|
+
|
|
672
675
|
// user event
|
|
673
676
|
this.#eventManager.triggerEvent('onFocus', { frameContext, event });
|
|
674
677
|
// plugin event
|
|
@@ -92,15 +92,38 @@ class Char {
|
|
|
92
92
|
}
|
|
93
93
|
|
|
94
94
|
/**
|
|
95
|
-
* @description
|
|
95
|
+
* @description Get the number of words in the content.
|
|
96
|
+
* - If [content] is `undefined`, get the current editor's word count.
|
|
97
|
+
* @param {string} [content] Content to count. (default: wysiwyg textContent)
|
|
98
|
+
* @returns {number}
|
|
99
|
+
* const currentWords = editor.$.char.getWordCount();
|
|
100
|
+
* const textWords = editor.$.char.getWordCount('Hello World');
|
|
101
|
+
*/
|
|
102
|
+
getWordCount(content) {
|
|
103
|
+
if (typeof content !== 'string') {
|
|
104
|
+
content = this.#frameContext.get('wysiwyg').innerText;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const trimmed = content.trim();
|
|
108
|
+
if (!trimmed) return 0;
|
|
109
|
+
|
|
110
|
+
return trimmed.split(/\s+/).length;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* @description Set the char count and word count to counter element textContent.
|
|
96
115
|
* @param {?SunEditor.FrameContext} [fc] Frame context
|
|
97
116
|
*/
|
|
98
117
|
display(fc) {
|
|
99
|
-
const
|
|
100
|
-
|
|
118
|
+
const ctx = fc || this.#frameContext;
|
|
119
|
+
const charCounter = ctx.get('charCounter');
|
|
120
|
+
const wordCounter = ctx.get('wordCounter');
|
|
121
|
+
|
|
122
|
+
if (charCounter || wordCounter) {
|
|
101
123
|
// Defer count update — DOM content may still be mutating from the current input/paste action
|
|
102
124
|
_w.setTimeout(() => {
|
|
103
|
-
charCounter.textContent = String(this.getLength());
|
|
125
|
+
if (charCounter) charCounter.textContent = String(this.getLength());
|
|
126
|
+
if (wordCounter) wordCounter.textContent = String(this.getWordCount());
|
|
104
127
|
}, 0);
|
|
105
128
|
}
|
|
106
129
|
}
|
|
@@ -300,7 +300,7 @@ class Viewer {
|
|
|
300
300
|
this.#originCssText = topArea.style.cssText;
|
|
301
301
|
this.#editorAreaOriginCssText = editorArea.style.cssText;
|
|
302
302
|
this.#wysiwygOriginCssText = wysiwygFrame.style.cssText;
|
|
303
|
-
this.#codeWrapperOriginCssText = codeWrapper
|
|
303
|
+
this.#codeWrapperOriginCssText = codeWrapper?.style.cssText;
|
|
304
304
|
this.#codeOriginCssText = code.style.cssText;
|
|
305
305
|
this.#codeNumberOriginCssText = codeNumbers?.style.cssText;
|
|
306
306
|
this.#markdownWrapperOriginCssText = markdownWrapper?.style.cssText;
|
|
@@ -344,9 +344,11 @@ class Viewer {
|
|
|
344
344
|
wysiwygFrame.style.cssText = (wysiwygFrame.style.cssText.match(/\s?display(\s+)?:(\s+)?[a-zA-Z]+;/) || [''])[0] + this.#frameOptions.get('_defaultStyles').editor + (isCodeView || isMarkdownView ? 'display: none;' : '');
|
|
345
345
|
|
|
346
346
|
// code wrapper
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
347
|
+
if (codeWrapper) {
|
|
348
|
+
codeWrapper.style.cssText = (codeWrapper.style.cssText.match(/\s?display(\s+)?:(\s+)?[a-zA-Z]+;/) || [''])[0] + `display: ${!isCodeView ? 'none' : 'flex'} !important;`;
|
|
349
|
+
codeWrapper.style.overflow = 'auto';
|
|
350
|
+
codeWrapper.style.height = '100%';
|
|
351
|
+
}
|
|
350
352
|
|
|
351
353
|
// markdown wrapper
|
|
352
354
|
if (markdownWrapper) {
|
|
@@ -384,7 +386,9 @@ class Viewer {
|
|
|
384
386
|
wysiwygFrame.style.cssText = this.#wysiwygOriginCssText.replace(/\s?display(\s+)?:(\s+)?[a-zA-Z]+;/, '') + (isCodeView || isMarkdownView ? 'display: none;' : '');
|
|
385
387
|
|
|
386
388
|
// code wrapper
|
|
387
|
-
|
|
389
|
+
if (codeWrapper) {
|
|
390
|
+
codeWrapper.style.cssText = this.#codeWrapperOriginCssText.replace(/\s?display(\s+)?:(\s+)?[a-zA-Z]+;/, '') + `display: ${!isCodeView ? 'none' : 'flex'} !important;`;
|
|
391
|
+
}
|
|
388
392
|
|
|
389
393
|
// code
|
|
390
394
|
code.style.cssText = this.#codeOriginCssText;
|
|
@@ -38,6 +38,8 @@ import { get as getNumber } from '../../helper/numbers';
|
|
|
38
38
|
* @property {HTMLElement} navigation - Navigation element (e.g., for outline or bookmarks).
|
|
39
39
|
* @property {HTMLElement} charWrapper - Wrapper for the character counter element.
|
|
40
40
|
* @property {HTMLElement} charCounter - Element showing the character counter.
|
|
41
|
+
* @property {HTMLElement} wordWrapper - Wrapper for the word counter element.
|
|
42
|
+
* @property {HTMLElement} wordCounter - Element showing the word counter.
|
|
41
43
|
* @property {Window} [_ww] - The window object of the WYSIWYG frame (iframe window).
|
|
42
44
|
* @property {Document} [_wd] - The document object of the WYSIWYG frame (iframe document).
|
|
43
45
|
*
|
|
@@ -171,4 +173,8 @@ export function UpdateStatusbarContext(statusbar, mapper) {
|
|
|
171
173
|
navigation ? mapper.set('navigation', navigation) : mapper.delete('navigation');
|
|
172
174
|
charWrapper ? mapper.set('charWrapper', charWrapper) : mapper.delete('charWrapper');
|
|
173
175
|
charCounter ? mapper.set('charCounter', charCounter) : mapper.delete('charCounter');
|
|
176
|
+
const wordWrapper = statusbar ? statusbar.querySelector('.se-word-counter-wrapper') : null;
|
|
177
|
+
const wordCounter = statusbar ? statusbar.querySelector('.se-word-counter-wrapper .se-word-counter') : null;
|
|
178
|
+
wordWrapper ? mapper.set('wordWrapper', wordWrapper) : mapper.delete('wordWrapper');
|
|
179
|
+
wordCounter ? mapper.set('wordCounter', wordCounter) : mapper.delete('wordCounter');
|
|
174
180
|
}
|
|
@@ -150,6 +150,13 @@ export const DEFAULTS = {
|
|
|
150
150
|
* - `char`: Characters length.
|
|
151
151
|
* - `byte`: Binary data size of characters.
|
|
152
152
|
* - `byte-html`: Binary data size of the full HTML string.
|
|
153
|
+
*
|
|
154
|
+
* === Word Counter ===
|
|
155
|
+
* @property {boolean} [wordCounter=false] - Shows the number of words in the editor.
|
|
156
|
+
* @property {?string} [wordCounter_label=null] - Text to be displayed in the `wordCounter` area of the bottom bar.
|
|
157
|
+
* ```js
|
|
158
|
+
* { wordCounter_label: 'Words :' }
|
|
159
|
+
* ```
|
|
153
160
|
*/
|
|
154
161
|
|
|
155
162
|
/** ================================================================================================================================ */
|
|
@@ -681,6 +688,8 @@ export const OPTION_FRAME_FIXED_FLAG = {
|
|
|
681
688
|
charCounter_max: true,
|
|
682
689
|
charCounter_label: true,
|
|
683
690
|
charCounter_type: true,
|
|
691
|
+
wordCounter: true,
|
|
692
|
+
wordCounter_label: true,
|
|
684
693
|
};
|
|
685
694
|
|
|
686
695
|
/**
|
|
@@ -772,12 +772,14 @@ export function InitOptions(options, editorTargets, plugins) {
|
|
|
772
772
|
* @description Create a context object for the editor frame.
|
|
773
773
|
* @param {SunEditor.FrameOptions} targetOptions - `editor.frameOptions`
|
|
774
774
|
* @param {HTMLElement} statusbar - statusbar element
|
|
775
|
-
* @returns {{statusbar: HTMLElement, navigation: HTMLElement, charWrapper: HTMLElement, charCounter: HTMLElement}}
|
|
775
|
+
* @returns {{statusbar: HTMLElement, navigation: HTMLElement, charWrapper: HTMLElement, charCounter: HTMLElement, wordWrapper: HTMLElement, wordCounter: HTMLElement}}
|
|
776
776
|
*/
|
|
777
777
|
export function CreateStatusbar(targetOptions, statusbar) {
|
|
778
778
|
let navigation = null;
|
|
779
779
|
let charWrapper = null;
|
|
780
780
|
let charCounter = null;
|
|
781
|
+
let wordWrapper = null;
|
|
782
|
+
let wordCounter = null;
|
|
781
783
|
|
|
782
784
|
if (targetOptions.get('statusbar')) {
|
|
783
785
|
statusbar ||= dom.utils.createElement('DIV', { class: 'se-status-bar sun-editor-common' });
|
|
@@ -786,10 +788,31 @@ export function CreateStatusbar(targetOptions, statusbar) {
|
|
|
786
788
|
navigation = statusbar.querySelector('.se-navigation') || dom.utils.createElement('DIV', { class: 'se-navigation sun-editor-common' });
|
|
787
789
|
statusbar.appendChild(navigation);
|
|
788
790
|
|
|
789
|
-
/**
|
|
791
|
+
/** word counter (left) */
|
|
792
|
+
if (targetOptions.get('wordCounter')) {
|
|
793
|
+
wordWrapper = statusbar.querySelector('.se-word-counter-wrapper') || dom.utils.createElement('DIV', { class: 'se-word-counter-wrapper' });
|
|
794
|
+
|
|
795
|
+
if (targetOptions.get('wordCounter_label')) {
|
|
796
|
+
const wordLabel = wordWrapper.querySelector('.se-word-label') || dom.utils.createElement('SPAN', { class: 'se-word-label' });
|
|
797
|
+
wordLabel.textContent = targetOptions.get('wordCounter_label');
|
|
798
|
+
wordWrapper.appendChild(wordLabel);
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
wordCounter = wordWrapper.querySelector('.se-word-counter') || dom.utils.createElement('SPAN', { class: 'se-word-counter' });
|
|
802
|
+
wordCounter.textContent = '0';
|
|
803
|
+
wordWrapper.appendChild(wordCounter);
|
|
804
|
+
|
|
805
|
+
statusbar.appendChild(wordWrapper);
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
/** char counter (right) */
|
|
790
809
|
if (targetOptions.get('charCounter')) {
|
|
791
810
|
charWrapper = statusbar.querySelector('.se-char-counter-wrapper') || dom.utils.createElement('DIV', { class: 'se-char-counter-wrapper' });
|
|
792
811
|
|
|
812
|
+
if (targetOptions.get('wordCounter') && charWrapper.className.indexOf('se-with-word-counter') === -1) {
|
|
813
|
+
charWrapper.className += ' se-with-word-counter';
|
|
814
|
+
}
|
|
815
|
+
|
|
793
816
|
if (targetOptions.get('charCounter_label')) {
|
|
794
817
|
const charLabel = charWrapper.querySelector('.se-char-label') || dom.utils.createElement('SPAN', { class: 'se-char-label' });
|
|
795
818
|
charLabel.textContent = targetOptions.get('charCounter_label');
|
|
@@ -815,6 +838,8 @@ export function CreateStatusbar(targetOptions, statusbar) {
|
|
|
815
838
|
navigation: /** @type {HTMLElement} */ (navigation),
|
|
816
839
|
charWrapper: /** @type {HTMLElement} */ (charWrapper),
|
|
817
840
|
charCounter: /** @type {HTMLElement} */ (charCounter),
|
|
841
|
+
wordWrapper: /** @type {HTMLElement} */ (wordWrapper),
|
|
842
|
+
wordCounter: /** @type {HTMLElement} */ (wordCounter),
|
|
818
843
|
};
|
|
819
844
|
}
|
|
820
845
|
|
|
@@ -852,6 +877,8 @@ function InitFrameOptions(o, origin) {
|
|
|
852
877
|
const charCounter_max = barContainer || o.charCounter_max === undefined ? origin.charCounter_max : o.charCounter_max;
|
|
853
878
|
const charCounter_label = barContainer || o.charCounter_label === undefined ? origin.charCounter_label : o.charCounter_label;
|
|
854
879
|
const charCounter_type = barContainer || o.charCounter_type === undefined ? origin.charCounter_type : o.charCounter_type;
|
|
880
|
+
const wordCounter = barContainer || o.wordCounter === undefined ? origin.wordCounter : o.wordCounter;
|
|
881
|
+
const wordCounter_label = barContainer || o.wordCounter_label === undefined ? origin.wordCounter_label : o.wordCounter_label;
|
|
855
882
|
|
|
856
883
|
// value
|
|
857
884
|
fo.set('value', value);
|
|
@@ -881,6 +908,9 @@ function InitFrameOptions(o, origin) {
|
|
|
881
908
|
fo.set('charCounter_max', numbers.is(charCounter_max) && charCounter_max > -1 ? charCounter_max * 1 : null);
|
|
882
909
|
fo.set('charCounter_label', typeof charCounter_label === 'string' ? charCounter_label.trim() : null);
|
|
883
910
|
fo.set('charCounter_type', typeof charCounter_type === 'string' ? charCounter_type : 'char');
|
|
911
|
+
// status bar - word count
|
|
912
|
+
fo.set('wordCounter', typeof wordCounter === 'boolean' ? wordCounter : false);
|
|
913
|
+
fo.set('wordCounter_label', typeof wordCounter_label === 'string' ? wordCounter_label.trim() : null);
|
|
884
914
|
|
|
885
915
|
return fo;
|
|
886
916
|
}
|
package/src/helper/markdown.js
CHANGED
|
@@ -1,6 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @fileoverview Markdown converter module
|
|
3
3
|
* - Supports GitHub Flavored Markdown (GFM) syntax
|
|
4
|
+
*
|
|
5
|
+
* @description Limitations — Style loss during roundtrip
|
|
6
|
+
*
|
|
7
|
+
* Markdown syntax cannot represent HTML inline styles, classes, or data attributes.
|
|
8
|
+
* When switching between WYSIWYG and Markdown view, the following behavior applies:
|
|
9
|
+
*
|
|
10
|
+
* | Content type | Behavior | Example |
|
|
11
|
+
* |-------------------------------------|---------------------------------------------|------------------------------------------------------|
|
|
12
|
+
* | Media components (`div.se-component`) | Converted to markdown — styles are lost | Image alignment, data-se-* attrs |
|
|
13
|
+
* | Styled `<span>` elements | Preserved as raw HTML in markdown | Font color, background, custom classes |
|
|
14
|
+
* | Tables, figures | Converted to markdown — styles are lost | Table cell styles, figure width, colgroup widths |
|
|
15
|
+
* | General elements (p, h1, blockquote) | Converted to markdown — styles are lost | text-align, color, font-size on paragraphs/headings |
|
|
16
|
+
*
|
|
17
|
+
* This is a fundamental limitation of the Markdown format.
|
|
18
|
+
* To preserve all HTML attributes without loss, use Code View instead.
|
|
4
19
|
*/
|
|
5
20
|
|
|
6
21
|
const VOID_ELEMENTS = /^(area|base|br|col|embed|hr|img|input|link|meta|param|source|track|wbr)$/i;
|
|
@@ -439,16 +454,16 @@ function nodeToMarkdown(node, indent, isBlock) {
|
|
|
439
454
|
|
|
440
455
|
// Span - pass through as inline (may contain styles)
|
|
441
456
|
if (tag === 'span') {
|
|
442
|
-
// Check if it has meaningful attributes that need HTML fallback
|
|
443
457
|
if (attributes.style || attributes.class) {
|
|
444
458
|
return nodeToHtmlFallback(node);
|
|
445
459
|
}
|
|
446
460
|
return childrenToInline(children);
|
|
447
461
|
}
|
|
448
462
|
|
|
449
|
-
// Figure - process children (
|
|
463
|
+
// Figure - process children (table, media)
|
|
450
464
|
if (tag === 'figure') {
|
|
451
|
-
|
|
465
|
+
const inner = children.map((c) => nodeToMarkdown(c, indent, true)).join('');
|
|
466
|
+
return /\n$/.test(inner) ? inner : inner + '\n\n';
|
|
452
467
|
}
|
|
453
468
|
if (tag === 'figcaption') {
|
|
454
469
|
const content = childrenToInline(children).trim();
|
|
@@ -377,14 +377,13 @@ class Controller {
|
|
|
377
377
|
if (!passive) {
|
|
378
378
|
this.#$.ui.onControllerContext();
|
|
379
379
|
this.#$.store.set('controlActive', true);
|
|
380
|
+
this.#$.store.set('_preventBlur', true);
|
|
380
381
|
}
|
|
381
382
|
|
|
382
383
|
if (!this.isOpen) {
|
|
383
384
|
this.#$.ui.opendControllers.push(info);
|
|
384
385
|
}
|
|
385
386
|
|
|
386
|
-
this.#$.store.set('_preventBlur', true);
|
|
387
|
-
|
|
388
387
|
this.isOpen = true;
|
|
389
388
|
|
|
390
389
|
this.host.controllerOn?.(form, target);
|
|
@@ -904,7 +904,7 @@ class Figure {
|
|
|
904
904
|
retainFigureFormat(container, originEl, anchorCover, fileManagerInst) {
|
|
905
905
|
const isInline = this.#$.component.isInline(container);
|
|
906
906
|
const originParent = originEl.parentNode;
|
|
907
|
-
let existElement = this.#$.format.isBlock(originParent) || dom.check.isWysiwygFrame(originParent) ? originEl : Figure.GetContainer(originEl)?.container || originParent || originEl;
|
|
907
|
+
let existElement = this.#$.format.isBlock(originParent) || dom.check.isWysiwygFrame(originParent) || originParent.nodeType >= 9 ? originEl : Figure.GetContainer(originEl)?.container || originParent || originEl;
|
|
908
908
|
|
|
909
909
|
if (dom.query.getParentElement(originEl, dom.check.isExcludeFormat)) {
|
|
910
910
|
existElement = anchorCover && anchorCover !== originEl ? anchorCover : originEl;
|
|
@@ -66,7 +66,7 @@ class CodeBlock extends PluginCommand {
|
|
|
66
66
|
// ───────────────── [[toolbar dropdown type]] ─────────────────
|
|
67
67
|
this.afterItem = dom.utils.createElement(
|
|
68
68
|
'button',
|
|
69
|
-
{ class: 'se-btn se-tooltip se-sub-arrow-btn', 'data-command': CodeBlock.key, 'data-type': 'dropdown' },
|
|
69
|
+
{ class: 'se-btn se-tooltip se-sub-arrow-btn', type: 'button', 'data-command': CodeBlock.key, 'data-type': 'dropdown' },
|
|
70
70
|
`${this.$.icons.arrow_down}<span class="se-tooltip-inner"><span class="se-tooltip-text">${this.$.lang.codeLanguage || 'Language'}</span></span>`,
|
|
71
71
|
);
|
|
72
72
|
|
|
@@ -27,7 +27,7 @@ class List_bulleted extends PluginCommand {
|
|
|
27
27
|
this.icon = 'list_bulleted';
|
|
28
28
|
this.afterItem = dom.utils.createElement(
|
|
29
29
|
'button',
|
|
30
|
-
{ class: 'se-btn se-tooltip se-sub-arrow-btn', 'data-command': List_bulleted.key, 'data-type': 'dropdown' },
|
|
30
|
+
{ class: 'se-btn se-tooltip se-sub-arrow-btn', type: 'button', 'data-command': List_bulleted.key, 'data-type': 'dropdown' },
|
|
31
31
|
`${this.$.icons.arrow_down}<span class="se-tooltip-inner"><span class="se-tooltip-text">${this.$.lang.bulletedList}</span></span>`,
|
|
32
32
|
);
|
|
33
33
|
|
|
@@ -27,7 +27,7 @@ class List_numbered extends PluginCommand {
|
|
|
27
27
|
this.icon = 'list_numbered';
|
|
28
28
|
this.afterItem = dom.utils.createElement(
|
|
29
29
|
'button',
|
|
30
|
-
{ class: 'se-btn se-tooltip se-sub-arrow-btn', 'data-command': List_numbered.key, 'data-type': 'dropdown' },
|
|
30
|
+
{ class: 'se-btn se-tooltip se-sub-arrow-btn', type: 'button', 'data-command': List_numbered.key, 'data-type': 'dropdown' },
|
|
31
31
|
`${this.$.icons.arrow_down}<span class="se-tooltip-inner"><span class="se-tooltip-text">${this.$.lang.numberedList}</span></span>`,
|
|
32
32
|
);
|
|
33
33
|
|
|
@@ -152,25 +152,25 @@ class FontSize extends PluginInput {
|
|
|
152
152
|
if (showIncDec) {
|
|
153
153
|
this.beforeItem = dom.utils.createElement(
|
|
154
154
|
'button',
|
|
155
|
-
{ class: 'se-btn se-tooltip se-sub-btn', 'data-command': FontSize.key, 'data-type': 'command', 'data-value': 'dec' },
|
|
155
|
+
{ class: 'se-btn se-tooltip se-sub-btn', type: 'button', 'data-command': FontSize.key, 'data-type': 'command', 'data-value': 'dec' },
|
|
156
156
|
`${this.$.icons.minus}<span class="se-tooltip-inner"><span class="se-tooltip-text">${this.$.lang.decrease}</span></span>`,
|
|
157
157
|
);
|
|
158
158
|
this.afterItem = dom.utils.createElement(
|
|
159
159
|
'button',
|
|
160
|
-
{ class: 'se-btn se-tooltip se-sub-btn', 'data-command': FontSize.key, 'data-type': 'command', 'data-value': 'inc' },
|
|
160
|
+
{ class: 'se-btn se-tooltip se-sub-btn', type: 'button', 'data-command': FontSize.key, 'data-type': 'command', 'data-value': 'inc' },
|
|
161
161
|
`${this.$.icons.plus}<span class="se-tooltip-inner"><span class="se-tooltip-text">${this.$.lang.increase}</span></span>`,
|
|
162
162
|
);
|
|
163
163
|
} else if (!disableInput) {
|
|
164
164
|
this.afterItem = dom.utils.createElement(
|
|
165
165
|
'button',
|
|
166
|
-
{ class: 'se-btn se-tooltip se-sub-arrow-btn', 'data-command': FontSize.key, 'data-type': 'dropdown' },
|
|
166
|
+
{ class: 'se-btn se-tooltip se-sub-arrow-btn', type: 'button', 'data-command': FontSize.key, 'data-type': 'dropdown' },
|
|
167
167
|
`${this.$.icons.arrow_down}<span class="se-tooltip-inner"><span class="se-tooltip-text">${this.$.lang.fontSize}</span></span>`,
|
|
168
168
|
);
|
|
169
169
|
this.$.menu.initDropdownTarget({ key: FontSize.key, type: 'dropdown' }, menu);
|
|
170
170
|
} else if (disableInput && !showIncDec) {
|
|
171
171
|
this.replaceButton = dom.utils.createElement(
|
|
172
172
|
'button',
|
|
173
|
-
{ class: 'se-btn se-tooltip se-btn-select se-btn-tool-font-size', 'data-command': FontSize.key, 'data-type': 'dropdown' },
|
|
173
|
+
{ class: 'se-btn se-tooltip se-btn-select se-btn-tool-font-size', type: 'button', 'data-command': FontSize.key, 'data-type': 'dropdown' },
|
|
174
174
|
`<span class="se-txt __se__font_size">${this.$.lang.fontSize}</span>${this.$.icons.arrow_down}<span class="se-tooltip-inner"><span class="se-tooltip-text">${this.$.lang.fontSize}</span></span>`,
|
|
175
175
|
);
|
|
176
176
|
this.$.menu.initDropdownTarget({ key: FontSize.key, type: 'dropdown' }, menu);
|
|
@@ -36,7 +36,16 @@ declare class Char {
|
|
|
36
36
|
*/
|
|
37
37
|
getByteLength(text: string): number;
|
|
38
38
|
/**
|
|
39
|
-
* @description
|
|
39
|
+
* @description Get the number of words in the content.
|
|
40
|
+
* - If [content] is `undefined`, get the current editor's word count.
|
|
41
|
+
* @param {string} [content] Content to count. (default: wysiwyg textContent)
|
|
42
|
+
* @returns {number}
|
|
43
|
+
* const currentWords = editor.$.char.getWordCount();
|
|
44
|
+
* const textWords = editor.$.char.getWordCount('Hello World');
|
|
45
|
+
*/
|
|
46
|
+
getWordCount(content?: string): number;
|
|
47
|
+
/**
|
|
48
|
+
* @description Set the char count and word count to counter element textContent.
|
|
40
49
|
* @param {?SunEditor.FrameContext} [fc] Frame context
|
|
41
50
|
*/
|
|
42
51
|
display(fc?: SunEditor.FrameContext | null): void;
|
|
@@ -37,6 +37,8 @@ import type {} from '../../typedef';
|
|
|
37
37
|
* @property {HTMLElement} navigation - Navigation element (e.g., for outline or bookmarks).
|
|
38
38
|
* @property {HTMLElement} charWrapper - Wrapper for the character counter element.
|
|
39
39
|
* @property {HTMLElement} charCounter - Element showing the character counter.
|
|
40
|
+
* @property {HTMLElement} wordWrapper - Wrapper for the word counter element.
|
|
41
|
+
* @property {HTMLElement} wordCounter - Element showing the word counter.
|
|
40
42
|
* @property {Window} [_ww] - The window object of the WYSIWYG frame (iframe window).
|
|
41
43
|
* @property {Document} [_wd] - The document object of the WYSIWYG frame (iframe document).
|
|
42
44
|
*
|
|
@@ -215,6 +217,14 @@ export type FrameContextStore = {
|
|
|
215
217
|
* - Element showing the character counter.
|
|
216
218
|
*/
|
|
217
219
|
charCounter: HTMLElement;
|
|
220
|
+
/**
|
|
221
|
+
* - Wrapper for the word counter element.
|
|
222
|
+
*/
|
|
223
|
+
wordWrapper: HTMLElement;
|
|
224
|
+
/**
|
|
225
|
+
* - Element showing the word counter.
|
|
226
|
+
*/
|
|
227
|
+
wordCounter: HTMLElement;
|
|
218
228
|
/**
|
|
219
229
|
* - The window object of the WYSIWYG frame (iframe window).
|
|
220
230
|
*/
|
|
@@ -105,6 +105,13 @@ export namespace DEFAULTS {
|
|
|
105
105
|
* - `char`: Characters length.
|
|
106
106
|
* - `byte`: Binary data size of characters.
|
|
107
107
|
* - `byte-html`: Binary data size of the full HTML string.
|
|
108
|
+
*
|
|
109
|
+
* === Word Counter ===
|
|
110
|
+
* @property {boolean} [wordCounter=false] - Shows the number of words in the editor.
|
|
111
|
+
* @property {?string} [wordCounter_label=null] - Text to be displayed in the `wordCounter` area of the bottom bar.
|
|
112
|
+
* ```js
|
|
113
|
+
* { wordCounter_label: 'Words :' }
|
|
114
|
+
* ```
|
|
108
115
|
*/
|
|
109
116
|
/** ================================================================================================================================ */
|
|
110
117
|
/**
|
|
@@ -729,8 +736,21 @@ export type EditorFrameOptions = {
|
|
|
729
736
|
* - `char`: Characters length.
|
|
730
737
|
* - `byte`: Binary data size of characters.
|
|
731
738
|
* - `byte-html`: Binary data size of the full HTML string.
|
|
739
|
+
*
|
|
740
|
+
* === Word Counter ===
|
|
732
741
|
*/
|
|
733
742
|
charCounter_type?: 'char' | 'byte' | 'byte-html';
|
|
743
|
+
/**
|
|
744
|
+
* - Shows the number of words in the editor.
|
|
745
|
+
*/
|
|
746
|
+
wordCounter?: boolean;
|
|
747
|
+
/**
|
|
748
|
+
* - Text to be displayed in the `wordCounter` area of the bottom bar.
|
|
749
|
+
* ```js
|
|
750
|
+
* { wordCounter_label: 'Words :' }
|
|
751
|
+
* ```
|
|
752
|
+
*/
|
|
753
|
+
wordCounter_label?: string | null;
|
|
734
754
|
};
|
|
735
755
|
export type OptionStyleResult = {
|
|
736
756
|
/**
|
|
@@ -42,7 +42,7 @@ export function InitOptions(
|
|
|
42
42
|
* @description Create a context object for the editor frame.
|
|
43
43
|
* @param {SunEditor.FrameOptions} targetOptions - `editor.frameOptions`
|
|
44
44
|
* @param {HTMLElement} statusbar - statusbar element
|
|
45
|
-
* @returns {{statusbar: HTMLElement, navigation: HTMLElement, charWrapper: HTMLElement, charCounter: HTMLElement}}
|
|
45
|
+
* @returns {{statusbar: HTMLElement, navigation: HTMLElement, charWrapper: HTMLElement, charCounter: HTMLElement, wordWrapper: HTMLElement, wordCounter: HTMLElement}}
|
|
46
46
|
*/
|
|
47
47
|
export function CreateStatusbar(
|
|
48
48
|
targetOptions: SunEditor.FrameOptions,
|
|
@@ -52,6 +52,8 @@ export function CreateStatusbar(
|
|
|
52
52
|
navigation: HTMLElement;
|
|
53
53
|
charWrapper: HTMLElement;
|
|
54
54
|
charCounter: HTMLElement;
|
|
55
|
+
wordWrapper: HTMLElement;
|
|
56
|
+
wordCounter: HTMLElement;
|
|
55
57
|
};
|
|
56
58
|
/**
|
|
57
59
|
* @description Update a button state, attributes, and icons
|