This commit is contained in:
2025-10-18 11:45:10 +08:00
commit 7ef89ed501
2705 changed files with 434789 additions and 0 deletions

View File

@@ -0,0 +1,28 @@
/**
* This class is the event object send when the BeforeExecCommand and ExecCommand event occurs.
*
* @class tinymce.CommandEvent
* @extends tinymce.Event
* @example
* tinymce.activeEditor.on('ExecCommand', function(e) {
* console.log(e.command, e.ui, e.value);
* });
*/
/**
* Name of the command to execute.
*
* @property {String} command
*/
/**
* User interface state. Normally false.
*
* @property {Boolean} ui User interface state.
*/
/**
* Value object for the execCommand call.
*
* @property {Object} value
*/

View File

@@ -0,0 +1,53 @@
/**
* This class is the event object send when the content events occurs such as GetContent/SetContent.
*
* @class tinymce.ContentEvent
* @extends tinymce.Event
* @example
* tinymce.activeEditor.on('GetContent', function(e) {
* console.log(e.content);
* });
*/
/**
* Optional state gets added for the load event then it's set to true.
*
* @property {Boolean} load
*/
/**
* Optional state gets added for the save event then it's set to true.
*
* @property {Boolean} save
*/
/**
* Optional state gets added for the getContent event then it's set to true.
*
* @property {Boolean} set
*/
/**
* Optional state gets added for the setContent event then it's set to true.
*
* @property {Boolean} get
*/
/**
* Optional element that the load/save event is for. This element is the textarea/div element that the
* contents gets parsed from or serialized to.
*
* @property {DOMElement} element
*/
/**
* Editor contents to be set or the content that was returned from the editor.
*
* @property {String} content HTML contents from the editor or to be put into the editor.
*/
/**
* Format of the contents normally "html".
*
* @property {String} format Format of the contents normally "html".
*/

View File

@@ -0,0 +1,121 @@
/**
* This file contains the documentation for the Editor API.
*/
/**
* Schema instance, enables you to validate elements and its children.
*
* @property schema
* @type tinymce.html.Schema
*/
/**
* DOM instance for the editor.
*
* @property dom
* @type tinymce.dom.DOMUtils
* @example
* // Adds a class to all paragraphs within the editor
* tinymce.activeEditor.dom.addClass(tinymce.activeEditor.dom.select('p'), 'someclass');
*/
/**
* HTML parser will be used when contents is inserted into the editor.
*
* @property parser
* @type tinymce.html.DomParser
*/
/**
* DOM serializer for the editor. Will be used when contents is extracted from the editor.
*
* @property serializer
* @type tinymce.dom.Serializer
* @example
* // Serializes the first paragraph in the editor into a string
* tinymce.activeEditor.serializer.serialize(tinymce.activeEditor.dom.select('p')[0]);
*/
/**
* Selection instance for the editor.
*
* @property selection
* @type tinymce.dom.Selection
* @example
* // Sets some contents to the current selection in the editor
* tinymce.activeEditor.selection.setContent('Some contents');
*
* // Gets the current selection
* alert(tinymce.activeEditor.selection.getContent());
*
* // Selects the first paragraph found
* tinymce.activeEditor.selection.select(tinymce.activeEditor.dom.select('p')[0]);
*/
/**
* Formatter instance.
*
* @property formatter
* @type tinymce.Formatter
*/
/**
* Undo manager instance, responsible for handling undo levels.
*
* @property undoManager
* @type tinymce.UndoManager
* @example
* // Undoes the last modification to the editor
* tinymce.activeEditor.undoManager.undo();
*/
/**
* Is set to true after the editor instance has been initialized
*
* @property initialized
* @type Boolean
* @example
* function isEditorInitialized(editor) {
* return editor && editor.initialized;
* }
*/
/**
* Window manager reference, use this to open new windows and dialogs.
*
* @property windowManager
* @type tinymce.WindowManager
* @example
* // Shows an alert message
* tinymce.activeEditor.windowManager.alert('Hello world!');
*
* // Opens a new dialog with the file.htm file and the size 320x240
* // It also adds a custom parameter this can be retrieved by using tinyMCEPopup.getWindowArg inside the dialog.
* tinymce.activeEditor.windowManager.open({
* url: 'file.htm',
* width: 320,
* height: 240
* }, {
* custom_param: 1
* });
*/
/**
* Notification manager reference, use this to open new windows and dialogs.
*
* @property notificationManager
* @type tinymce.NotificationManager
* @example
* // Shows a notification info message.
* tinymce.activeEditor.notificationManager.open({text: 'Hello world!', type: 'info'});
*/
/**
* Reference to the theme instance that was used to generate the UI.
*
* @property theme
* @type tinymce.Theme
* @example
* // Executes a method on the theme directly
* tinymce.activeEditor.theme.someMethod();
*/

