This commit is contained in:
2025-10-22 15:39:40 +08:00
commit b0b510fac1
2720 changed files with 415933 additions and 0 deletions

View File

@@ -0,0 +1,27 @@
/**
* Copyright (c) Tiny Technologies, Inc. All rights reserved.
* Licensed under the LGPL or a commercial license.
* For LGPL see License.txt in the project root for license information.
* For commercial licenses see https://www.tiny.cloud/
*/
import { Cell } from '@ephox/katamari';
import PluginManager from 'tinymce/core/api/PluginManager';
import Api from './api/Api';
import Commands from './api/Commands';
import Keyboard from './core/Keyboard';
import Bindings from './core/Bindings';
import * as Buttons from './ui/Buttons';
PluginManager.add('visualchars', function (editor) {
const toggleState = Cell(false);
Commands.register(editor, toggleState);
Buttons.register(editor);
Keyboard.setup(editor, toggleState);
Bindings.setup(editor, toggleState);
return Api.get(toggleState);
});
export default function () {}

View File

@@ -0,0 +1,20 @@
/**
* Copyright (c) Tiny Technologies, Inc. All rights reserved.
* Licensed under the LGPL or a commercial license.
* For LGPL see License.txt in the project root for license information.
* For commercial licenses see https://www.tiny.cloud/
*/
const get = function (toggleState) {
const isEnabled = function () {
return toggleState.get();
};
return {
isEnabled
};
};
export default {
get
};

View File

@@ -0,0 +1,18 @@
/**
* Copyright (c) Tiny Technologies, Inc. All rights reserved.
* Licensed under the LGPL or a commercial license.
* For LGPL see License.txt in the project root for license information.
* For commercial licenses see https://www.tiny.cloud/
*/
import Actions from '../core/Actions';
const register = function (editor, toggleState) {
editor.addCommand('mceVisualChars', function () {
Actions.toggleVisualChars(editor, toggleState);
});
};
export default {
register
};

View File

@@ -0,0 +1,14 @@
/**
* Copyright (c) Tiny Technologies, Inc. All rights reserved.
* Licensed under the LGPL or a commercial license.
* For LGPL see License.txt in the project root for license information.
* For commercial licenses see https://www.tiny.cloud/
*/
const fireVisualChars = function (editor, state) {
return editor.fire('VisualChars', { state });
};
export default {
fireVisualChars
};

View File

@@ -0,0 +1,16 @@
/**
* Copyright (c) Tiny Technologies, Inc. All rights reserved.
* Licensed under the LGPL or a commercial license.
* For LGPL see License.txt in the project root for license information.
* For commercial licenses see https://www.tiny.cloud/
*/
import { Editor } from 'tinymce/core/api/Editor';
const isEnabledByDefault = (editor: Editor) => {
return editor.getParam('visualchars_default_state', false);
};
export default {
isEnabledByDefault
};

View File

@@ -0,0 +1,32 @@
/**
* Copyright (c) Tiny Technologies, Inc. All rights reserved.
* Licensed under the LGPL or a commercial license.
* For LGPL see License.txt in the project root for license information.
* For commercial licenses see https://www.tiny.cloud/
*/
import Events from '../api/Events';
import VisualChars from './VisualChars';
const toggleVisualChars = function (editor, toggleState) {
const body = editor.getBody();
const selection = editor.selection;
let bookmark;
toggleState.set(!toggleState.get());
Events.fireVisualChars(editor, toggleState.get());
bookmark = selection.getBookmark();
if (toggleState.get() === true) {
VisualChars.show(editor, body);
} else {
VisualChars.hide(editor, body);
}
selection.moveToBookmark(bookmark);
};
export default {
toggleVisualChars
};

View File

@@ -0,0 +1,23 @@
/**
* Copyright (c) Tiny Technologies, Inc. All rights reserved.
* Licensed under the LGPL or a commercial license.
* For LGPL see License.txt in the project root for license information.
* For commercial licenses see https://www.tiny.cloud/
*/
import { Editor } from 'tinymce/core/api/Editor';
import Settings from '../api/Settings';
import Actions from './Actions';
const setup = (editor: Editor, toggleState) => {
editor.on('init', () => {
// should be false when enabled, so toggling will change it to true
const valueForToggling = !Settings.isEnabledByDefault(editor);
toggleState.set(valueForToggling);
Actions.toggleVisualChars(editor, toggleState);
});
};
export default {
setup
};

View File

@@ -0,0 +1,43 @@
/**
* Copyright (c) Tiny Technologies, Inc. All rights reserved.
* Licensed under the LGPL or a commercial license.
* For LGPL see License.txt in the project root for license information.
* For commercial licenses see https://www.tiny.cloud/
*/
const charMap = {
'\u00a0': 'nbsp',
'\u00ad': 'shy'
};
const charMapToRegExp = function (charMap, global?) {
let key, regExp = '';
for (key in charMap) {
regExp += key;
}
return new RegExp('[' + regExp + ']', global ? 'g' : '');
};
const charMapToSelector = function (charMap) {
let key, selector = '';
for (key in charMap) {
if (selector) {
selector += ',';
}
selector += 'span.mce-' + charMap[key];
}
return selector;
};
export default {
charMap,
regExp: charMapToRegExp(charMap),
regExpGlobal: charMapToRegExp(charMap, true),
selector: charMapToSelector(charMap),
charMapToRegExp,
charMapToSelector
};

