561 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			561 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /**
 | |
|  * TinyMCE version 6.8.4 (2024-06-19)
 | |
|  */
 | |
| 
 | |
| (function () {
 | |
|     'use strict';
 | |
| 
 | |
|     const Cell = initial => {
 | |
|       let value = initial;
 | |
|       const get = () => {
 | |
|         return value;
 | |
|       };
 | |
|       const set = v => {
 | |
|         value = v;
 | |
|       };
 | |
|       return {
 | |
|         get,
 | |
|         set
 | |
|       };
 | |
|     };
 | |
| 
 | |
|     var global = tinymce.util.Tools.resolve('tinymce.PluginManager');
 | |
| 
 | |
|     const get$2 = toggleState => {
 | |
|       const isEnabled = () => {
 | |
|         return toggleState.get();
 | |
|       };
 | |
|       return { isEnabled };
 | |
|     };
 | |
| 
 | |
|     const fireVisualChars = (editor, state) => {
 | |
|       return editor.dispatch('VisualChars', { state });
 | |
|     };
 | |
| 
 | |
|     const hasProto = (v, constructor, predicate) => {
 | |
|       var _a;
 | |
|       if (predicate(v, constructor.prototype)) {
 | |
|         return true;
 | |
|       } else {
 | |
|         return ((_a = v.constructor) === null || _a === void 0 ? void 0 : _a.name) === constructor.name;
 | |
|       }
 | |
|     };
 | |
|     const typeOf = x => {
 | |
|       const t = typeof x;
 | |
|       if (x === null) {
 | |
|         return 'null';
 | |
|       } else if (t === 'object' && Array.isArray(x)) {
 | |
|         return 'array';
 | |
|       } else if (t === 'object' && hasProto(x, String, (o, proto) => proto.isPrototypeOf(o))) {
 | |
|         return 'string';
 | |
|       } else {
 | |
|         return t;
 | |
|       }
 | |
|     };
 | |
|     const isType$1 = type => value => typeOf(value) === type;
 | |
|     const isSimpleType = type => value => typeof value === type;
 | |
|     const eq = t => a => t === a;
 | |
|     const isString = isType$1('string');
 | |
|     const isObject = isType$1('object');
 | |
|     const isNull = eq(null);
 | |
|     const isBoolean = isSimpleType('boolean');
 | |
|     const isNullable = a => a === null || a === undefined;
 | |
|     const isNonNullable = a => !isNullable(a);
 | |
|     const isNumber = isSimpleType('number');
 | |
| 
 | |
|     class Optional {
 | |
|       constructor(tag, value) {
 | |
|         this.tag = tag;
 | |
|         this.value = value;
 | |
|       }
 | |
|       static some(value) {
 | |
|         return new Optional(true, value);
 | |
|       }
 | |
|       static none() {
 | |
|         return Optional.singletonNone;
 | |
|       }
 | |
|       fold(onNone, onSome) {
 | |
|         if (this.tag) {
 | |
|           return onSome(this.value);
 | |
|         } else {
 | |
|           return onNone();
 | |
|         }
 | |
|       }
 | |
|       isSome() {
 | |
|         return this.tag;
 | |
|       }
 | |
|       isNone() {
 | |
|         return !this.tag;
 | |
|       }
 | |
|       map(mapper) {
 | |
|         if (this.tag) {
 | |
|           return Optional.some(mapper(this.value));
 | |
|         } else {
 | |
|           return Optional.none();
 | |
|         }
 | |
|       }
 | |
|       bind(binder) {
 | |
|         if (this.tag) {
 | |
|           return binder(this.value);
 | |
|         } else {
 | |
|           return Optional.none();
 | |
|         }
 | |
|       }
 | |
|       exists(predicate) {
 | |
|         return this.tag && predicate(this.value);
 | |
|       }
 | |
|       forall(predicate) {
 | |
|         return !this.tag || predicate(this.value);
 | |
|       }
 | |
|       filter(predicate) {
 | |
|         if (!this.tag || predicate(this.value)) {
 | |
|           return this;
 | |
|         } else {
 | |
|           return Optional.none();
 | |
|         }
 | |
|       }
 | |
|       getOr(replacement) {
 | |
|         return this.tag ? this.value : replacement;
 | |
|       }
 | |
|       or(replacement) {
 | |
|         return this.tag ? this : replacement;
 | |
|       }
 | |
|       getOrThunk(thunk) {
 | |
|         return this.tag ? this.value : thunk();
 | |
|       }
 | |
|       orThunk(thunk) {
 | |
|         return this.tag ? this : thunk();
 | |
|       }
 | |
|       getOrDie(message) {
 | |
|         if (!this.tag) {
 | |
|           throw new Error(message !== null && message !== void 0 ? message : 'Called getOrDie on None');
 | |
|         } else {
 | |
|           return this.value;
 | |
|         }
 | |
|       }
 | |
|       static from(value) {
 | |
|         return isNonNullable(value) ? Optional.some(value) : Optional.none();
 | |
|       }
 | |
|       getOrNull() {
 | |
|         return this.tag ? this.value : null;
 | |
|       }
 | |
|       getOrUndefined() {
 | |
|         return this.value;
 | |
|       }
 | |
|       each(worker) {
 | |
|         if (this.tag) {
 | |
|           worker(this.value);
 | |
|         }
 | |
|       }
 | |
|       toArray() {
 | |
|         return this.tag ? [this.value] : [];
 | |
|       }
 | |
|       toString() {
 | |
|         return this.tag ? `some(${ this.value })` : 'none()';
 | |
|       }
 | |
|     }
 | |
|     Optional.singletonNone = new Optional(false);
 | |
| 
 | |
|     const map = (xs, f) => {
 | |
|       const len = xs.length;
 | |
|       const r = new Array(len);
 | |
|       for (let i = 0; i < len; i++) {
 | |
|         const x = xs[i];
 | |
|         r[i] = f(x, i);
 | |
|       }
 | |
|       return r;
 | |
|     };
 | |
|     const each$1 = (xs, f) => {
 | |
|       for (let i = 0, len = xs.length; i < len; i++) {
 | |
|         const x = xs[i];
 | |
|         f(x, i);
 | |
|       }
 | |
|     };
 | |
|     const filter = (xs, pred) => {
 | |
|       const r = [];
 | |
|       for (let i = 0, len = xs.length; i < len; i++) {
 | |
|         const x = xs[i];
 | |
|         if (pred(x, i)) {
 | |
|           r.push(x);
 | |
|         }
 | |
|       }
 | |
|       return r;
 | |
|     };
 | |
| 
 | |
|     const keys = Object.keys;
 | |
|     const each = (obj, f) => {
 | |
|       const props = keys(obj);
 | |
|       for (let k = 0, len = props.length; k < len; k++) {
 | |
|         const i = props[k];
 | |
|         const x = obj[i];
 | |
|         f(x, i);
 | |
|       }
 | |
|     };
 | |
| 
 | |
|     const Global = typeof window !== 'undefined' ? window : Function('return this;')();
 | |
| 
 | |
|     const path = (parts, scope) => {
 | |
|       let o = scope !== undefined && scope !== null ? scope : Global;
 | |
|       for (let i = 0; i < parts.length && o !== undefined && o !== null; ++i) {
 | |
|         o = o[parts[i]];
 | |
|       }
 | |
|       return o;
 | |
|     };
 | |
|     const resolve = (p, scope) => {
 | |
|       const parts = p.split('.');
 | |
|       return path(parts, scope);
 | |
|     };
 | |
| 
 | |
|     const unsafe = (name, scope) => {
 | |
|       return resolve(name, scope);
 | |
|     };
 | |
|     const getOrDie = (name, scope) => {
 | |
|       const actual = unsafe(name, scope);
 | |
|       if (actual === undefined || actual === null) {
 | |
|         throw new Error(name + ' not available on this browser');
 | |
|       }
 | |
|       return actual;
 | |
|     };
 | |
| 
 | |
|     const getPrototypeOf = Object.getPrototypeOf;
 | |
|     const sandHTMLElement = scope => {
 | |
|       return getOrDie('HTMLElement', scope);
 | |
|     };
 | |
|     const isPrototypeOf = x => {
 | |
|       const scope = resolve('ownerDocument.defaultView', x);
 | |
|       return isObject(x) && (sandHTMLElement(scope).prototype.isPrototypeOf(x) || /^HTML\w*Element$/.test(getPrototypeOf(x).constructor.name));
 | |
|     };
 | |
| 
 | |
|     const ELEMENT = 1;
 | |
|     const TEXT = 3;
 | |
| 
 | |
|     const type = element => element.dom.nodeType;
 | |
|     const value = element => element.dom.nodeValue;
 | |
|     const isType = t => element => type(element) === t;
 | |
|     const isHTMLElement = element => isElement(element) && isPrototypeOf(element.dom);
 | |
|     const isElement = isType(ELEMENT);
 | |
|     const isText = isType(TEXT);
 | |
| 
 | |
|     const rawSet = (dom, key, value) => {
 | |
|       if (isString(value) || isBoolean(value) || isNumber(value)) {
 | |
|         dom.setAttribute(key, value + '');
 | |
|       } else {
 | |
|         console.error('Invalid call to Attribute.set. Key ', key, ':: Value ', value, ':: Element ', dom);
 | |
|         throw new Error('Attribute value was not simple');
 | |
|       }
 | |
|     };
 | |
|     const set = (element, key, value) => {
 | |
|       rawSet(element.dom, key, value);
 | |
|     };
 | |
|     const get$1 = (element, key) => {
 | |
|       const v = element.dom.getAttribute(key);
 | |
|       return v === null ? undefined : v;
 | |
|     };
 | |
|     const remove$3 = (element, key) => {
 | |
|       element.dom.removeAttribute(key);
 | |
|     };
 | |
| 
 | |
|     const read = (element, attr) => {
 | |
|       const value = get$1(element, attr);
 | |
|       return value === undefined || value === '' ? [] : value.split(' ');
 | |
|     };
 | |
|     const add$2 = (element, attr, id) => {
 | |
|       const old = read(element, attr);
 | |
|       const nu = old.concat([id]);
 | |
|       set(element, attr, nu.join(' '));
 | |
|       return true;
 | |
|     };
 | |
|     const remove$2 = (element, attr, id) => {
 | |
|       const nu = filter(read(element, attr), v => v !== id);
 | |
|       if (nu.length > 0) {
 | |
|         set(element, attr, nu.join(' '));
 | |
|       } else {
 | |
|         remove$3(element, attr);
 | |
|       }
 | |
|       return false;
 | |
|     };
 | |
| 
 | |
|     const supports = element => element.dom.classList !== undefined;
 | |
|     const get = element => read(element, 'class');
 | |
|     const add$1 = (element, clazz) => add$2(element, 'class', clazz);
 | |
|     const remove$1 = (element, clazz) => remove$2(element, 'class', clazz);
 | |
| 
 | |
|     const add = (element, clazz) => {
 | |
|       if (supports(element)) {
 | |
|         element.dom.classList.add(clazz);
 | |
|       } else {
 | |
|         add$1(element, clazz);
 | |
|       }
 | |
|     };
 | |
|     const cleanClass = element => {
 | |
|       const classList = supports(element) ? element.dom.classList : get(element);
 | |
|       if (classList.length === 0) {
 | |
|         remove$3(element, 'class');
 | |
|       }
 | |
|     };
 | |
|     const remove = (element, clazz) => {
 | |
|       if (supports(element)) {
 | |
|         const classList = element.dom.classList;
 | |
|         classList.remove(clazz);
 | |
|       } else {
 | |
|         remove$1(element, clazz);
 | |
|       }
 | |
|       cleanClass(element);
 | |
|     };
 | |
| 
 | |
|     const fromHtml = (html, scope) => {
 | |
|       const doc = scope || document;
 | |
|       const div = doc.createElement('div');
 | |
|       div.innerHTML = html;
 | |
|       if (!div.hasChildNodes() || div.childNodes.length > 1) {
 | |
|         const message = 'HTML does not have a single root node';
 | |
|         console.error(message, html);
 | |
|         throw new Error(message);
 | |
|       }
 | |
|       return fromDom(div.childNodes[0]);
 | |
|     };
 | |
|     const fromTag = (tag, scope) => {
 | |
|       const doc = scope || document;
 | |
|       const node = doc.createElement(tag);
 | |
|       return fromDom(node);
 | |
|     };
 | |
|     const fromText = (text, scope) => {
 | |
|       const doc = scope || document;
 | |
|       const node = doc.createTextNode(text);
 | |
|       return fromDom(node);
 | |
|     };
 | |
|     const fromDom = node => {
 | |
|       if (node === null || node === undefined) {
 | |
|         throw new Error('Node cannot be null or undefined');
 | |
|       }
 | |
|       return { dom: node };
 | |
|     };
 | |
|     const fromPoint = (docElm, x, y) => Optional.from(docElm.dom.elementFromPoint(x, y)).map(fromDom);
 | |
|     const SugarElement = {
 | |
|       fromHtml,
 | |
|       fromTag,
 | |
|       fromText,
 | |
|       fromDom,
 | |
|       fromPoint
 | |
|     };
 | |
| 
 | |
|     const charMap = {
 | |
|       '\xA0': 'nbsp',
 | |
|       '\xAD': 'shy'
 | |
|     };
 | |
|     const charMapToRegExp = (charMap, global) => {
 | |
|       let regExp = '';
 | |
|       each(charMap, (_value, key) => {
 | |
|         regExp += key;
 | |
|       });
 | |
|       return new RegExp('[' + regExp + ']', global ? 'g' : '');
 | |
|     };
 | |
|     const charMapToSelector = charMap => {
 | |
|       let selector = '';
 | |
|       each(charMap, value => {
 | |
|         if (selector) {
 | |
|           selector += ',';
 | |
|         }
 | |
|         selector += 'span.mce-' + value;
 | |
|       });
 | |
|       return selector;
 | |
|     };
 | |
|     const regExp = charMapToRegExp(charMap);
 | |
|     const regExpGlobal = charMapToRegExp(charMap, true);
 | |
|     const selector = charMapToSelector(charMap);
 | |
|     const nbspClass = 'mce-nbsp';
 | |
| 
 | |
|     const getRaw = element => element.dom.contentEditable;
 | |
| 
 | |
|     const wrapCharWithSpan = value => '<span data-mce-bogus="1" class="mce-' + charMap[value] + '">' + value + '</span>';
 | |
| 
 | |
|     const isWrappedNbsp = node => node.nodeName.toLowerCase() === 'span' && node.classList.contains('mce-nbsp-wrap');
 | |
|     const isMatch = n => {
 | |
|       const value$1 = value(n);
 | |
|       return isText(n) && isString(value$1) && regExp.test(value$1);
 | |
|     };
 | |
|     const isContentEditableFalse = node => isHTMLElement(node) && getRaw(node) === 'false';
 | |
|     const isChildEditable = (node, currentState) => {
 | |
|       if (isHTMLElement(node) && !isWrappedNbsp(node.dom)) {
 | |
|         const value = getRaw(node);
 | |
|         if (value === 'true') {
 | |
|           return true;
 | |
|         } else if (value === 'false') {
 | |
|           return false;
 | |
|         }
 | |
|       }
 | |
|       return currentState;
 | |
|     };
 | |
|     const filterEditableDescendants = (scope, predicate, editable) => {
 | |
|       let result = [];
 | |
|       const dom = scope.dom;
 | |
|       const children = map(dom.childNodes, SugarElement.fromDom);
 | |
|       const isEditable = node => isWrappedNbsp(node.dom) || !isContentEditableFalse(node);
 | |
|       each$1(children, x => {
 | |
|         if (editable && isEditable(x) && predicate(x)) {
 | |
|           result = result.concat([x]);
 | |
|         }
 | |
|         result = result.concat(filterEditableDescendants(x, predicate, isChildEditable(x, editable)));
 | |
|       });
 | |
|       return result;
 | |
|     };
 | |
|     const findParentElm = (elm, rootElm) => {
 | |
|       while (elm.parentNode) {
 | |
|         if (elm.parentNode === rootElm) {
 | |
|           return rootElm;
 | |
|         }
 | |
|         elm = elm.parentNode;
 | |
|       }
 | |
|       return undefined;
 | |
|     };
 | |
|     const replaceWithSpans = text => text.replace(regExpGlobal, wrapCharWithSpan);
 | |
| 
 | |
|     const show = (editor, rootElm) => {
 | |
|       const dom = editor.dom;
 | |
|       const nodeList = filterEditableDescendants(SugarElement.fromDom(rootElm), isMatch, editor.dom.isEditable(rootElm));
 | |
|       each$1(nodeList, n => {
 | |
|         var _a;
 | |
|         const parent = n.dom.parentNode;
 | |
|         if (isWrappedNbsp(parent)) {
 | |
|           add(SugarElement.fromDom(parent), nbspClass);
 | |
|         } else {
 | |
|           const withSpans = replaceWithSpans(dom.encode((_a = value(n)) !== null && _a !== void 0 ? _a : ''));
 | |
|           const div = dom.create('div', {}, withSpans);
 | |
|           let node;
 | |
|           while (node = div.lastChild) {
 | |
|             dom.insertAfter(node, n.dom);
 | |
|           }
 | |
|           editor.dom.remove(n.dom);
 | |
|         }
 | |
|       });
 | |
|     };
 | |
|     const hide = (editor, rootElm) => {
 | |
|       const nodeList = editor.dom.select(selector, rootElm);
 | |
|       each$1(nodeList, node => {
 | |
|         if (isWrappedNbsp(node)) {
 | |
|           remove(SugarElement.fromDom(node), nbspClass);
 | |
|         } else {
 | |
|           editor.dom.remove(node, true);
 | |
|         }
 | |
|       });
 | |
|     };
 | |
|     const toggle = editor => {
 | |
|       const body = editor.getBody();
 | |
|       const bookmark = editor.selection.getBookmark();
 | |
|       let parentNode = findParentElm(editor.selection.getNode(), body);
 | |
|       parentNode = parentNode !== undefined ? parentNode : body;
 | |
|       hide(editor, parentNode);
 | |
|       show(editor, parentNode);
 | |
|       editor.selection.moveToBookmark(bookmark);
 | |
|     };
 | |
| 
 | |
|     const applyVisualChars = (editor, toggleState) => {
 | |
|       fireVisualChars(editor, toggleState.get());
 | |
|       const body = editor.getBody();
 | |
|       if (toggleState.get() === true) {
 | |
|         show(editor, body);
 | |
|       } else {
 | |
|         hide(editor, body);
 | |
|       }
 | |
|     };
 | |
|     const toggleVisualChars = (editor, toggleState) => {
 | |
|       toggleState.set(!toggleState.get());
 | |
|       const bookmark = editor.selection.getBookmark();
 | |
|       applyVisualChars(editor, toggleState);
 | |
|       editor.selection.moveToBookmark(bookmark);
 | |
|     };
 | |
| 
 | |
|     const register$2 = (editor, toggleState) => {
 | |
|       editor.addCommand('mceVisualChars', () => {
 | |
|         toggleVisualChars(editor, toggleState);
 | |
|       });
 | |
|     };
 | |
| 
 | |
|     const option = name => editor => editor.options.get(name);
 | |
|     const register$1 = editor => {
 | |
|       const registerOption = editor.options.register;
 | |
|       registerOption('visualchars_default_state', {
 | |
|         processor: 'boolean',
 | |
|         default: false
 | |
|       });
 | |
|     };
 | |
|     const isEnabledByDefault = option('visualchars_default_state');
 | |
| 
 | |
|     const setup$1 = (editor, toggleState) => {
 | |
|       editor.on('init', () => {
 | |
|         applyVisualChars(editor, toggleState);
 | |
|       });
 | |
|     };
 | |
| 
 | |
|     const first = (fn, rate) => {
 | |
|       let timer = null;
 | |
|       const cancel = () => {
 | |
|         if (!isNull(timer)) {
 | |
|           clearTimeout(timer);
 | |
|           timer = null;
 | |
|         }
 | |
|       };
 | |
|       const throttle = (...args) => {
 | |
|         if (isNull(timer)) {
 | |
|           timer = setTimeout(() => {
 | |
|             timer = null;
 | |
|             fn.apply(null, args);
 | |
|           }, rate);
 | |
|         }
 | |
|       };
 | |
|       return {
 | |
|         cancel,
 | |
|         throttle
 | |
|       };
 | |
|     };
 | |
| 
 | |
|     const setup = (editor, toggleState) => {
 | |
|       const debouncedToggle = first(() => {
 | |
|         toggle(editor);
 | |
|       }, 300);
 | |
|       editor.on('keydown', e => {
 | |
|         if (toggleState.get() === true) {
 | |
|           e.keyCode === 13 ? toggle(editor) : debouncedToggle.throttle();
 | |
|         }
 | |
|       });
 | |
|       editor.on('remove', debouncedToggle.cancel);
 | |
|     };
 | |
| 
 | |
|     const toggleActiveState = (editor, enabledStated) => api => {
 | |
|       api.setActive(enabledStated.get());
 | |
|       const editorEventCallback = e => api.setActive(e.state);
 | |
|       editor.on('VisualChars', editorEventCallback);
 | |
|       return () => editor.off('VisualChars', editorEventCallback);
 | |
|     };
 | |
|     const register = (editor, toggleState) => {
 | |
|       const onAction = () => editor.execCommand('mceVisualChars');
 | |
|       editor.ui.registry.addToggleButton('visualchars', {
 | |
|         tooltip: 'Show invisible characters',
 | |
|         icon: 'visualchars',
 | |
|         onAction,
 | |
|         onSetup: toggleActiveState(editor, toggleState)
 | |
|       });
 | |
|       editor.ui.registry.addToggleMenuItem('visualchars', {
 | |
|         text: 'Show invisible characters',
 | |
|         icon: 'visualchars',
 | |
|         onAction,
 | |
|         onSetup: toggleActiveState(editor, toggleState)
 | |
|       });
 | |
|     };
 | |
| 
 | |
|     var Plugin = () => {
 | |
|       global.add('visualchars', editor => {
 | |
|         register$1(editor);
 | |
|         const toggleState = Cell(isEnabledByDefault(editor));
 | |
|         register$2(editor, toggleState);
 | |
|         register(editor, toggleState);
 | |
|         setup(editor, toggleState);
 | |
|         setup$1(editor, toggleState);
 | |
|         return get$2(toggleState);
 | |
|       });
 | |
|     };
 | |
| 
 | |
|     Plugin();
 | |
| 
 | |
| })();
 |