View File

@@ -0,0 +1,50 @@
/**
* This is the base class for all TinyMCE events.
*
* @class tinymce.Event
*/
/**
* Prevents the default action of an event to be executed.
*
* @method preventDefault
*/
/**
* Stops the event from propagating up to listeners on parent objects.
*
* @method stopPropagation
*/
/**
* Prevents the event from propagating to listeners on the same object.
*
* @method stopImmediatePropagation
*/
/**
* Returns true/false if the default action is to be prevented or not.
*
* @method isDefaultPrevented
* @return {Boolean} True/false if the event is to be execured or not.
*/
/**
* Returns true/false if the event propagation is stopped or not.
*
* @method isPropagationStopped
* @return {Boolean} True/false if the event propagation is stopped or not.
*/
/**
* Returns true/false if the event immediate propagation is stopped or not.
*
* @method isImmediatePropagationStopped
* @return {Boolean} True/false if the event immediate propagation is stopped or not.
*/
/**
* The event type name for example "click".
*
* @property {String} type
*/

View File

@@ -0,0 +1,26 @@
/**
* This class is the event object sent when editors are focused/blurred.
*
* @class tinymce.FocusEvent
* @extends tinymce.Event
* @example
* tinymce.activeEditor.on('focus', function(e) {
* console.log(e.blurredEditor);
* });
*
* tinymce.activeEditor.on('blur', function(e) {
* console.log(e.focusedEditor);
* });
*/
/**
* Optional editor instance that got the focus when the blur event occurs.
*
* @property {tinymce.Editor} focusedEditor
*/
/**
* Optional editor instance that got blurred when the focus event occurs.
*
* @property {tinymce.Editor} blurredEditor
*/

View File

@@ -0,0 +1,18 @@
/**
* This class is the event object send when the ProgressState event occurs.
*
* @class tinymce.ProgressStateEvent
* @extends tinymce.Event
*/
/**
* True/false state when to show/hide the progress/throbber state for the editor.
*
* @property {Boolean} state State if the progress/throbber should be shown/hidden.
*/
/**
* Time to wait before the progress/throbber is shown.
*
* @property {Number} time
*/

View File

@@ -0,0 +1,35 @@
/**
* This class is the event object sent when objects gets resized within the editor.
*
* @class tinymce.ResizeEvent
* @extends tinymce.Event
* @example
* tinymce.activeEditor.on('ObjectResizeStart', function(e) {
* if (e.target.nodeName == 'IMG') {
* // Prevent resize
* e.preventDefault();
* }
* });
*
* tinymce.activeEditor.on('ObjectResized', function(e) {
* console.log(e.target, e.width, e.height);
* });
*/
/**
* Current element that is to be resized or has been resized.
*
* @property {DOMElement} target
*/
/**
* Current width of the object before or after resize.
*
* @property {Number} width
*/
/**
* Current height of the object before or after resize.
*
* @property {Number} height
*/

View File

@@ -0,0 +1,51 @@
/**
* TinyMCE core class.
*
* @static
* @class tinymce
* @borrow-members tinymce.EditorManager
* @borrow-members tinymce.util.Tools
*/
/**
* @property {tinymce.dom.DOMUtils} DOM Global DOM instance.
* @property {tinymce.dom.ScriptLoader} ScriptLoader Global ScriptLoader instance.
* @property {tinymce.AddOnManager} PluginManager Global PluginManager instance.
* @property {tinymce.AddOnManager} ThemeManager Global ThemeManager instance.
*/
/**
* Root level namespace this contains classes directly related to the TinyMCE editor.
*
* @namespace tinymce
*/
/**
* Contains classes for handling the browsers DOM.
*
* @namespace tinymce.dom
*/
/**
* Contains html parser and serializer logic.
*
* @namespace tinymce.html
*/
/**
* Contains the different UI types such as buttons, listboxes etc.
*
* @namespace tinymce.ui
*/
/**
* Contains various utility classes such as json parser, cookies etc.
*
* @namespace tinymce.util
*/
/**
* Contains modules to handle data binding.
*
* @namespace tinymce.data
*/

View File

@@ -0,0 +1,20 @@
let generate = (items, suffix, f) => {
let out = {};
items.forEach(k => out[k + '-' + suffix] = f(k));
return out;
};
let prefixes = (obj, mappings) => {
const objMappings = {};
mappings.forEach(v => objMappings[v[0]] = v[1]);
return Object.assign(obj, objMappings);
};
module.exports = {
generate,
prefixes
};