View File

@@ -0,0 +1,16 @@
/**
* Copyright (c) Tiny Technologies, Inc. All rights reserved.
* Licensed under the LGPL or a commercial license.
* For LGPL see License.txt in the project root for license information.
* For commercial licenses see https://www.tiny.cloud/
*/
import Data from './Data';
const wrapCharWithSpan = function (value) {
return '<span data-mce-bogus="1" class="mce-' + Data.charMap[value] + '">' + value + '</span>';
};
export default {
wrapCharWithSpan
};

View File

@@ -0,0 +1,27 @@
/**
* Copyright (c) Tiny Technologies, Inc. All rights reserved.
* Licensed under the LGPL or a commercial license.
* For LGPL see License.txt in the project root for license information.
* For commercial licenses see https://www.tiny.cloud/
*/
import Delay from 'tinymce/core/api/util/Delay';
import VisualChars from './VisualChars';
const setup = function (editor, toggleState) {
const debouncedToggle = Delay.debounce(function () {
VisualChars.toggle(editor);
}, 300);
if (editor.settings.forced_root_block !== false) {
editor.on('keydown', function (e) {
if (toggleState.get() === true) {
e.keyCode === 13 ? VisualChars.toggle(editor) : debouncedToggle();
}
});
}
};
export default {
setup
};

View File

@@ -0,0 +1,52 @@
/**
* Copyright (c) Tiny Technologies, Inc. All rights reserved.
* Licensed under the LGPL or a commercial license.
* For LGPL see License.txt in the project root for license information.
* For commercial licenses see https://www.tiny.cloud/
*/
import { Arr } from '@ephox/katamari';
import { Element, Node } from '@ephox/sugar';
import Data from './Data';
import Html from './Html';
const isMatch = function (n) {
return Node.isText(n) &&
Node.value(n) !== undefined &&
Data.regExp.test(Node.value(n));
};
// inlined sugars PredicateFilter.descendants for file size
const filterDescendants = function (scope, predicate) {
let result = [];
const dom = scope.dom();
const children = Arr.map(dom.childNodes, Element.fromDom);
Arr.each(children, function (x) {
if (predicate(x)) {
result = result.concat([ x ]);
}
result = result.concat(filterDescendants(x, predicate));
});
return result;
};
const findParentElm = function (elm, rootElm) {
while (elm.parentNode) {
if (elm.parentNode === rootElm) {
return elm;
}
elm = elm.parentNode;
}
};
const replaceWithSpans = function (html) {
return html.replace(Data.regExpGlobal, Html.wrapCharWithSpan);
};
export default {
isMatch,
filterDescendants,
findParentElm,
replaceWithSpans
};

View File

@@ -0,0 +1,55 @@
/**
* Copyright (c) Tiny Technologies, Inc. All rights reserved.
* Licensed under the LGPL or a commercial license.
* For LGPL see License.txt in the project root for license information.
* For commercial licenses see https://www.tiny.cloud/
*/
import Data from './Data';
import Nodes from './Nodes';
import { Arr } from '@ephox/katamari';
import { Element, Node } from '@ephox/sugar';
const show = function (editor, rootElm) {
let node, div;
const nodeList = Nodes.filterDescendants(Element.fromDom(rootElm), Nodes.isMatch);
Arr.each(nodeList, function (n) {
const withSpans = Nodes.replaceWithSpans(Node.value(n));
div = editor.dom.create('div', null, withSpans);
while ((node = div.lastChild)) {
editor.dom.insertAfter(node, n.dom());
}
editor.dom.remove(n.dom());
});
};
const hide = function (editor, body) {
const nodeList = editor.dom.select(Data.selector, body);
Arr.each(nodeList, function (node) {
editor.dom.remove(node, 1);
});
};
const toggle = function (editor) {
const body = editor.getBody();
const bookmark = editor.selection.getBookmark();
let parentNode = Nodes.findParentElm(editor.selection.getNode(), body);
// if user does select all the parentNode will be undefined
parentNode = parentNode !== undefined ? parentNode : body;
hide(editor, parentNode);
show(editor, parentNode);
editor.selection.moveToBookmark(bookmark);
};
export default {
show,
hide,
toggle
};

View File

@@ -0,0 +1,38 @@
/**
* Copyright (c) Tiny Technologies, Inc. All rights reserved.
* Licensed under the LGPL or a commercial license.
* For LGPL see License.txt in the project root for license information.
* For commercial licenses see https://www.tiny.cloud/
*/
const toggleActiveState = function (editor) {
return function (e) {
const ctrl = e.control;
editor.on('VisualChars', function (e) {
ctrl.active(e.state);
});
};
};
const register = function (editor) {
editor.addButton('visualchars', {
active: false,
title: 'Show invisible characters',
cmd: 'mceVisualChars',
onPostRender: toggleActiveState(editor)
});
editor.addMenuItem('visualchars', {
text: 'Show invisible characters',
cmd: 'mceVisualChars',
onPostRender: toggleActiveState(editor),
selectable: true,
context: 'view',
prependToContext: true
});
};
export {
register
};