View File

@@ -0,0 +1,181 @@
let { TsConfigPathsPlugin } = require('awesome-typescript-loader');
let LiveReloadPlugin = require('webpack-livereload-plugin');
let path = require('path');
const packageData = require("../../package.json");
let create = (entries, tsConfig, outDir, filename) => {
return {
entry: entries,
mode: 'development',
devtool: 'source-map',
optimization: {
removeAvailableModules: false,
removeEmptyChunks: false,
splitChunks: false,
},
resolve: {
symlinks: false,
extensions: ['.ts', '.js'],
plugins: [
// We need to use the awesome typescript loader config paths since the one for ts-loader doesn't resolve aliases correctly
new TsConfigPathsPlugin({
baseUrl: '.',
compiler: 'typescript',
configFileName: tsConfig
})
]
},
module: {
rules: [
// {
// test: /\.js$/,
// use: [
// 'source-map-loader'
// ],
// enforce: 'pre'
// },
{
test: /\.ts$/,
use: [
{
loader: 'string-replace-loader',
options: {
test: /EditorManager.ts/,
multiple: [
{
search: '@@majorVersion@@',
replace: packageData.version.split('.')[0],
},
{
search: '@@minorVersion@@',
replace: packageData.version.split('.').slice(1).join('.'),
},
{
search: '@@releaseDate@@',
replace: packageData.date,
}
]
}
},
{
loader: 'ts-loader',
options: {
transpileOnly: true,
configFile: tsConfig,
experimentalWatchApi: true
}
}
// {
// loader: 'awesome-typescript-loader',
// options: {
// transpileOnly: true,
// configFileName: tsConfig
// }
// }
]
}
]
},
plugins: [
new LiveReloadPlugin()
],
output: {
filename: typeof entries === 'string' ? filename : "[name]/" + filename,
path: path.resolve(outDir),
pathinfo: false
}
};
};
let buildDemoEntries = (pluginNames, type, demo) => pluginNames.reduce(
(acc, name) => {
acc[name] = `src/${type}/${name}/demo/ts/demo/${demo}`;
return acc;
}, {}
)
let buildEntries = (pluginNames, type, entry) => pluginNames.reduce(
(acc, name) => {
acc[name] = `src/${type}/${name}/main/ts/${entry}`;
return acc;
}, {}
)
let createPlugin = (name) => {
return create(`src/plugins/${name}/demo/ts/demo/Demo.ts`, 'tsconfig.plugin.json', `scratch/demos/plugins/${name}/`, 'demo.js');
};
let createTheme = (name) => {
return create(`src/themes/${name}/demo/ts/demo/Demos.ts`, 'tsconfig.theme.json', `scratch/demos/themes/${name}`, 'demo.js');
};
let allPluginDemos = (plugins) => {
return create(buildDemoEntries(plugins, 'plugins', 'Demo.ts'), 'tsconfig.plugin.json', 'scratch/demos/plugins', 'demo.js')
}
let allThemeDemos = (themes) => {
return create(buildDemoEntries(themes, 'themes', 'Demos.ts'), 'tsconfig.theme.json', 'scratch/demos/themes', 'demo.js')
}
let all = (plugins, themes) => {
return [
allPluginDemos(plugins),
allThemeDemos(themes),
create(`src/core/demo/ts/demo/Demos.ts`, 'tsconfig.json', 'scratch/demos/core/', 'demo.js'),
create('src/core/main/ts/api/Main.ts', 'tsconfig.json', 'js/tinymce/', 'tinymce.js'),
create(buildEntries(plugins, 'plugins', 'Plugin.ts'), 'tsconfig.plugin.json', 'js/tinymce/plugins', 'plugin.js'),
create(buildEntries(themes, 'themes', 'Theme.ts'), 'tsconfig.theme.json', 'js/tinymce/themes', 'theme.js')
];
}
let generateDemoIndex = (grunt, app, plugins, themes) => {
let demoList = grunt.file.expand(['src/**/demo/html/*.html'])
let sortedDemos = demoList.reduce((acc, link) => {
const type = link.split('/')[1];
if (!acc[type]) {
acc[type] = [];
}
acc[type].push(link)
return acc;
}, {})
let lists = Object.keys(sortedDemos).map(
type => `
<h2>${type}</h2>
<ul>
${sortedDemos[type].map(
link => `<li>${type !== 'core' ? `<strong>${link.split('/')[2]}</strong> - ` : ''}<a href="${link}">${path.basename(link)}</a></li>`).join('')
}
</ul>`
).join('');
let html = `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Demos</title>
</head>
<body>
${lists}
</body>
</html>
`
app.get('/', (req, res) => res.send(html))
}
module.exports = {
createPlugin,
createTheme,
create,
all,
allPluginDemos,
allThemeDemos,
generateDemoIndex
};

View File

@@ -0,0 +1,42 @@
/*eslint-env node */
var path = require("path");
var getDirectories = function (grunt, excludes) {
return function (path) {
var directories = grunt.file.expand({ filter: "isDirectory" }, path + "/*");
var excludedPlugins = grunt.file.match(excludes, directories);
return directories.filter(function (dir) {
return excludedPlugins.indexOf(dir) === -1;
});
};
};
var generateIndex = function (prefix, singularName) {
return function (directory) {
var dirName = path.basename(directory);
return {
path: path.join(prefix, dirName, "index.js"),
data: "// Exports the \"" + dirName + "\" " + singularName + " for usage with module loaders\n" +
"// Usage:\n" +
"// CommonJS:\n" +
"// require('tinymce/" + prefix + "/" + dirName + "')\n" +
"// ES2015:\n" +
"// import 'tinymce/" + prefix + "/" + dirName + "'\n" +
"require('./" + singularName + ".js');"
};
};
};
var addIndexFiles = function (zip, directories, generator) {
directories.forEach(function (dir) {
var generated = generator(dir);
zip.addData(generated.path, generated.data);
});
};
module.exports = {
addIndexFiles: addIndexFiles,
generateIndex: generateIndex,
getDirectories: getDirectories
};

View File

@@ -0,0 +1,58 @@
var fs = require("fs");
var path = require("path");
module.exports = function (grunt) {
grunt.registerMultiTask("bundle", "Bundles code, themes and bundles to a single file.", function () {
var options, contents, themes, plugins;
function appendFile(src) {
src = src.replace(/\\/g, '/');
if (fs.existsSync(src)) {
grunt.log.writeln("Appending file:", src);
contents += grunt.file.read(src);
} else {
grunt.fail.fatal("Could not find file: " + src);
}
}
function append(dirPath, fileName, value) {
if (value) {
value.split(/,/).forEach(function (src) {
appendFile(path.join(dirPath, src, fileName));
});
}
}
options = grunt.config([this.name, this.target]).options;
options.themesDir = options.themesDir || "plugins";
options.themeFileName = options.themeFileName || "theme.min.js";
options.pluginsDir = options.pluginsDir || "plugins";
options.pluginFileName = options.pluginFileName || "plugin.min.js";
options.outputPath = options.outputPath || "full.min.js";
themes = grunt.option("themes");
plugins = grunt.option("plugins");
if (!themes && !plugins) {
grunt.log.writeln("Use: grunt bundle --themes <comma separated list of themes> --plugins <comma separated list of plugins>");
process.exit(-1);
return;
}
contents = "";
this.files.forEach(function (filePair) {
filePair.src.forEach(function (src) {
appendFile(src);
});
});
append(options.themesDir, options.themeFileName, themes);
append(options.pluginsDir, options.pluginFileName, plugins);
if (contents.length > 0) {
grunt.file.write(options.outputPath, contents);
grunt.log.ok("Created bundle js:", options.outputPath);
}
});
};

View File

@@ -0,0 +1,98 @@
var child_process = require('child_process');
var fs = require('fs');
var path = require('path');
var readFile = function (filePath) {
return fs.readFileSync(filePath, 'utf8').toString();
};
var writeFile = function (filePath, content) {
fs.writeFileSync(filePath, content);
};
var parseConfig = function (filePath) {
var data = JSON.parse(readFile(filePath));
return data;
};
var mkdirp = function (grunt, dirPath) {
grunt.file.mkdir(dirPath);
};
var fail = function (msg) {
throw new Error(msg);
};
var required = function (config, propName) {
var failMsg = 'Required property \'' + propName + '\' not defined in:\n' + JSON.stringify(config, null, ' ');
return propName in config ? config[propName] : fail(failMsg);
};
var createTargetInfo = function (filePath, targetId, globalId) {
return {
filePath: filePath,
targetId: targetId,
globalId: globalId,
globalName: globalId.split('.').pop()
};
};
var targetIdToTargetInfo = function (outputPath, replacer) {
return function (targetId) {
var filePath = path.join(outputPath, targetId.replace(/\./g, '\/'));
var globalId = replacer(targetId);
return createTargetInfo(filePath, targetId, globalId);
};
};
var replaceVariables = function (str, variables) {
Object.keys(variables).forEach(function (variable) {
str = str.replace(new RegExp('\\{\\$' + variable + '\\}', 'g'), variables[variable]);
});
return str;
};
var generateGlobaliserModule = function (templateFile, targetInfo) {
var template = readFile(templateFile);
writeFile(targetInfo.filePath + '.js', replaceVariables(template, targetInfo));
};
var replacePrefixes = function (id, search, replace) {
return search.reduce(function (id, item) {
return id.replace(item, replace);
}, id);
};
var replacePrefix = function (grunt, templateFile, outputPath, config) {
var search = required(config, 'search');
var replace = required(config, 'replace');
var targets = required(config, 'targets');
targets
.map(targetIdToTargetInfo(outputPath, id => replacePrefixes(id, search, replace)))
.forEach(function (targetInfo) {
mkdirp(grunt, path.dirname(targetInfo.filePath));
generateGlobaliserModule(templateFile, targetInfo);
});
};
var executeAction = function (grunt, action, templateFile, outputPath, config) {
if (action === 'replace.prefix') {
replacePrefix(grunt, templateFile, outputPath, config);
}
};
module.exports = function (grunt) {
grunt.registerTask('globals', 'Generates a globals layer', function () {
var options = grunt.config([this.name]).options;
var templateFile = required(options, 'templateFile');
var outputDir = required(options, 'outputDir');
var configJson = required(options, 'configFile');
var config = parseConfig(configJson);
Object.keys(config).forEach(function (action) {
executeAction(grunt, action, templateFile, outputDir, config[action]);
});
});
};

View File

@@ -0,0 +1,95 @@
var fs = require("fs");
var path = require("path");
var ZipWriter = require('moxie-zip').ZipWriter;
module.exports = function (grunt) {
grunt.registerMultiTask("moxiezip", "Creates zip files.", function () {
var target = grunt.config([this.name, this.target]);
var archive = new ZipWriter();
var done = this.async();
var options = target.options || {}, excludePaths = {};
options.baseDir = (options.baseDir || '').replace(/\\/g, '/');
function addExcludes(excludes) {
if (Array.isArray(excludes)) {
excludes.forEach(function (excludePath) {
excludePaths[path.resolve(excludePath)] = true;
});
}
}
function filterZipPath(zipFilePath) {
if (options.pathFilter) {
return options.pathFilter(zipFilePath);
}
return zipFilePath;
}
function process(filePath, zipFilePath) {
var args, stat = fs.statSync(filePath);
if (excludePaths[path.resolve(filePath)]) {
return;
}
zipFilePath = zipFilePath || filePath;
filePath = filePath.replace(/\\/g, '/');
zipFilePath = zipFilePath.replace(/\\/g, '/');
zipFilePath = filterZipPath(zipFilePath);
if (stat.isFile()) {
var data = fs.readFileSync(filePath);
if (options.dataFilter) {
args = { filePath: filePath, zipFilePath: zipFilePath, data: data };
options.dataFilter(args);
data = args.data;
}
archive.addData(path.join(options.baseDir, zipFilePath), data);
} else if (stat.isDirectory()) {
fs.readdirSync(filePath).forEach(function (fileName) {
process(path.join(filePath, fileName), path.join(zipFilePath, fileName));
});
}
}
if (options.concat) {
options.concat.forEach(function (pair) {
var chunks;
chunks = grunt.file.expand(pair.src).map(function (src) {
return grunt.file.read(src);
});
pair.dest.forEach(function (zipFilePath) {
zipFilePath = filterZipPath(zipFilePath);
archive.addData(path.join(options.baseDir, zipFilePath), chunks.join('\r\n'));
});
});
}
if (target.options.excludes) {
addExcludes(grunt.file.expand(target.options.excludes));
}
this.files.forEach(function (filePair) {
filePair.src.forEach(function (src) {
process(src);
});
});
if (options.onBeforeSave) {
options.onBeforeSave(archive);
}
grunt.file.mkdir(path.dirname(options.to));
archive.saveAs(options.to, function () {
grunt.log.ok('Created zip file:', options.to);
done();
});
});
};

View File

@@ -0,0 +1,20 @@
/* eslint-env node */
var fs = require('fs');
var path = require('path');
var package = require(path.join(__dirname, '../../package.json'));
module.exports = function (grunt) {
grunt.registerTask("validateVersion", "Check that version number in changelog and package.json match", function () {
var changelog = fs.readFileSync(path.join(__dirname, '../../changelog.txt'));
var changelogVersion = /Version ([0-9.]+)/.exec(changelog)[1];
if (package.version !== changelogVersion) {
grunt.fail.fatal(
'Latest changelog version ' + changelogVersion +
' and package.json version ' + package.version +
' does not match.'
);
}
});
};