Changes the TinyMCE workflow for building and compiling plugins

git-svn-id: https://svn.libreccm.org/ccm/trunk@5812 8810af33-2d31-482b-a856-94f89814c4df
master
baka 2019-01-28 15:02:04 +00:00
parent 2c736f7c97
commit 1d4a0804c7
1627 changed files with 8802 additions and 8031 deletions

File diff suppressed because it is too large Load Diff

View File

@ -12,8 +12,11 @@ waf.bebop.dhtml_editor_src=/assets/tinymce/js/tinymce/tinymce.min.js
This change will only take effect after you've run `ant load-bundle` again and restarted your installation.
## How to compile an individual plugin
## How to compile the editor and the plugins
After you changed a plugin in `tools-ng/tinymce/plugins`, you may want to see this changes reflected in your installation for testing. To do this, you need to change into the directory of the plugin and execute `npm run build`. Of course you need to have `npm` installed to do this.
Use the handy `tinymce.sh` script for that. You can do the following things:
After you've done this, you need to recompile your code. You can do this by running `ant deploy`.
- `./tinymce.sh` - Sets up the environment for compiling and builds the whole editor with all plugins. You can find the compiled files in `editor/js`
- `./tinymce.sh build` - Builds everything (Editor, Plugins, Themes, ...)
- `./tinymce.sh plugin <name>` - To compile a single plugin run this with the desired plugin name. The plugin will also be copied into `ccm-core`
- `./tinymce.sh test <name>` - Similar to `plugin`, but without linting and copies the files into the current runtime for instant testing.

View File

@ -1,3 +1,24 @@
Version 4.9.2 (2018-12-17)
Fixed a bug with pressing the space key on IE 11 would result in nbsp characters being inserted between words at the end of a block. #TINY-2996
Fixed a bug where character composition using quote and space on US International keyboards would produce a space instead of a quote. #TINY-2999
Fixed a bug where remove format wouldn't remove the inner most inline element in some situations. #TINY-2982
Fixed a bug where outdenting an list item would affect attributes on other list items within the same list. #TINY-2971
Fixed a bug where the DomParser filters wouldn't be applied for elements created when parsing invalid html. #TINY-2978
Fixed a bug where setProgressState wouldn't automatically close floating ui elements like menus. #TINY-2896
Fixed a bug where it wasn't possible to navigate out of a figcaption element using the arrow keys. #TINY-2894
Fixed a bug where enter key before an image inside a link would remove the image. #TINY-2780
Version 4.9.1 (2018-12-04)
Added functionality to insert html to the replacement feature of the Textpattern Plugin. #TINY-2839
Fixed a bug where `editor.selection.getContent({format: 'text'})` didn't work as expected in IE11 on an unfocused editor. #TINY-2862
Fixed a bug in the Textpattern Plugin where the editor would get an incorrect selection after inserting a text pattern on Safari. #TINY-2838
Fixed a bug where the space bar didn't work correctly in editors with the forced_root_block setting set to false. #TINY-2816
Version 4.9.0 (2018-11-27)
Added a replace feature to the Textpattern Plugin. #TINY-1908
Added functionality to the Lists Plugin that improves the indentation logic. #TINY-1790
Fixed a bug where it wasn't possible to delete/backspace when the caret was between a contentEditable=false element and a BR. #TINY-2372
Fixed a bug where copying table cells without a text selection would fail to copy anything. #TINY-1789
Implemented missing `autosave_restore_when_empty` functionality in the Autosave Plugin. Patch contributed by gzzo. #GH-4447
Reduced insertion of unnecessary nonbreaking spaces in the editor. #TINY-1879
Version 4.8.5 (2018-10-30)
Added a content_css_cors setting to the editor that adds the crossorigin="anonymous" attribute to link tags added by the StyleSheetLoader. #TINY-1909
Fixed a bug where trying to remove formatting with a collapsed selection range would throw an exception. #GH-4636

View File

@ -1,8 +0,0 @@
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
trim_trailing_whitespace = true
end_of_line = lf

View File

@ -1,16 +0,0 @@
* eol=lf
*.jar binary
*.gif binary
*.png binary
*.jpg binary
*.swf binary
*.xap binary
*.zip binary
*.eot binary
*.woff binary
*.ttf binary
*.mov binary
*.avi binary
*.flv binary
*.rm binary
*.dcr binary

View File

@ -1,9 +0,0 @@
**Do you want to request a *feature* or report a *bug*?**
**What is the current behavior?**
**If the current behavior is a bug, please provide the steps to reproduce and if possible a minimal demo of the problem via [fiddle.tinymce.com](http://fiddle.tinymce.com/) or similar.**
**What is the expected behavior?**
**Which versions of TinyMCE, and which browser / OS are affected by this issue? Did this work in previous versions of TinyMCE?**

View File

@ -1,7 +0,0 @@
**Before submitting a pull request** please do the following:
1. Fork [the repository](https://github.com/tinymce/tinymce) and create your branch from `master`
2. Have you added some code that should be tested? Write some tests! (Are you unsure how to write the test you want to write, ask us for help!)
3. Ensure that the tests pass: `grunt test`
4. Ensure that your code passes the linter: `grunt lint`
5. Make sure to sign the CLA.

View File

@ -1,825 +0,0 @@
/*eslint-env node */
let zipUtils = require('./tools/modules/zip-helper');
let gruntUtils = require('./tools/modules/grunt-utils');
let gruntWebPack = require('./tools/modules/grunt-webpack');
let swag = require('@ephox/swag');
let path = require('path');
let plugins = [
'advlist', 'anchor', 'autolink', 'autoresize', 'autosave', 'bbcode', 'charmap', 'code', 'codesample',
'colorpicker', /*'compat3x', */ 'contextmenu', 'directionality', 'emoticons', 'help', 'fullpage',
'fullscreen', 'hr', 'image', 'imagetools', 'importcss', 'insertdatetime', 'legacyoutput', 'link',
'lists', 'media', 'nonbreaking', 'noneditable', 'pagebreak', 'paste', 'preview', 'print', 'save',
'searchreplace', 'spellchecker', 'tabfocus', 'table', 'template', 'textcolor', 'textpattern', 'toc',
'visualblocks', 'visualchars', 'wordcount',
];
let themes = [
'modern', 'mobile', 'inlite'
];
module.exports = function (grunt) {
var packageData = grunt.file.readJSON('package.json');
var changelogLine = grunt.file.read('changelog.txt').toString().split('\n')[0];
var BUILD_VERSION = packageData.version + '-' + (process.env.BUILD_NUMBER ? process.env.BUILD_NUMBER : '0');
packageData.date = /^Version [^\(]+\(([^\)]+)\)/.exec(changelogLine)[1];
grunt.initConfig({
pkg: packageData,
shell: {
tsc: { command: 'node ./node_modules/typescript/bin/tsc' }
},
tslint: {
options: {
configuration: 'tslint.json'
},
files: { src: [ 'src/**/*.ts' ] }
},
globals: {
options: {
configFile: 'src/core/main/json/globals.json',
outputDir: 'lib/globals',
templateFile: 'src/core/main/js/GlobalsTemplate.js'
}
},
rollup: Object.assign(
{
core: {
options: {
treeshake: true,
name: 'tinymce',
format: 'iife',
banner: '(function () {',
footer: '})();',
plugins: [
swag.nodeResolve({
basedir: __dirname,
prefixes: {
'tinymce/core': 'lib/core/main/ts'
}
}),
swag.remapImports()
]
},
files:[
{
src: 'lib/core/main/ts/api/Main.js',
dest: 'js/tinymce/tinymce.js'
}
]
}
},
gruntUtils.generate(plugins, 'plugin', (name) => {
return {
options: {
treeshake: true,
name: name,
format: 'iife',
banner: '(function () {',
footer: '})();',
plugins: [
swag.nodeResolve({
basedir: __dirname,
prefixes: gruntUtils.prefixes({
'tinymce/core': 'lib/globals/tinymce/core'
}, [
[`tinymce/plugins/${name}`, `lib/plugins/${name}/main/ts`]
])
}),
swag.remapImports()
]
},
files:[ { src: `lib/plugins/${name}/main/ts/Plugin.js`, dest: `js/tinymce/plugins/${name}/plugin.js` } ]
};
}),
gruntUtils.generate(themes, 'theme', (name) => {
return {
options: {
treeshake: true,
name: name,
format: 'iife',
banner: '(function () {',
footer: '})();',
plugins: [
swag.nodeResolve({
basedir: __dirname,
prefixes: gruntUtils.prefixes({
'tinymce/core': 'lib/globals/tinymce/core',
'tinymce/ui': 'lib/ui/main/ts'
}, [
[`tinymce/themes/${name}`, `lib/themes/${name}/main/ts`]
])
}),
swag.remapImports()
]
},
files:[
{
src: `lib/themes/${name}/main/ts/Theme.js`,
dest: `js/tinymce/themes/${name}/theme.js`
}
]
};
})
),
uglify: Object.assign(
{
options: {
output: {
ascii_only: true,
},
ie8: true
},
core: {
files: [
{ src: 'js/tinymce/tinymce.js', dest: 'js/tinymce/tinymce.min.js' },
{ src: 'src/core/main/js/JqueryIntegration.js', dest: 'js/tinymce/jquery.tinymce.min.js' }
]
},
'compat3x-plugin': {
files: [
{
src: 'src/plugins/compat3x/main/js/plugin.js',
dest: 'js/tinymce/plugins/compat3x/plugin.min.js'
}
]
}
},
gruntUtils.generate(plugins, 'plugin', (name) => {
return {
files: [ { src: `js/tinymce/plugins/${name}/plugin.js`, dest: `js/tinymce/plugins/${name}/plugin.min.js` } ]
};
}),
gruntUtils.generate(themes, 'theme', (name) => {
return {
files: [ { src: `js/tinymce/themes/${name}/theme.js`, dest: `js/tinymce/themes/${name}/theme.min.js` } ]
};
})
),
webpack: Object.assign(
{core: () => gruntWebPack.create('src/core/demo/ts/demo/Demos.ts', 'tsconfig.json', 'scratch/demos/core', 'demo.js')},
{plugins: () => gruntWebPack.allPlugins(plugins)},
{themes: () => gruntWebPack.allThemes(themes)},
gruntUtils.generate(plugins, 'plugin', (name) => () => gruntWebPack.createPlugin(name) ),
gruntUtils.generate(themes, 'theme', (name) => () => gruntWebPack.createTheme(name) )
),
'webpack-dev-server': {
options: {
webpack: gruntWebPack.all(plugins, themes),
publicPath: '/',
inline: false,
port: grunt.option('webpack-port') !== undefined ? grunt.option('webpack-port') : 3000,
host: '0.0.0.0',
disableHostCheck: true,
before: app => gruntWebPack.generateDemoIndex(grunt, app, plugins, themes)
},
start: { }
},
less: {
desktop: {
options: {
cleancss: true,
strictImports: true,
compress: true,
yuicompress: true,
sourceMap: true,
sourceMapRootpath: '.',
optimization: 2
},
files: {
'js/tinymce/skins/lightgray/skin.min.css': 'src/skins/lightgray/main/less/desktop/Skin.less'
}
},
mobile: {
options: {
plugins : [ new (require('less-plugin-autoprefix'))({ browsers : [ 'last 2 versions', /* for phantom */'safari >= 4' ] }) ],
compress: true,
yuicompress: true,
sourceMap: true,
sourceMapRootpath: '.',
optimization: 2
},
files: {
'js/tinymce/skins/lightgray/skin.mobile.min.css': 'src/skins/lightgray/main/less/mobile/app/mobile-less.less'
}
},
'content-mobile': {
options: {
cleancss: true,
strictImports: true,
compress: true
},
files: {
'js/tinymce/skins/lightgray/content.mobile.min.css': 'src/skins/lightgray/main/less/mobile/content.less'
}
},
content: {
options: {
cleancss: true,
strictImports: true,
compress: true
},
files: {
'js/tinymce/skins/lightgray/content.min.css': 'src/skins/lightgray/main/less/desktop/Content.less'
}
},
'content-inline': {
options: {
cleancss: true,
strictImports: true,
compress: true
},
files: {
'js/tinymce/skins/lightgray/content.inline.min.css': 'src/skins/lightgray/main/less/desktop/Content.Inline.less'
}
}
},
copy: {
core: {
options: {
process: function (content) {
return content.
replace('@@majorVersion@@', packageData.version.split('.')[0]).
replace('@@minorVersion@@', packageData.version.split('.').slice(1).join('.')).
replace('@@releaseDate@@', packageData.date);
}
},
files: [
{
src: 'js/tinymce/tinymce.js',
dest: 'js/tinymce/tinymce.js'
},
{
src: 'js/tinymce/tinymce.min.js',
dest: 'js/tinymce/tinymce.min.js'
},
{
src: 'src/core/main/text/readme_lang.md',
dest: 'js/tinymce/langs/readme.md'
},
{
src: 'LICENSE.TXT',
dest: 'js/tinymce/license.txt'
}
]
},
skins: {
files: [
{
expand: true,
flatten: true,
cwd: 'src/skins/lightgray/main/fonts',
src: [
'**',
'!*.json',
'!*.md'
],
dest: 'js/tinymce/skins/lightgray/fonts'
},
{
expand: true,
flatten: true,
cwd: 'src/skins/lightgray/main/img',
src: '**',
dest: 'js/tinymce/skins/lightgray/img'
}
]
},
plugins: {
files: [
{ expand: true, cwd: 'src/plugins/compat3x/main', src: ['img/**'], dest: 'js/tinymce/plugins/compat3x' },
{ expand: true, cwd: 'src/plugins/compat3x/main', src: ['css/**'], dest: 'js/tinymce/plugins/compat3x' },
{ expand: true, cwd: 'src/plugins/compat3x/main/js', src: ['utils/**', 'plugin.js', 'tiny_mce_popup.js'], dest: 'js/tinymce/plugins/compat3x' },
{ src: 'src/plugins/codesample/main/css/prism.css', dest: 'js/tinymce/plugins/codesample/css/prism.css' }
]
},
'emoticons-plugin': {
files: [
{
flatten: true,
expand: true,
cwd: 'src/plugins/emoticons/main/img',
src: '*.gif',
dest: 'js/tinymce/plugins/emoticons/img/'
}
]
},
'help-plugin': {
files: [
{ src: 'src/plugins/help/main/img/logo.png', dest: 'js/tinymce/plugins/help/img/logo.png' }
]
},
'visualblocks-plugin': {
files: [
{ src: 'src/plugins/visualblocks/main/css/visualblocks.css', dest: 'js/tinymce/plugins/visualblocks/css/visualblocks.css' }
]
}
},
moxiezip: {
production: {
options: {
baseDir: 'tinymce',
excludes: [
'js/**/plugin.js',
'js/**/theme.js',
'js/**/*.map',
'js/tinymce/tinymce.full.min.js',
'js/tinymce/plugins/moxiemanager',
'js/tinymce/plugins/compat3x',
'js/tinymce/plugins/visualblocks/img',
'js/tinymce/skins/*/fonts/*.json',
'js/tinymce/skins/*/fonts/readme.md',
'readme.md'
],
to: 'tmp/tinymce_<%= pkg.version %>.zip'
},
src: [
'js/tinymce/langs',
'js/tinymce/plugins',
'js/tinymce/skins',
'js/tinymce/themes',
'js/tinymce/tinymce.min.js',
'js/tinymce/jquery.tinymce.min.js',
'js/tinymce/license.txt',
'changelog.txt',
'LICENSE.TXT',
'readme.md'
]
},
development: {
options: {
baseDir: 'tinymce',
excludes: [
'src/**/dist',
'src/**/scratch',
'src/**/lib',
'src/**/dependency',
'js/tinymce/tinymce.full.min.js',
'js/tests/.jshintrc'
],
to: 'tmp/tinymce_<%= pkg.version %>_dev.zip'
},
src: [
'config',
'src',
'js',
'tests',
'tools',
'changelog.txt',
'LICENSE.TXT',
'Gruntfile.js',
'readme.md',
'package.json',
'.eslintrc',
'.jscsrc',
'.jshintrc'
]
},
cdn: {
options: {
onBeforeSave: function (zip) {
zip.addData('dist/version.txt', packageData.version);
},
pathFilter: function (zipFilePath) {
return zipFilePath.replace('js/tinymce/', 'dist/');
},
excludes: [
'js/**/config',
'js/**/scratch',
'js/**/classes',
'js/**/lib',
'js/**/dependency',
'js/**/src',
'js/**/*.less',
'js/**/*.dev.js',
'js/**/*.dev.svg',
'js/**/*.map',
'js/tinymce/tinymce.full.min.js',
'js/tinymce/plugins/moxiemanager',
'js/tinymce/plugins/visualblocks/img',
'js/tinymce/skins/*/fonts/*.json',
'js/tinymce/skins/*/fonts/*.dev.svg',
'js/tinymce/skins/*/fonts/readme.md',
'readme.md',
'js/tests/.jshintrc'
],
concat: [
{
src: [
'js/tinymce/tinymce.min.js',
'js/tinymce/themes/*/theme.min.js',
'js/tinymce/plugins/*/plugin.min.js',
'!js/tinymce/plugins/compat3x/plugin.min.js',
'!js/tinymce/plugins/example/plugin.min.js',
'!js/tinymce/plugins/example_dependency/plugin.min.js'
],
dest: [
'js/tinymce/tinymce.min.js'
]
}
],
to: 'tmp/tinymce_<%= pkg.version %>_cdn.zip'
},
src: [
'js/tinymce/jquery.tinymce.min.js',
'js/tinymce/tinymce.js',
'js/tinymce/langs',
'js/tinymce/plugins',
'js/tinymce/skins',
'js/tinymce/themes',
'js/tinymce/license.txt'
]
},
component: {
options: {
excludes: [
'js/**/config',
'js/**/scratch',
'js/**/classes',
'js/**/lib',
'js/**/dependency',
'js/**/src',
'js/**/*.less',
'js/**/*.dev.svg',
'js/**/*.dev.js',
'js/**/*.map',
'js/tinymce/tinymce.full.min.js',
'js/tinymce/plugins/moxiemanager',
'js/tinymce/plugins/example',
'js/tinymce/plugins/example_dependency',
'js/tinymce/plugins/compat3x',
'js/tinymce/plugins/visualblocks/img',
'js/tinymce/skins/*/fonts/*.json',
'js/tinymce/skins/*/fonts/readme.md'
],
pathFilter: function (zipFilePath) {
if (zipFilePath.indexOf('js/tinymce/') === 0) {
return zipFilePath.substr('js/tinymce/'.length);
}
return zipFilePath;
},
onBeforeSave: function (zip) {
function jsonToBuffer(json) {
return new Buffer(JSON.stringify(json, null, '\t'));
}
zip.addData('bower.json', jsonToBuffer({
'name': 'tinymce',
'description': 'Web based JavaScript HTML WYSIWYG editor control.',
'license': 'LGPL-2.1',
'keywords': ['editor', 'wysiwyg', 'tinymce', 'richtext', 'javascript', 'html'],
'homepage': 'http://www.tinymce.com',
'ignore': ['readme.md', 'composer.json', 'package.json', '.npmignore', 'changelog.txt']
}));
zip.addData('package.json', jsonToBuffer({
'name': 'tinymce',
'version': packageData.version,
'repository': {
'type': 'git',
'url': 'https://github.com/tinymce/tinymce-dist.git'
},
'description': 'Web based JavaScript HTML WYSIWYG editor control.',
'author': 'Ephox Corporation',
'main': 'tinymce.js',
'license': 'LGPL-2.1',
'keywords': ['editor', 'wysiwyg', 'tinymce', 'richtext', 'javascript', 'html'],
'bugs': { 'url': 'https://github.com/tinymce/tinymce/issues' }
}));
zip.addData('composer.json', jsonToBuffer({
'name': 'tinymce/tinymce',
'version': packageData.version,
'description': 'Web based JavaScript HTML WYSIWYG editor control.',
'license': ['LGPL-2.1-only'],
'keywords': ['editor', 'wysiwyg', 'tinymce', 'richtext', 'javascript', 'html'],
'homepage': 'http://www.tinymce.com',
'type': 'component',
'extra': {
'component': {
'scripts': [
'tinymce.js',
'plugins/*/plugin.js',
'themes/*/theme.js'
],
'files': [
'tinymce.min.js',
'plugins/*/plugin.min.js',
'themes/*/theme.min.js',
'skins/**'
]
}
},
'archive': {
'exclude': ['readme.md', 'bower.js', 'package.json', '.npmignore', 'changelog.txt']
}
}));
zip.addFile(
'jquery.tinymce.js',
'js/tinymce/jquery.tinymce.min.js'
);
var getDirs = zipUtils.getDirectories(grunt, this.excludes);
zipUtils.addIndexFiles(
zip,
getDirs('js/tinymce/plugins'),
zipUtils.generateIndex('plugins', 'plugin')
);
zipUtils.addIndexFiles(
zip,
getDirs('js/tinymce/themes'),
zipUtils.generateIndex('themes', 'theme')
);
},
to: 'tmp/tinymce_<%= pkg.version %>_component.zip'
},
src: [
'js/tinymce/skins',
'js/tinymce/plugins',
'js/tinymce/themes',
'js/tinymce/tinymce.js',
'js/tinymce/tinymce.min.js',
'js/tinymce/jquery.tinymce.min.js',
'js/tinymce/license.txt',
'changelog.txt',
'readme.md'
]
}
},
nugetpack: {
main: {
options: {
id: 'TinyMCE',
version: packageData.version,
authors: 'Ephox Corp',
owners: 'Ephox Corp',
description: 'The best WYSIWYG editor! TinyMCE is a platform independent web based Javascript HTML WYSIWYG editor ' +
'control released as Open Source under LGPL by Ephox Corp. TinyMCE has the ability to convert HTML ' +
'TEXTAREA fields or other HTML elements to editor instances. TinyMCE is very easy to integrate ' +
'into other Content Management Systems.',
releaseNotes: 'Release notes for my package.',
summary: 'TinyMCE is a platform independent web based Javascript HTML WYSIWYG editor ' +
'control released as Open Source under LGPL by Ephox Corp.',
projectUrl: 'http://www.tinymce.com/',
iconUrl: 'http://www.tinymce.com/favicon.ico',
licenseUrl: 'http://www.tinymce.com/license',
requireLicenseAcceptance: true,
tags: 'Editor TinyMCE HTML HTMLEditor',
excludes: [
'js/**/config',
'js/**/scratch',
'js/**/classes',
'js/**/lib',
'js/**/dependency',
'js/**/src',
'js/**/*.less',
'js/**/*.dev.svg',
'js/**/*.dev.js',
'js/**/*.map',
'js/tinymce/tinymce.full.min.js'
],
outputDir: 'tmp'
},
files: [
{ src: 'js/tinymce/langs', dest: '/content/scripts/tinymce/langs' },
{ src: 'js/tinymce/plugins', dest: '/content/scripts/tinymce/plugins' },
{ src: 'js/tinymce/themes', dest: '/content/scripts/tinymce/themes' },
{ src: 'js/tinymce/skins', dest: '/content/scripts/tinymce/skins' },
{ src: 'js/tinymce/tinymce.js', dest: '/content/scripts/tinymce/tinymce.js' },
{ src: 'js/tinymce/tinymce.min.js', dest: '/content/scripts/tinymce/tinymce.min.js' },
{ src: 'js/tinymce/jquery.tinymce.min.js', dest: '/content/scripts/tinymce/jquery.tinymce.min.js' },
{ src: 'js/tinymce/license.txt', dest: '/content/scripts/tinymce/license.txt' }
]
},
jquery: {
options: {
id: 'TinyMCE.jQuery',
title: 'TinyMCE.jQuery [Deprecated]',
version: packageData.version,
authors: 'Ephox Corp',
owners: 'Ephox Corp',
description: 'This package has been deprecated use https://www.nuget.org/packages/TinyMCE/',
releaseNotes: 'This package has been deprecated use https://www.nuget.org/packages/TinyMCE/',
summary: 'This package has been deprecated use https://www.nuget.org/packages/TinyMCE/',
projectUrl: 'http://www.tinymce.com/',
iconUrl: 'http://www.tinymce.com/favicon.ico',
licenseUrl: 'http://www.tinymce.com/license',
requireLicenseAcceptance: true,
tags: 'Editor TinyMCE HTML HTMLEditor',
excludes: [
'js/**/config',
'js/**/scratch',
'js/**/classes',
'js/**/lib',
'js/**/dependency',
'js/**/src',
'js/**/*.less',
'js/**/*.dev.svg',
'js/**/*.dev.js',
'js/**/*.map',
'js/tinymce/tinymce.full.min.js'
],
outputDir: 'tmp'
},
files: [
{ src: 'js/tinymce/langs', dest: '/content/scripts/tinymce/langs' },
{ src: 'js/tinymce/plugins', dest: '/content/scripts/tinymce/plugins' },
{ src: 'js/tinymce/themes', dest: '/content/scripts/tinymce/themes' },
{ src: 'js/tinymce/skins', dest: '/content/scripts/tinymce/skins' },
{ src: 'js/tinymce/tinymce.js', dest: '/content/scripts/tinymce/tinymce.js' },
{ src: 'js/tinymce/tinymce.min.js', dest: '/content/scripts/tinymce/tinymce.min.js' },
{ src: 'js/tinymce/jquery.tinymce.min.js', dest: '/content/scripts/tinymce/jquery.tinymce.min.js' },
{ src: 'js/tinymce/license.txt', dest: '/content/scripts/tinymce/license.txt' }
]
}
},
bundle: {
minified: {
options: {
themesDir: 'js/tinymce/themes',
pluginsDir: 'js/tinymce/plugins',
pluginFileName: 'plugin.min.js',
themeFileName: 'theme.min.js',
outputPath: 'js/tinymce/tinymce.full.min.js'
},
src: [
'js/tinymce/tinymce.min.js'
]
},
source: {
options: {
themesDir: 'js/tinymce/themes',
pluginsDir: 'js/tinymce/plugins',
pluginFileName: 'plugin.js',
themeFileName: 'theme.js',
outputPath: 'js/tinymce/tinymce.full.js'
},
src: [
'js/tinymce/tinymce.js'
]
}
},
clean: {
dist: ['js'],
lib: ['lib'],
scratch: ['scratch'],
release: ['tmp']
},
'bedrock-manual': {
core: {
config: 'tsconfig.json',
projectdir: '.',
stopOnFailure: true,
testfiles: [
'src/**/test/ts/atomic/**/*Test.ts',
'src/**/test/ts/browser/**/*Test.ts'
],
customRoutes: 'src/core/test/json/routes.json'
}
},
'bedrock-auto': {
phantomjs: {
browser: 'phantomjs',
config: 'tsconfig.json',
testfiles: ['src/**/test/ts/**/*Test.ts'],
stopOnFailure: true,
overallTimeout: 600000,
singleTimeout: 300000,
customRoutes: 'src/core/test/json/routes.json',
name: 'phantomjs'
},
'chrome-headless': {
browser: 'chrome-headless',
config: 'tsconfig.json',
testfiles: ['src/**/test/ts/**/*Test.ts'],
stopOnFailure: true,
overallTimeout: 600000,
singleTimeout: 300000,
customRoutes: 'src/core/test/json/routes.json',
name: 'chrome-headless'
},
chrome: {
browser: 'chrome',
config: 'tsconfig.json',
testfiles: ['src/**/test/ts/**/*Test.ts'],
stopOnFailure: true,
overallTimeout: 600000,
singleTimeout: 300000,
customRoutes: 'src/core/test/json/routes.json',
name: 'chrome'
},
firefox: {
browser: 'firefox',
config: 'tsconfig.json',
testfiles: ['src/**/test/ts/**/*Test.ts'],
stopOnFailure: true,
overallTimeout: 600000,
singleTimeout: 300000,
customRoutes: 'src/core/test/json/routes.json',
name: 'firefox'
},
MicrosoftEdge: {
browser: 'MicrosoftEdge',
config: 'tsconfig.json',
testfiles: ['src/**/test/ts/**/*Test.ts'],
stopOnFailure: true,
overallTimeout: 600000,
singleTimeout: 300000,
customRoutes: 'src/core/test/json/routes.json',
name: 'MicrosoftEdge'
},
ie: {
browser: 'ie',
config: 'tsconfig.json',
testfiles: ['src/**/test/ts/**/*Test.ts'],
stopOnFailure: true,
overallTimeout: 600000,
singleTimeout: 300000,
customRoutes: 'src/core/test/json/routes.json',
name: 'ie'
}
},
watch: {
skins: {
files: ['src/skins/lightgray/main/less/**/*'],
tasks: ['less', 'copy:skins'],
options: {
spawn: false
}
}
}
});
grunt.registerTask('version', 'Creates a version file', function () {
grunt.file.write('tmp/version.txt', BUILD_VERSION);
});
grunt.registerTask('build-headers', 'Appends build headers to js files', function () {
var header = '// ' + packageData.version + ' (' + packageData.date + ')\n';
grunt.file.write('js/tinymce/tinymce.js', header + grunt.file.read('js/tinymce/tinymce.js'));
grunt.file.write('js/tinymce/tinymce.min.js', header + grunt.file.read('js/tinymce/tinymce.min.js'));
});
require('load-grunt-tasks')(grunt);
grunt.loadTasks('tools/tasks');
grunt.loadNpmTasks('@ephox/bedrock');
grunt.loadNpmTasks('@ephox/swag');
grunt.loadNpmTasks('grunt-tslint');
grunt.registerTask('prod', [
'validateVersion',
'shell:tsc',
'tslint',
'globals',
'rollup',
'uglify',
'less',
'copy',
'build-headers',
'clean:release',
'moxiezip',
'nugetpack',
'version'
]);
grunt.registerTask('dev', [
'shell:tsc',
'globals',
'rollup',
'less',
'copy'
]);
grunt.registerTask('start', ['webpack-dev-server']);
grunt.registerTask('default', ['prod']);
grunt.registerTask('test', ['bedrock-auto:phantomjs']);
};

View File

@ -1,99 +0,0 @@
TinyMCE - JavaScript Library for Rich Text Editing
===================================================
Building TinyMCE
-----------------
Install [Node.js](https://nodejs.org/en/) on your system.
Clone this repository on your system
```
$ git clone https://github.com/tinymce/tinymce.git
```
Open a console and go to the project directory.
```
$ cd tinymce/
```
Install `grunt` command line tool globally.
```
$ npm i -g grunt-cli
```
Install all package dependencies.
```
$ npm install
```
Now, build TinyMCE by using `grunt`.
```
$ grunt
```
Build tasks
------------
`grunt`
Lints, compiles, minifies and creates release packages for TinyMCE. This will produce the production ready packages.
`grunt start`
Starts a webpack-dev-server that compiles the core, themes, plugins and all demos. Go to `localhost:3000` for a list of links to all the demo pages.
`grunt dev`
Runs tsc, webpack and less. This will only produce the bare essentials for a development build and is a lot faster.
`grunt test`
Runs all tests on PhantomJS.
`grunt bedrock-manual`
Runs all tests manually in a browser.
`grunt bedrock-auto:<browser>`
Runs all tests through selenium browsers supported are chrome, firefox, ie, MicrosoftEdge, chrome-headless and phantomjs.
`grunt webpack:core`
Builds the demo js files for the core part of tinymce this is required to get the core demos working.
`grunt webpack:plugins`
Builds the demo js files for the plugins part of tinymce this is required to get the plugins demos working.
`grunt webpack:themes`
Builds the demo js files for the themes part of tinymce this is required to get the themes demos working.
`grunt webpack:<name>-plugin`
Builds the demo js files for the specific plugin.
`grunt webpack:<name>-theme`
Builds the demo js files for the specific theme.
`grunt --help`
Displays the various build tasks.
Bundle themes and plugins into a single file
---------------------------------------------
`grunt bundle --themes=modern --plugins=table,paste`
Minifies the core, adds the modern theme and adds the table and paste plugin into tinymce.min.js.
Contributing to the TinyMCE project
------------------------------------
TinyMCE is an open source software project and we encourage developers to contribute patches and code to be included in the main package of TinyMCE.
__Basic Rules__
* Contributed code will be licensed under the LGPL license but not limited to LGPL
* Copyright notices will be changed to Ephox Corporation, contributors will get credit for their work
* All third party code will be reviewed, tested and possibly modified before being released
* All contributors will have to have signed the Contributor License Agreement
These basic rules ensures that the contributed code remains open source and under the LGPL license.
__How to Contribute to the Code__
The TinyMCE source code is [hosted on Github](https://github.com/tinymce/tinymce). Through Github you can submit pull requests and log new bugs and feature requests.
When you submit a pull request, you will get a notice about signing the __Contributors License Agreement (CLA)__.
You should have a __valid email address on your GitHub account__, and you will be sent a key to verify your identity and digitally sign the agreement.
After you signed your pull request will automatically be ready for review & merge.
__How to Contribute to the Docs__
Docs are hosted on Github in the [tinymce-docs](https://github.com/tinymce/tinymce-docs) repo.
[How to contribute](https://www.tinymce.com/docs/advanced/contributing-docs/) to the docs, including a style guide, can be found on the TinyMCE website.

View File

@ -1,14 +0,0 @@
/**
* NativeTypes.ts
*
* Released under LGPL License.
* Copyright (c) 1999-2018 Ephox Corp. All rights reserved
*
* License: http://www.tinymce.com/license
* Contributing: http://www.tinymce.com/contributing
*/
import { Selection } from '@ephox/dom-globals';
// tslint:disable-next-line:no-empty-interface
export interface NativeSelection extends Selection {}

View File

@ -1,25 +0,0 @@
/**
* InputKeys.ts
*
* Released under LGPL License.
* Copyright (c) 1999-2018 Ephox Corp. All rights reserved
*
* License: http://www.tinymce.com/license
* Contributing: http://www.tinymce.com/contributing
*/
import { Editor } from '../api/Editor';
import { normalizeNbspsInEditor } from './Nbsps';
const setup = (editor: Editor) => {
editor.on('input', (e) => {
// We only care about non composing inputs since moving the caret or modifying the text node will blow away the IME
if (e.isComposing === false) {
normalizeNbspsInEditor(editor);
}
});
};
export {
setup
};

View File

@ -1,30 +0,0 @@
/**
* InsertNewLine.js
*
* Released under LGPL License.
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved
*
* License: http://www.tinymce.com/license
* Contributing: http://www.tinymce.com/contributing
*/
import { Fun } from '@ephox/katamari';
import InsertBlock from './InsertBlock';
import InsertBr from './InsertBr';
import NewLineAction from './NewLineAction';
const insert = function (editor, evt) {
NewLineAction.getAction(editor, evt).fold(
function () {
InsertBr.insert(editor, evt);
},
function () {
InsertBlock.insert(editor, evt);
},
Fun.noop
);
};
export default {
insert
};

View File

@ -1,68 +0,0 @@
/**
* GetSelectionContent.js
*
* Released under LGPL License.
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved
*
* License: http://www.tinymce.com/license
* Contributing: http://www.tinymce.com/contributing
*/
import { Element } from '@ephox/sugar';
import EventProcessRanges from './EventProcessRanges';
import FragmentReader from './FragmentReader';
import MultiRange from './MultiRange';
import Zwsp from '../text/Zwsp';
const getContent = function (editor, args) {
const rng = editor.selection.getRng(), tmpElm = editor.dom.create('body');
const sel = editor.selection.getSel();
let fragment;
const ranges = EventProcessRanges.processRanges(editor, MultiRange.getRanges(sel));
args = args || {};
args.get = true;
args.format = args.format || 'html';
args.selection = true;
args = editor.fire('BeforeGetContent', args);
if (args.isDefaultPrevented()) {
editor.fire('GetContent', args);
return args.content;
}
if (args.format === 'text') {
return editor.selection.isCollapsed() ? '' : Zwsp.trim(rng.text || (sel.toString ? sel.toString() : ''));
}
if (rng.cloneContents) {
fragment = args.contextual ? FragmentReader.read(Element.fromDom(editor.getBody()), ranges).dom() : rng.cloneContents();
if (fragment) {
tmpElm.appendChild(fragment);
}
} else if (rng.item !== undefined || rng.htmlText !== undefined) {
// IE will produce invalid markup if elements are present that
// it doesn't understand like custom elements or HTML5 elements.
// Adding a BR in front of the contents and then remoiving it seems to fix it though.
tmpElm.innerHTML = '<br>' + (rng.item ? rng.item(0).outerHTML : rng.htmlText);
tmpElm.removeChild(tmpElm.firstChild);
} else {
tmpElm.innerHTML = rng.toString();
}
args.getInner = true;
const content = editor.selection.serializer.serialize(tmpElm, args);
if (args.format === 'tree') {
return content;
}
args.content = editor.selection.isCollapsed() ? '' : content;
editor.fire('GetContent', args);
return args.content;
};
export default {
getContent
};

View File

@ -1,17 +0,0 @@
/**
* Bidi.js
*
* Released under LGPL License.
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved
*
* License: http://www.tinymce.com/license
* Contributing: http://www.tinymce.com/contributing
*/
const strongRtl = /[\u0591-\u07FF\uFB1D-\uFDFF\uFE70-\uFEFC]/;
const hasStrongRtl = (text: string) => strongRtl.test(text);
export {
hasStrongRtl
};

View File

@ -1,90 +0,0 @@
/**
* Fun.js
*
* Released under LGPL License.
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved
*
* License: http://www.tinymce.com/license
* Contributing: http://www.tinymce.com/contributing
*/
/**
* Functional utility class.
*
* @private
* @class tinymce.util.Fun
*/
const slice = [].slice;
const constant = function (value) {
return function () {
return value;
};
};
const negate = function (predicate) {
return function (x) {
return !predicate(x);
};
};
const compose = function (f, g) {
return function (x) {
return f(g(x));
};
};
const or = function (...x: any[]) {
const args = slice.call(arguments);
return function (x) {
for (let i = 0; i < args.length; i++) {
if (args[i](x)) {
return true;
}
}
return false;
};
};
const and = function (...x: any[]) {
const args = slice.call(arguments);
return function (x) {
for (let i = 0; i < args.length; i++) {
if (!args[i](x)) {
return false;
}
}
return true;
};
};
const curry = function (fn, ...x: any[]) {
const args = slice.call(arguments);
if (args.length - 1 >= fn.length) {
return fn.apply(this, args.slice(1));
}
return function () {
const tempArgs = args.concat([].slice.call(arguments));
return curry.apply(this, tempArgs);
};
};
const noop = function () {
};
export default {
constant,
negate,
and,
or,
curry,
compose,
noop
};

View File

@ -1,302 +0,0 @@
import { GeneralSteps, Keys, Logger, Pipeline } from '@ephox/agar';
import { TinyActions, TinyApis, TinyLoader } from '@ephox/mcagar';
import Theme from 'tinymce/themes/modern/Theme';
import { UnitTest } from '@ephox/bedrock';
UnitTest.asynctest('browser.tinymce.core.keyboard.SpaceKeyTest', (success, failure) => {
Theme();
TinyLoader.setup(function (editor, onSuccess, onFailure) {
const tinyApis = TinyApis(editor);
const tinyActions = TinyActions(editor);
const img = '<img src="data:image/gif;base64,R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs=" />';
Pipeline.async({}, [
Logger.t('Space key around inline boundary elements', GeneralSteps.sequence([
Logger.t('Press space at beginning of inline boundary inserting nbsp', GeneralSteps.sequence([
tinyApis.sFocus,
tinyApis.sSetContent('<p>a <a href="#">b</a> c</p>'),
tinyApis.sSetCursor([0, 1, 0], 0),
tinyApis.sNodeChanged,
tinyActions.sContentKeystroke(Keys.space(), {}),
tinyApis.sAssertSelection([0, 1, 0], 1, [0, 1, 0], 1),
tinyApis.sAssertContent('<p>a <a href="#">&nbsp;b</a> c</p>')
])),
Logger.t('Press space at end of inline boundary inserting nbsp', GeneralSteps.sequence([
tinyApis.sFocus,
tinyApis.sSetContent('<p>a <a href="#">b</a> c</p>'),
tinyApis.sSetCursor([0, 1, 0], 1),
tinyApis.sNodeChanged,
tinyActions.sContentKeystroke(Keys.space(), {}),
tinyApis.sAssertSelection([0, 1, 0], 2, [0, 1, 0], 2),
tinyApis.sAssertContent('<p>a <a href="#">b&nbsp;</a> c</p>')
])),
Logger.t('Press space at beginning of inline boundary inserting space', GeneralSteps.sequence([
tinyApis.sFocus,
tinyApis.sSetContent('<p>a<a href="#">b</a>c</p>'),
tinyApis.sSetCursor([0, 1, 0], 0),
tinyApis.sNodeChanged,
tinyActions.sContentKeystroke(Keys.space(), {}),
tinyApis.sAssertSelection([0, 1, 0], 1, [0, 1, 0], 1),
tinyApis.sAssertContent('<p>a<a href="#"> b</a>c</p>')
])),
Logger.t('Press space at end of inline boundary inserting space', GeneralSteps.sequence([
tinyApis.sFocus,
tinyApis.sSetContent('<p>a<a href="#">b</a>c</p>'),
tinyApis.sSetCursor([0, 1, 0], 1),
tinyApis.sNodeChanged,
tinyActions.sContentKeystroke(Keys.space(), {}),
tinyApis.sAssertSelection([0, 1, 0], 2, [0, 1, 0], 2),
tinyApis.sAssertContent('<p>a<a href="#">b </a>c</p>')
])),
Logger.t('Press space at start of inline boundary with leading space inserting nbsp', GeneralSteps.sequence([
tinyApis.sFocus,
tinyApis.sSetContent('<p>a<a href="#"> b</a>c</p>'),
tinyApis.sSetCursor([0, 1, 0], 0),
tinyApis.sNodeChanged,
tinyActions.sContentKeystroke(Keys.space(), {}),
tinyApis.sAssertSelection([0, 1, 0], 1, [0, 1, 0], 1),
tinyApis.sAssertContent('<p>a<a href="#">&nbsp; b</a>c</p>')
])),
Logger.t('Press space at end of inline boundary with trailing space inserting nbsp', GeneralSteps.sequence([
tinyApis.sFocus,
tinyApis.sSetContent('<p>a<a href="#">b </a>c</p>'),
tinyApis.sSetCursor([0, 1, 0], 2),
tinyApis.sNodeChanged,
tinyActions.sContentKeystroke(Keys.space(), {}),
tinyApis.sAssertSelection([0, 1, 0], 3, [0, 1, 0], 3),
tinyApis.sAssertContent('<p>a<a href="#">b &nbsp;</a>c</p>')
]))
])),
Logger.t('Space key in block elements', GeneralSteps.sequence([
Logger.t('Press space at beginning of block', GeneralSteps.sequence([
tinyApis.sFocus,
tinyApis.sSetContent('<p>a</p>'),
tinyApis.sSetCursor([0, 0], 0),
tinyActions.sContentKeystroke(Keys.space(), {}),
tinyApis.sAssertSelection([0, 0], 1, [0, 0], 1),
tinyApis.sAssertContent('<p>&nbsp;a</p>')
])),
Logger.t('Press space at end of block', GeneralSteps.sequence([
tinyApis.sFocus,
tinyApis.sSetContent('<p>a</p>'),
tinyApis.sSetCursor([0, 0], 1),
tinyActions.sContentKeystroke(Keys.space(), {}),
tinyApis.sAssertSelection([0, 0], 2, [0, 0], 2),
tinyApis.sAssertContent('<p>a&nbsp;</p>')
]))
])),
Logger.t('Space key in text', GeneralSteps.sequence([
Logger.t('Press space in middle of text', GeneralSteps.sequence([
tinyApis.sFocus,
tinyApis.sSetContent('<p>ab</p>'),
tinyApis.sSetCursor([0, 0], 1),
tinyActions.sContentKeystroke(Keys.space(), {}),
tinyApis.sAssertSelection([0, 0], 2, [0, 0], 2),
tinyApis.sAssertContent('<p>a b</p>')
])),
Logger.t('Press space after letter preceded by space', GeneralSteps.sequence([
tinyApis.sFocus,
tinyApis.sSetContent('<p>a bc</p>'),
tinyApis.sSetCursor([0, 0], 3),
tinyActions.sContentKeystroke(Keys.space(), {}),
tinyApis.sAssertSelection([0, 0], 4, [0, 0], 4),
tinyApis.sAssertContent('<p>a b c</p>')
])),
Logger.t('Press space before letter followed by space', GeneralSteps.sequence([
tinyApis.sFocus,
tinyApis.sSetContent('<p>ab c</p>'),
tinyApis.sSetCursor([0, 0], 1),
tinyActions.sContentKeystroke(Keys.space(), {}),
tinyApis.sAssertSelection([0, 0], 2, [0, 0], 2),
tinyApis.sAssertContent('<p>a b c</p>')
])),
Logger.t('Press space after letter followed by space in inline element', GeneralSteps.sequence([
tinyApis.sFocus,
tinyApis.sSetContent('<p>a<em> c</em></p>'),
tinyApis.sSetCursor([0, 0], 1),
tinyActions.sContentKeystroke(Keys.space(), {}),
tinyApis.sAssertSelection([0, 0], 2, [0, 0], 2),
tinyApis.sAssertContent('<p>a&nbsp;<em> c</em></p>')
])),
Logger.t('Press space before letter preceded by space in inline element', GeneralSteps.sequence([
tinyApis.sFocus,
tinyApis.sSetContent('<p>a<em>b </em>c</p>'),
tinyApis.sSetCursor([0, 2], 0),
tinyActions.sContentKeystroke(Keys.space(), {}),
tinyApis.sAssertSelection([0, 2], 1, [0, 2], 1),
tinyApis.sAssertContent('<p>a<em>b </em>&nbsp;c</p>')
])),
Logger.t('Press space after letter followed by nbsp in inline element', GeneralSteps.sequence([
tinyApis.sFocus,
tinyApis.sSetContent('<p>a<em>&nbsp;c</em></p>'),
tinyApis.sSetCursor([0, 0], 1),
tinyActions.sContentKeystroke(Keys.space(), {}),
tinyApis.sAssertSelection([0, 0], 2, [0, 0], 2),
tinyApis.sAssertContent('<p>a <em>&nbsp;c</em></p>')
])),
Logger.t('Press space before letter preceded by nbsp in inline element', GeneralSteps.sequence([
tinyApis.sFocus,
tinyApis.sSetContent('<p>a<em>b&nbsp;</em>c</p>'),
tinyApis.sSetCursor([0, 2], 0),
tinyActions.sContentKeystroke(Keys.space(), {}),
tinyApis.sAssertSelection([0, 2], 1, [0, 2], 1),
tinyApis.sAssertContent('<p>a<em>b&nbsp;</em> c</p>')
])),
Logger.t('Press space before nbsp in text', GeneralSteps.sequence([
tinyApis.sFocus,
tinyApis.sSetContent('<p>a&nbsp;b</p>'),
tinyApis.sSetCursor([0, 0], 1),
tinyActions.sContentKeystroke(Keys.space(), {}),
tinyApis.sAssertSelection([0, 0], 2, [0, 0], 2),
tinyApis.sAssertContent('<p>a &nbsp;b</p>')
])),
Logger.t('Press space after nbsp in text', GeneralSteps.sequence([
tinyApis.sFocus,
tinyApis.sSetContent('<p>a&nbsp;b</p>'),
tinyApis.sSetCursor([0, 0], 2),
tinyActions.sContentKeystroke(Keys.space(), {}),
tinyApis.sAssertSelection([0, 0], 3, [0, 0], 3),
tinyApis.sAssertContent('<p>a&nbsp; b</p>')
])),
Logger.t('Press space between two nbsp in text', GeneralSteps.sequence([
tinyApis.sFocus,
tinyApis.sSetContent('<p>a&nbsp;&nbsp;b</p>'),
tinyApis.sSetCursor([0, 0], 2),
tinyActions.sContentKeystroke(Keys.space(), {}),
tinyApis.sAssertSelection([0, 0], 3, [0, 0], 3),
tinyApis.sAssertContent('<p>a&nbsp; &nbsp;b</p>')
])),
Logger.t('Press space before two nbsp in text', GeneralSteps.sequence([
tinyApis.sFocus,
tinyApis.sSetContent('<p>a&nbsp;&nbsp;b</p>'),
tinyApis.sSetCursor([0, 0], 1),
tinyActions.sContentKeystroke(Keys.space(), {}),
tinyApis.sAssertSelection([0, 0], 2, [0, 0], 2),
tinyApis.sAssertContent('<p>a &nbsp;&nbsp;b</p>')
])),
Logger.t('Press space after two nbsp in text', GeneralSteps.sequence([
tinyApis.sFocus,
tinyApis.sSetContent('<p>a&nbsp;&nbsp;b</p>'),
tinyApis.sSetCursor([0, 0], 3),
tinyActions.sContentKeystroke(Keys.space(), {}),
tinyApis.sAssertSelection([0, 0], 4, [0, 0], 4),
tinyApis.sAssertContent('<p>a&nbsp;&nbsp; b</p>')
])),
Logger.t('Press space before letter followed by space in inline element', GeneralSteps.sequence([
tinyApis.sFocus,
tinyApis.sSetContent('<p>ab<em> c</em></p>'),
tinyApis.sSetCursor([0, 0], 1),
tinyActions.sContentKeystroke(Keys.space(), {}),
tinyApis.sAssertSelection([0, 0], 2, [0, 0], 2),
tinyApis.sAssertContent('<p>a b<em> c</em></p>')
])),
Logger.t('Press space after letter preceded by space in inline element', GeneralSteps.sequence([
tinyApis.sFocus,
tinyApis.sSetContent('<p>a<em>b </em>cd</p>'),
tinyApis.sSetCursor([0, 2], 1),
tinyActions.sContentKeystroke(Keys.space(), {}),
tinyApis.sAssertSelection([0, 2], 2, [0, 2], 2),
tinyApis.sAssertContent('<p>a<em>b </em>c d</p>')
]))
])),
Logger.t('Space key in preformatted text', GeneralSteps.sequence([
Logger.t('Press space at start of pre', GeneralSteps.sequence([
tinyApis.sFocus,
tinyApis.sSetContent('<pre>ab</pre>'),
tinyApis.sSetCursor([0, 0], 0),
tinyActions.sContentKeystroke(Keys.space(), {}),
tinyApis.sAssertSelection([0, 0], 1, [0, 0], 1),
tinyApis.sAssertContent('<pre> ab</pre>')
])),
Logger.t('Press space in middle of text', GeneralSteps.sequence([
tinyApis.sFocus,
tinyApis.sSetContent('<pre>ab</pre>'),
tinyApis.sSetCursor([0, 0], 1),
tinyActions.sContentKeystroke(Keys.space(), {}),
tinyApis.sAssertSelection([0, 0], 2, [0, 0], 2),
tinyApis.sAssertContent('<pre>a b</pre>')
])),
Logger.t('Press space at end of pre', GeneralSteps.sequence([
tinyApis.sFocus,
tinyApis.sSetContent('<pre>ab</pre>'),
tinyApis.sSetCursor([0, 0], 2),
tinyActions.sContentKeystroke(Keys.space(), {}),
tinyApis.sAssertSelection([0, 0], 3, [0, 0], 3),
tinyApis.sAssertContent('<pre>ab </pre>')
])),
Logger.t('Press space in after space', GeneralSteps.sequence([
tinyApis.sFocus,
tinyApis.sSetContent('<pre>a b</pre>'),
tinyApis.sSetCursor([0, 0], 2),
tinyActions.sContentKeystroke(Keys.space(), {}),
tinyApis.sAssertSelection([0, 0], 3, [0, 0], 3),
tinyApis.sAssertContent('<pre>a b</pre>')
])),
Logger.t('Press space in before space', GeneralSteps.sequence([
tinyApis.sFocus,
tinyApis.sSetContent('<pre>a b</pre>'),
tinyApis.sSetCursor([0, 0], 1),
tinyActions.sContentKeystroke(Keys.space(), {}),
tinyApis.sAssertSelection([0, 0], 2, [0, 0], 2),
tinyApis.sAssertContent('<pre>a b</pre>')
]))
])),
Logger.t('Space key at br', GeneralSteps.sequence([
Logger.t('Press space between two br:s in block', GeneralSteps.sequence([
tinyApis.sFocus,
tinyApis.sSetRawContent('<p><br><br></p>'),
tinyApis.sSetCursor([0], 1),
tinyActions.sContentKeystroke(Keys.space(), {}),
tinyApis.sAssertSelection([0, 1], 1, [0, 1], 1),
tinyApis.sAssertContent('<p><br />&nbsp;</p>')
])),
Logger.t('Press space after br in beginning of text node', GeneralSteps.sequence([
tinyApis.sFocus,
tinyApis.sSetRawContent('<p>a<br />b</p>'),
tinyApis.sSetCursor([0, 2], 0),
tinyActions.sContentKeystroke(Keys.space(), {}),
tinyApis.sAssertSelection([0, 2], 1, [0, 2], 1),
tinyApis.sAssertContent('<p>a<br />&nbsp;b</p>')
])),
Logger.t('Press space before br in beginning of text node', GeneralSteps.sequence([
tinyApis.sFocus,
tinyApis.sSetRawContent('<p><br />b</p>'),
tinyApis.sSetCursor([0], 0),
tinyActions.sContentKeystroke(Keys.space(), {}),
tinyApis.sAssertSelection([0, 0], 1, [0, 0], 1),
tinyApis.sAssertContent('<p>&nbsp;<br />b</p>')
]))
])),
Logger.t('Space key at node indexes', GeneralSteps.sequence([
Logger.t('Press space before image element in block', GeneralSteps.sequence([
tinyApis.sFocus,
tinyApis.sSetRawContent(`<p>${img}</p>`),
tinyApis.sSetCursor([0], 0),
tinyActions.sContentKeystroke(Keys.space(), {}),
tinyApis.sAssertSelection([0, 0], 1, [0, 0], 1),
tinyApis.sAssertContent(`<p>&nbsp;${img}</p>`)
])),
Logger.t('Press space between two image elements in block', GeneralSteps.sequence([
tinyApis.sFocus,
tinyApis.sSetRawContent(`<p>${img}${img}</p>`),
tinyApis.sSetCursor([0], 1),
tinyActions.sContentKeystroke(Keys.space(), {}),
tinyApis.sAssertSelection([0, 1], 1, [0, 1], 1),
tinyApis.sAssertContent(`<p>${img} ${img}</p>`)
])),
Logger.t('Press space after image element in block', GeneralSteps.sequence([
tinyApis.sFocus,
tinyApis.sSetRawContent(`<p>${img}</p>`),
tinyApis.sSetCursor([0], 1),
tinyActions.sContentKeystroke(Keys.space(), {}),
tinyApis.sAssertSelection([0, 1], 1, [0, 1], 1),
tinyApis.sAssertContent(`<p>${img}&nbsp;</p>`)
]))
]))
], onSuccess, onFailure);
}, {
indent: false,
skin_url: '/project/js/tinymce/skins/lightgray'
}, success, failure);
});

View File

@ -1,58 +0,0 @@
import { LegacyUnit } from '@ephox/mcagar';
import { Pipeline } from '@ephox/agar';
import Fun from 'tinymce/core/util/Fun';
import { UnitTest } from '@ephox/bedrock';
UnitTest.asynctest('browser.tinymce.core.util.FunTest', function () {
const success = arguments[arguments.length - 2];
const failure = arguments[arguments.length - 1];
const suite = LegacyUnit.createSuite();
const isTrue = function (value) {
return value === true;
};
const isFalse = function (value) {
return value === true;
};
const isAbove = function (target, value) {
return value() > target();
};
suite.test('constant', function () {
LegacyUnit.strictEqual(Fun.constant(1)(), 1);
LegacyUnit.strictEqual(Fun.constant('1')(), '1');
LegacyUnit.strictEqual(Fun.constant(null)(), null);
});
suite.test('negate', function () {
LegacyUnit.strictEqual(Fun.negate(isTrue)(false), true);
LegacyUnit.strictEqual(Fun.negate(isFalse)(true), false);
});
suite.test('and', function () {
const isAbove5 = Fun.curry(isAbove, Fun.constant(5));
const isAbove10 = Fun.curry(isAbove, Fun.constant(10));
LegacyUnit.strictEqual(Fun.and(isAbove10, isAbove5)(Fun.constant(10)), false);
LegacyUnit.strictEqual(Fun.and(isAbove10, isAbove5)(Fun.constant(30)), true);
});
suite.test('or', function () {
const isAbove5 = Fun.curry(isAbove, Fun.constant(5));
const isAbove10 = Fun.curry(isAbove, Fun.constant(10));
LegacyUnit.strictEqual(Fun.or(isAbove10, isAbove5)(Fun.constant(5)), false);
LegacyUnit.strictEqual(Fun.or(isAbove10, isAbove5)(Fun.constant(15)), true);
LegacyUnit.strictEqual(Fun.or(isAbove5, isAbove10)(Fun.constant(15)), true);
});
suite.test('compose', function () {
LegacyUnit.strictEqual(Fun.compose(Fun.curry(isAbove, Fun.constant(5)), Fun.constant)(10), true);
});
Pipeline.async({}, suite.toSteps({}), function () {
success();
}, failure);
});

View File

@ -1,21 +0,0 @@
/**
* Commands.js
*
* Released under LGPL License.
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved
*
* License: http://www.tinymce.com/license
* Contributing: http://www.tinymce.com/contributing
*/
import Dialog from '../ui/Dialog';
const register = function (editor) {
editor.addCommand('mceAnchor', function () {
Dialog.open(editor);
});
};
export default {
register
};

View File

@ -1,21 +0,0 @@
/**
* Commands.js
*
* Released under LGPL License.
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved
*
* License: http://www.tinymce.com/license
* Contributing: http://www.tinymce.com/contributing
*/
import Resize from '../core/Resize';
const register = function (editor, oldSize) {
editor.addCommand('mceAutoResize', function () {
Resize.resize(editor, oldSize);
});
};
export default {
register
};

View File

@ -1,18 +0,0 @@
/**
* Settings.js
*
* Released under LGPL License.
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved
*
* License: http://www.tinymce.com/license
* Contributing: http://www.tinymce.com/contributing
*/
const getDialect = function (editor) {
// Note: This option isn't even used since we only support one dialect
return editor.getParam('bbcode_dialect', 'punbb').toLowerCase();
};
export default {
getDialect
};

View File

@ -1,21 +0,0 @@
/**
* Commands.js
*
* Released under LGPL License.
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved
*
* License: http://www.tinymce.com/license
* Contributing: http://www.tinymce.com/contributing
*/
import Dialog from '../ui/Dialog';
const register = function (editor) {
editor.addCommand('mceShowCharmap', function () {
Dialog.open(editor);
});
};
export default {
register
};

View File

@ -1,17 +0,0 @@
/**
* Events.js
*
* Released under LGPL License.
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved
*
* License: http://www.tinymce.com/license
* Contributing: http://www.tinymce.com/contributing
*/
const fireInsertCustomChar = function (editor, chr) {
return editor.fire('insertCustomChar', { chr });
};
export default {
fireInsertCustomChar
};

View File

@ -1,22 +0,0 @@
/**
* Settings.js
*
* Released under LGPL License.
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved
*
* License: http://www.tinymce.com/license
* Contributing: http://www.tinymce.com/contributing
*/
const getCharMap = function (editor) {
return editor.settings.charmap;
};
const getCharMapAppend = function (editor) {
return editor.settings.charmap_append;
};
export default {
getCharMap,
getCharMapAppend
};

View File

@ -1,21 +0,0 @@
/**
* Commands.js
*
* Released under LGPL License.
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved
*
* License: http://www.tinymce.com/license
* Contributing: http://www.tinymce.com/contributing
*/
import Dialog from '../ui/Dialog';
const register = function (editor) {
editor.addCommand('mceCodeEditor', function () {
Dialog.open(editor);
});
};
export default {
register
};

View File

@ -1,23 +0,0 @@
/**
* Api.js
*
* Released under LGPL License.
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved
*
* License: http://www.tinymce.com/license
* Contributing: http://www.tinymce.com/contributing
*/
const get = function (visibleState) {
const isContextMenuVisible = function () {
return visibleState.get();
};
return {
isContextMenuVisible
};
};
export default {
get
};

View File

@ -1,21 +0,0 @@
/**
* Commands.js
*
* Released under LGPL License.
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved
*
* License: http://www.tinymce.com/license
* Contributing: http://www.tinymce.com/contributing
*/
import Dialog from '../ui/Dialog';
const register = function (editor, headState) {
editor.addCommand('mceFullPageProperties', function () {
Dialog.open(editor, headState);
});
};
export default {
register
};

View File

@ -1,21 +0,0 @@
/**
* Api.js
*
* Released under LGPL License.
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved
*
* License: http://www.tinymce.com/license
* Contributing: http://www.tinymce.com/contributing
*/
const get = function (fullscreenState) {
return {
isFullscreen () {
return fullscreenState.get() !== null;
}
};
};
export default {
get
};

View File

@ -1,17 +0,0 @@
/**
* Events.js
*
* Released under LGPL License.
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved
*
* License: http://www.tinymce.com/license
* Contributing: http://www.tinymce.com/contributing
*/
const fireFullscreenStateChanged = function (editor, state) {
editor.fire('FullscreenStateChanged', { state });
};
export default {
fireFullscreenStateChanged
};

View File

@ -1,19 +0,0 @@
/**
* Commands.js
*
* Released under LGPL License.
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved
*
* License: http://www.tinymce.com/license
* Contributing: http://www.tinymce.com/contributing
*/
import Dialog from '../ui/Dialog';
const register = function (editor, pluginUrl) {
editor.addCommand('mceHelp', Dialog.open(editor, pluginUrl));
};
export default {
register
};

View File

@ -1,19 +0,0 @@
/**
* Commands.js
*
* Released under LGPL License.
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved
*
* License: http://www.tinymce.com/license
* Contributing: http://www.tinymce.com/contributing
*/
const register = function (editor) {
editor.addCommand('InsertHorizontalRule', function () {
editor.execCommand('mceInsertContent', false, '<hr />');
});
};
export default {
register
};

View File

@ -1,19 +0,0 @@
/**
* Commands.js
*
* Released under LGPL License.
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved
*
* License: http://www.tinymce.com/license
* Contributing: http://www.tinymce.com/contributing
*/
import Dialog from '../ui/Dialog';
const register = function (editor) {
editor.addCommand('mceImage', Dialog(editor).open);
};
export default {
register
};

View File

@ -1,19 +0,0 @@
/**
* Commands.js
*
* Released under LGPL License.
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved
*
* License: http://www.tinymce.com/license
* Contributing: http://www.tinymce.com/contributing
*/
import Actions from '../core/Actions';
const register = function (editor) {
editor.addCommand('mceLink', Actions.openDialog(editor));
};
export default {
register
};

View File

@ -1,19 +0,0 @@
/**
* Keyboard.js
*
* Released under LGPL License.
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved
*
* License: http://www.tinymce.com/license
* Contributing: http://www.tinymce.com/contributing
*/
import Actions from './Actions';
const setup = function (editor) {
editor.addShortcut('Meta+K', '', Actions.openDialog(editor));
};
export default {
setup
};

View File

@ -1,75 +0,0 @@
/**
* Indentation.js
*
* Released under LGPL License.
* Copyright (c) 1999-2018 Ephox Corp. All rights reserved
*
* License: http://www.tinymce.com/license
* Contributing: http://www.tinymce.com/contributing
*/
import { Arr } from '@ephox/katamari';
import { Compare, Element, Replication, Traverse } from '@ephox/sugar';
import { Editor } from 'tinymce/core/api/Editor';
import Range from '../core/Range';
import Selection from '../core/Selection';
import SplitList from '../core/SplitList';
import { IndentValue } from '../listModel/Indentation';
import { listsIndentation } from '../listModel/ListsIndendation';
const outdentDlItem = (editor: Editor, item: Element): void => {
if (Compare.is(item, 'DD')) {
Replication.mutate(item, 'DT');
} else if (Compare.is(item, 'DT')) {
Traverse.parent(item).each((dl) => SplitList.splitList(editor, dl.dom(), item.dom()));
}
};
const indentDlItem = (item: Element): void => {
if (Compare.is(item, 'DT')) {
Replication.mutate(item, 'DD');
}
};
const dlIndentation = (editor: Editor, indentation: IndentValue, dlItems: Element[]) => {
if (indentation === IndentValue.Indent) {
Arr.each(dlItems, indentDlItem);
} else {
Arr.each(dlItems, (item) => outdentDlItem(editor, item));
}
};
const selectionIndentation = (editor: Editor, indentation: IndentValue) => {
const dlItems = Arr.map(Selection.getSelectedDlItems(editor), Element.fromDom);
const lists = Arr.map(Selection.getSelectedListRoots(editor), Element.fromDom);
if (dlItems.length || lists.length) {
const bookmark = editor.selection.getBookmark();
dlIndentation(editor, indentation, dlItems);
listsIndentation(
editor,
lists,
indentation
);
editor.selection.moveToBookmark(bookmark);
editor.selection.setRng(Range.normalizeRange(editor.selection.getRng()));
editor.nodeChanged();
}
};
const indentListSelection = (editor: Editor) => {
selectionIndentation(editor, IndentValue.Indent);
};
const outdentListSelection = (editor: Editor) => {
selectionIndentation(editor, IndentValue.Outdent);
};
const flattenListSelection = (editor: Editor) => {
selectionIndentation(editor, IndentValue.Flatten);
};
export { indentListSelection, outdentListSelection, flattenListSelection };

View File

@ -1,23 +0,0 @@
/**
* Api.js
*
* Released under LGPL License.
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved
*
* License: http://www.tinymce.com/license
* Contributing: http://www.tinymce.com/contributing
*/
import Delete from '../core/Delete';
const get = function (editor) {
return {
backspaceDelete (isForward) {
Delete.backspaceDelete(editor, isForward);
}
};
};
export default {
get
};

View File

@ -1,17 +0,0 @@
/**
* Settings.js
*
* Released under LGPL License.
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved
*
* License: http://www.tinymce.com/license
* Contributing: http://www.tinymce.com/contributing
*/
const shouldIndentOnTab = function (editor) {
return editor.getParam('lists_indent_on_tab', true);
};
export default {
shouldIndentOnTab
};

View File

@ -1,106 +0,0 @@
/**
* ComposeList.js
*
* Released under LGPL License.
* Copyright (c) 1999-2018 Ephox Corp. All rights reserved
*
* License: http://www.tinymce.com/license
* Contributing: http://www.tinymce.com/contributing
*/
import { Entry } from './Entry';
import { Element, Insert, InsertAll, Attr, Css, Node, Replication } from '@ephox/sugar';
import { Arr, Option, Options } from '@ephox/katamari';
import { ListType } from './ListType';
interface Section {
list: Element;
item: Element;
}
const createSection = (listType: ListType): Section => {
const section: Section = {
list: Element.fromTag(listType),
item: Element.fromTag('li')
};
Insert.append(section.list, section.item);
return section;
};
const joinSections = (parent: Section, appendor: Section): void => {
Insert.append(parent.item, appendor.list);
};
const createJoinedSections = (length: number, listType: ListType): Section[] => {
const sections: Section[] = [];
for (let i = 0; i < length; i++) {
const newSection = createSection(listType);
Arr.last(sections).each((lastSection) => joinSections(lastSection, newSection));
sections.push(newSection);
}
return sections;
};
const normalizeSection = (section: Section, entry: Entry): void => {
if (Node.name(section.list).toUpperCase() !== entry.listType) {
section.list = Replication.mutate(section.list, entry.listType);
}
Attr.setAll(section.list, entry.listAttributes);
};
const createItem = (attr: Record<string, any>, content: Element[]): Element => {
const item = Element.fromTag('li');
Attr.setAll(item, attr);
InsertAll.append(item, content);
return item;
};
const setItem = (section: Section, item: Element): void => {
Insert.append(section.list, item);
section.item = item;
};
const writeShallow = (outline: Section[], entry: Entry): Section[] => {
const newOutline = outline.slice(0, entry.depth);
Arr.last(newOutline).each((section) => {
setItem(section, createItem(entry.itemAttributes, entry.content));
normalizeSection(section, entry);
});
return newOutline;
};
const populateSections = (sections: Section[], entry: Entry): void => {
Arr.last(sections).each((section) => {
Attr.setAll(section.list, entry.listAttributes);
Attr.setAll(section.item, entry.itemAttributes);
InsertAll.append(section.item, entry.content);
});
for (let i = 0; i < sections.length - 1; i++) {
Css.set(sections[i].item, 'list-style-type', 'none');
}
};
const writeDeep = (outline: Section[], entry: Entry): Section[] => {
const newSections = createJoinedSections(entry.depth - outline.length, entry.listType);
populateSections(newSections, entry);
Options.liftN([
Arr.last(outline),
Arr.head(newSections)
], joinSections);
return outline.concat(newSections);
};
const composeList = (entries: Entry[]): Option<Element> => {
const outline: Section[] = Arr.foldl(entries, (outline, entry) => {
return entry.depth > outline.length ? writeDeep(outline, entry) : writeShallow(outline, entry);
}, []);
return Arr.head(outline).map((section) => section.list);
};
export {
composeList
};

View File

@ -1,43 +0,0 @@
/**
* Entry.js
*
* Released under LGPL License.
* Copyright (c) 1999-2018 Ephox Corp. All rights reserved
*
* License: http://www.tinymce.com/license
* Contributing: http://www.tinymce.com/contributing
*/
import { Element } from '@ephox/sugar';
import { ListType } from './ListType';
/*
General workflow: Parse lists to entries -> Manipulate entries -> Compose entries to lists
0-------1---2--------->Depth
<ol> |
<li>a</li> | Entry { depth: 1, content: [a], listType: ListType.OL, ... }
<li>b | Entry { depth: 1, content: [b], listType: ListType.OL, ... }
<ul> |
<li>c</li> | Entry { depth: 2, content: [c], listType: ListType.UL, ... }
</ul> |
</li> |
</ol> |
0-------1---2--------->Depth
*/
export interface Entry {
depth: number;
content: Element[];
isSelected: boolean;
listType: ListType;
listAttributes: Record<string, any>;
itemAttributes: Record<string, any>;
}
export const isIndented = (entry: Entry) => {
return entry.depth > 0;
};
export const isSelected = (entry: Entry) => {
return entry.isSelected;
};

View File

@ -1,32 +0,0 @@
/**
* Indentation.js
*
* Released under LGPL License.
* Copyright (c) 1999-2018 Ephox Corp. All rights reserved
*
* License: http://www.tinymce.com/license
* Contributing: http://www.tinymce.com/contributing
*/
import { Entry } from './Entry';
export const enum IndentValue {
Indent = 'Indent',
Outdent = 'Outdent',
Flatten = 'Flatten'
}
export const indentEntry = (indentation: IndentValue, entry: Entry): void => {
switch (indentation) {
case IndentValue.Indent:
entry.depth ++;
break;
case IndentValue.Outdent:
entry.depth --;
break;
case IndentValue.Flatten:
entry.depth = 0;
}
};

View File

@ -1,39 +0,0 @@
/**
* ListType.js
*
* Released under LGPL License.
* Copyright (c) 1999-2018 Ephox Corp. All rights reserved
*
* License: http://www.tinymce.com/license
* Contributing: http://www.tinymce.com/contributing
*/
import { Element, Compare, Node } from '@ephox/sugar';
import { Option } from '@ephox/katamari';
export enum ListType {
OL = 'OL',
UL = 'UL',
DL = 'DL',
}
const getListType = (list: Element): Option<ListType> => {
switch (Node.name(list)) {
case 'ol':
return Option.some(ListType.OL);
case 'ul':
return Option.some(ListType.UL);
case 'dl':
return Option.some(ListType.DL);
default:
return Option.none();
}
};
const isList = (el: Element) => {
return Compare.is(el, 'OL,UL,DL');
};
export {
isList,
getListType
};

View File

@ -1,47 +0,0 @@
/**
* NormalizeEntries.js
*
* Released under LGPL License.
* Copyright (c) 1999-2018 Ephox Corp. All rights reserved
*
* License: http://www.tinymce.com/license
* Contributing: http://www.tinymce.com/contributing
*/
import { Entry } from './Entry';
import { Arr, Merger, Option } from '@ephox/katamari';
const assimilateEntry = (adherent: Entry, source: Entry) => {
adherent.listType = source.listType;
adherent.listAttributes = Merger.merge({}, source.listAttributes);
adherent.itemAttributes = Merger.merge({}, source.itemAttributes);
};
const normalizeShallow = (outline: Array<Option<Entry>>, entry: Entry): Array<Option<Entry>> => {
const matchingEntryDepth = entry.depth - 1;
outline[matchingEntryDepth].each((matchingEntry) => assimilateEntry(entry, matchingEntry));
const newOutline = outline.slice(0, matchingEntryDepth);
newOutline.push(Option.some(entry));
return newOutline;
};
const normalizeDeep = (outline: Array<Option<Entry>>, entry: Entry): Array<Option<Entry>> => {
const newOutline = outline.slice(0);
const diff = entry.depth - outline.length;
for (let i = 1; i < diff; i++) {
newOutline.push(Option.none());
}
newOutline.push(Option.some(entry));
return newOutline;
};
const normalizeEntries = (entries: Entry[]): void => {
Arr.foldl(entries, (outline: Array<Option<Entry>>, entry) => {
return entry.depth > outline.length ? normalizeDeep(outline, entry) : normalizeShallow(outline, entry);
}, []);
};
export {
normalizeEntries
};

View File

@ -1,88 +0,0 @@
/**
* ParseLists.js
*
* Released under LGPL License.
* Copyright (c) 1999-2018 Ephox Corp. All rights reserved
*
* License: http://www.tinymce.com/license
* Contributing: http://www.tinymce.com/contributing
*/
import { Arr, Cell, Fun, Option } from '@ephox/katamari';
import { Attr, Compare, Element, Replication, Traverse } from '@ephox/sugar';
import { getListType, isList, ListType } from './ListType';
import { Entry } from './Entry';
import { hasLastChildList } from './Util';
type Parser = (depth: number, itemSelection: Option<ItemTuple>, selectionState: Cell<boolean>, el: Element) => Entry[];
export interface ItemTuple {
start: Element;
end: Element;
}
export interface EntrySet {
entries: Entry[];
sourceList: Element;
}
const enum ItemRange {
Start = 'Start',
End = 'End'
}
const getItemContent = (li: Element): Element[] => {
const childNodes = Traverse.children(li);
const contentLength = childNodes.length + (hasLastChildList(li) ? -1 : 0);
return Arr.map(childNodes.slice(0, contentLength), Replication.deep);
};
const createEntry = (li: Element, depth: number, isSelected: boolean): Entry => {
const list = Traverse.parent(li);
return {
depth,
isSelected,
content: getItemContent(li),
listType: list.bind(getListType).getOr(ListType.OL),
listAttributes: list.map(Attr.clone).getOr({}),
itemAttributes: Attr.clone(li)
};
};
const parseItem: Parser = (depth: number, itemSelection: Option<ItemTuple>, selectionState: Cell<boolean>, item: Element): Entry[] => {
const curriedParseList = Fun.curry(parseList, depth, itemSelection, selectionState);
const updateSelectionState = (itemRange: ItemRange) => itemSelection.each((selection) => {
if (Compare.eq(itemRange === ItemRange.Start ? selection.start : selection.end, item)) {
selectionState.set(itemRange === ItemRange.Start);
}
});
return Traverse.firstChild(item).filter(isList).fold(() => {
updateSelectionState(ItemRange.Start);
const fromCurrentItem: Entry = createEntry(item, depth, selectionState.get());
updateSelectionState(ItemRange.End);
const fromChildList: Entry[] = Traverse.lastChild(item).filter(isList).map(curriedParseList).getOr([]);
return [ fromCurrentItem, ...fromChildList ];
}, curriedParseList);
};
const parseList: Parser = (depth: number, itemSelection: Option<ItemTuple>, selectionState: Cell<boolean>, list: Element): Entry[] => {
const newDepth = depth + 1;
return Arr.bind(Traverse.children(list), (child) =>
isList(child) ? parseList(newDepth, itemSelection, selectionState, child) : parseItem(newDepth, itemSelection, selectionState, child)
);
};
const parseLists = (lists: Element[], itemSelection: Option<ItemTuple>): EntrySet[] => {
const selectionState = Cell(false);
const initialDepth = 0;
return Arr.map(lists, (list) => ({
entries: parseList(initialDepth, itemSelection, selectionState, list),
sourceList: list
}));
};
export { parseLists };

View File

@ -1,25 +0,0 @@
/**
* Util.js
*
* Released under LGPL License.
* Copyright (c) 1999-2018 Ephox Corp. All rights reserved
*
* License: http://www.tinymce.com/license
* Contributing: http://www.tinymce.com/contributing
*/
import { Element, Traverse } from '@ephox/sugar';
import { isList } from './ListType';
const hasFirstChildList = (li: Element) => {
return Traverse.firstChild(li).map(isList).getOr(false);
};
const hasLastChildList = (li: Element) => {
return Traverse.lastChild(li).map(isList).getOr(false);
};
export {
hasFirstChildList,
hasLastChildList
};

View File

@ -1,25 +0,0 @@
/**
* Api.js
*
* Released under LGPL License.
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved
*
* License: http://www.tinymce.com/license
* Contributing: http://www.tinymce.com/contributing
*/
import Dialog from '../ui/Dialog';
const get = function (editor) {
const showDialog = function () {
Dialog.showDialog(editor);
};
return {
showDialog
};
};
export default {
get
};

View File

@ -1,23 +0,0 @@
/**
* Commands.js
*
* Released under LGPL License.
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved
*
* License: http://www.tinymce.com/license
* Contributing: http://www.tinymce.com/contributing
*/
import Dialog from '../ui/Dialog';
const register = function (editor) {
const showDialog = function () {
Dialog.showDialog(editor);
};
editor.addCommand('mceMedia', showDialog);
};
export default {
register
};

View File

@ -1,21 +0,0 @@
/**
* Commands.js
*
* Released under LGPL License.
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved
*
* License: http://www.tinymce.com/license
* Contributing: http://www.tinymce.com/contributing
*/
import Actions from '../core/Actions';
const register = function (editor) {
editor.addCommand('mceNonBreaking', function () {
Actions.insertNbsp(editor, 1);
});
};
export default {
register
};

View File

@ -1,18 +0,0 @@
/**
* Plugin.js
*
* Released under LGPL License.
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved
*
* License: http://www.tinymce.com/license
* Contributing: http://www.tinymce.com/contributing
*/
import PluginManager from 'tinymce/core/api/PluginManager';
import FilterContent from './core/FilterContent';
PluginManager.add('noneditable', function (editor) {
FilterContent.setup(editor);
});
export default function () { }

View File

@ -1,22 +0,0 @@
/**
* Api.js
*
* Released under LGPL License.
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved
*
* License: http://www.tinymce.com/license
* Contributing: http://www.tinymce.com/contributing
*/
import { Clipboard } from '../api/Clipboard';
const get = function (clipboard: Clipboard, quirks) {
return {
clipboard,
quirks
};
};
export default {
get
};

View File

@ -1,21 +0,0 @@
/**
* Commands.js
*
* Released under LGPL License.
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved
*
* License: http://www.tinymce.com/license
* Contributing: http://www.tinymce.com/contributing
*/
import Dialog from '../ui/Dialog';
const register = function (editor) {
editor.addCommand('mcePreview', function () {
Dialog.open(editor);
});
};
export default {
register
};

View File

@ -1,19 +0,0 @@
/**
* Commands.js
*
* Released under LGPL License.
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved
*
* License: http://www.tinymce.com/license
* Contributing: http://www.tinymce.com/contributing
*/
const register = function (editor) {
editor.addCommand('mcePrint', function () {
editor.getWin().print();
});
};
export default {
register
};

View File

@ -1,21 +0,0 @@
/**
* Commands.js
*
* Released under LGPL License.
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved
*
* License: http://www.tinymce.com/license
* Contributing: http://www.tinymce.com/contributing
*/
import Dialog from '../ui/Dialog';
const register = function (editor, currentIndexState) {
editor.addCommand('SearchReplace', function () {
Dialog.open(editor, currentIndexState);
});
};
export default {
register
};

View File

@ -1,18 +0,0 @@
/**
* Plugin.js
*
* Released under LGPL License.
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved
*
* License: http://www.tinymce.com/license
* Contributing: http://www.tinymce.com/contributing
*/
import PluginManager from 'tinymce/core/api/PluginManager';
import Keyboard from './core/Keyboard';
PluginManager.add('tabfocus', function (editor) {
Keyboard.setup(editor);
});
export default function () { }

View File

@ -1,25 +0,0 @@
/**
* Plugin.js
*
* Released under LGPL License.
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved
*
* License: http://www.tinymce.com/license
* Contributing: http://www.tinymce.com/contributing
*/
import { Cell } from '@ephox/katamari';
import PluginManager from 'tinymce/core/api/PluginManager';
import Api from './api/Api';
import Settings from './api/Settings';
import Keyboard from './core/Keyboard';
PluginManager.add('textpattern', function (editor) {
const patternsState = Cell(Settings.getPatterns(editor.settings));
Keyboard.setup(editor, patternsState);
return Api.get(patternsState);
});
export default function () { }

View File

@ -1,28 +0,0 @@
/**
* Api.js
*
* Released under LGPL License.
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved
*
* License: http://www.tinymce.com/license
* Contributing: http://www.tinymce.com/contributing
*/
const get = function (patternsState) {
const setPatterns = function (newPatterns) {
patternsState.set(newPatterns);
};
const getPatterns = function () {
return patternsState.get();
};
return {
setPatterns,
getPatterns
};
};
export default {
get
};

View File

@ -1,34 +0,0 @@
/**
* Settings.js
*
* Released under LGPL License.
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved
*
* License: http://www.tinymce.com/license
* Contributing: http://www.tinymce.com/contributing
*/
const defaultPatterns = [
{ start: '*', end: '*', format: 'italic' },
{ start: '**', end: '**', format: 'bold' },
{ start: '***', end: '***', format: ['bold', 'italic'] },
{ start: '#', format: 'h1' },
{ start: '##', format: 'h2' },
{ start: '###', format: 'h3' },
{ start: '####', format: 'h4' },
{ start: '#####', format: 'h5' },
{ start: '######', format: 'h6' },
{ start: '1. ', cmd: 'InsertOrderedList' },
{ start: '* ', cmd: 'InsertUnorderedList' },
{ start: '- ', cmd: 'InsertUnorderedList' }
];
const getPatterns = function (editorSettings) {
return editorSettings.textpattern_patterns !== undefined ?
editorSettings.textpattern_patterns :
defaultPatterns;
};
export default {
getPatterns
};

View File

@ -1,175 +0,0 @@
/**
* Formatter.js
*
* Released under LGPL License.
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved
*
* License: http://www.tinymce.com/license
* Contributing: http://www.tinymce.com/contributing
*/
import TreeWalker from 'tinymce/core/api/dom/TreeWalker';
import Tools from 'tinymce/core/api/util/Tools';
import Patterns from './Patterns';
import { document } from '@ephox/dom-globals';
const splitContainer = function (container, pattern, endOffset, startOffset, space) {
// Split text node and remove start/end from text node
container = startOffset > 0 ? container.splitText(startOffset) : container;
container.splitText(endOffset - startOffset + pattern.end.length);
container.deleteData(0, pattern.start.length);
container.deleteData(container.data.length - pattern.end.length, pattern.end.length);
return container;
};
const patternFromRng = function (patterns, rng, space) {
if (rng.collapsed === false) {
return;
}
const container = rng.startContainer;
const text = container.data;
const delta = space === true ? 1 : 0;
if (container.nodeType !== 3) {
return;
}
// Find best matching end
const endPattern = Patterns.findEndPattern(patterns, text, rng.startOffset, delta);
if (endPattern === undefined) {
return;
}
// Find start of matched pattern
let endOffset = text.lastIndexOf(endPattern.end, rng.startOffset - delta);
const startOffset = text.lastIndexOf(endPattern.start, endOffset - endPattern.end.length);
endOffset = text.indexOf(endPattern.end, startOffset + endPattern.start.length);
if (startOffset === -1) {
return;
}
// Setup a range for the matching word
const patternRng = document.createRange();
patternRng.setStart(container, startOffset);
patternRng.setEnd(container, endOffset + endPattern.end.length);
const startPattern = Patterns.findPattern(patterns, patternRng.toString());
if (endPattern === undefined || startPattern !== endPattern || (container.data.length <= endPattern.start.length + endPattern.end.length)) {
return;
}
return {
pattern: endPattern,
startOffset,
endOffset
};
};
const splitAndApply = function (editor, container, found, space) {
const formatArray = Tools.isArray(found.pattern.format) ? found.pattern.format : [found.pattern.format];
const validFormats = Tools.grep(formatArray, function (formatName) {
const format = editor.formatter.get(formatName);
return format && format[0].inline;
});
if (validFormats.length !== 0) {
editor.undoManager.transact(function () {
container = splitContainer(container, found.pattern, found.endOffset, found.startOffset, space);
formatArray.forEach(function (format) {
editor.formatter.apply(format, {}, container);
});
});
return container;
}
};
// Handles inline formats like *abc* and **abc**
const doApplyInlineFormat = function (editor, patterns, space) {
const rng = editor.selection.getRng(true);
const foundPattern = patternFromRng(patterns, rng, space);
if (foundPattern) {
return splitAndApply(editor, rng.startContainer, foundPattern, space);
}
};
const applyInlineFormatSpace = function (editor, patterns) {
return doApplyInlineFormat(editor, patterns, true);
};
const applyInlineFormatEnter = function (editor, patterns) {
return doApplyInlineFormat(editor, patterns, false);
};
// Handles block formats like ##abc or 1. abc
const applyBlockFormat = function (editor, patterns) {
let selection, dom, container, firstTextNode, node, format, textBlockElm, pattern, walker, rng, offset;
selection = editor.selection;
dom = editor.dom;
if (!selection.isCollapsed()) {
return;
}
textBlockElm = dom.getParent(selection.getStart(), 'p');
if (textBlockElm) {
walker = new TreeWalker(textBlockElm, textBlockElm);
while ((node = walker.next())) {
if (node.nodeType === 3) {
firstTextNode = node;
break;
}
}
if (firstTextNode) {
pattern = Patterns.findPattern(patterns, firstTextNode.data);
if (!pattern) {
return;
}
rng = selection.getRng(true);
container = rng.startContainer;
offset = rng.startOffset;
if (firstTextNode === container) {
offset = Math.max(0, offset - pattern.start.length);
}
if (Tools.trim(firstTextNode.data).length === pattern.start.length) {
return;
}
if (pattern.format) {
format = editor.formatter.get(pattern.format);
if (format && format[0].block) {
firstTextNode.deleteData(0, pattern.start.length);
editor.formatter.apply(pattern.format, {}, firstTextNode);
rng.setStart(container, offset);
rng.collapse(true);
selection.setRng(rng);
}
}
if (pattern.cmd) {
editor.undoManager.transact(function () {
firstTextNode.deleteData(0, pattern.start.length);
editor.execCommand(pattern.cmd);
});
}
}
}
};
export default {
patternFromRng,
applyInlineFormatSpace,
applyInlineFormatEnter,
applyBlockFormat
};

View File

@ -1,76 +0,0 @@
/**
* KeyHandler.js
*
* Released under LGPL License.
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved
*
* License: http://www.tinymce.com/license
* Contributing: http://www.tinymce.com/contributing
*/
import VK from 'tinymce/core/api/util/VK';
import Formatter from './Formatter';
function handleEnter(editor, patterns) {
let wrappedTextNode, rng;
wrappedTextNode = Formatter.applyInlineFormatEnter(editor, patterns);
if (wrappedTextNode) {
rng = editor.dom.createRng();
rng.setStart(wrappedTextNode, wrappedTextNode.data.length);
rng.setEnd(wrappedTextNode, wrappedTextNode.data.length);
editor.selection.setRng(rng);
}
Formatter.applyBlockFormat(editor, patterns);
}
function handleInlineKey(editor, patterns) {
let wrappedTextNode, lastChar, lastCharNode, rng, dom;
wrappedTextNode = Formatter.applyInlineFormatSpace(editor, patterns);
if (wrappedTextNode) {
dom = editor.dom;
lastChar = wrappedTextNode.data.slice(-1);
// Move space after the newly formatted node
if (/[\u00a0 ]/.test(lastChar)) {
wrappedTextNode.deleteData(wrappedTextNode.data.length - 1, 1);
lastCharNode = dom.doc.createTextNode(lastChar);
dom.insertAfter(lastCharNode, wrappedTextNode.parentNode);
rng = dom.createRng();
rng.setStart(lastCharNode, 1);
rng.setEnd(lastCharNode, 1);
editor.selection.setRng(rng);
}
}
}
const checkKeyEvent = function (codes, event, predicate) {
for (let i = 0; i < codes.length; i++) {
if (predicate(codes[i], event)) {
return true;
}
}
};
const checkKeyCode = function (codes, event) {
return checkKeyEvent(codes, event, function (code, event) {
return code === event.keyCode && VK.modifierPressed(event) === false;
});
};
const checkCharCode = function (chars, event) {
return checkKeyEvent(chars, event, function (chr, event) {
return chr.charCodeAt(0) === event.charCode;
});
};
export default {
handleEnter,
handleInlineKey,
checkCharCode,
checkKeyCode
};

View File

@ -1,67 +0,0 @@
/**
* Patterns.js
*
* Released under LGPL License.
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved
*
* License: http://www.tinymce.com/license
* Contributing: http://www.tinymce.com/contributing
*/
// Returns a sorted patterns list, ordered descending by start length
const sortPatterns = function (patterns) {
return patterns.sort(function (a, b) {
if (a.start.length > b.start.length) {
return -1;
}
if (a.start.length < b.start.length) {
return 1;
}
return 0;
});
};
// Finds a matching pattern to the specified text
const findPattern = function (patterns, text) {
for (let i = 0; i < patterns.length; i++) {
if (text.indexOf(patterns[i].start) !== 0) {
continue;
}
if (patterns[i].end && text.lastIndexOf(patterns[i].end) !== (text.length - patterns[i].end.length)) {
continue;
}
return patterns[i];
}
};
const isMatchingPattern = function (pattern, text, offset, delta) {
const textEnd = text.substr(offset - pattern.end.length - delta, pattern.end.length);
return textEnd === pattern.end;
};
const hasContent = function (offset, delta, pattern) {
return (offset - delta - pattern.end.length - pattern.start.length) > 0;
};
// Finds the best matching end pattern
const findEndPattern = function (patterns, text, offset, delta) {
let pattern, i;
const sortedPatterns = sortPatterns(patterns);
// Find best matching end
for (i = 0; i < sortedPatterns.length; i++) {
pattern = sortedPatterns[i];
if (pattern.end !== undefined && isMatchingPattern(pattern, text, offset, delta) && hasContent(offset, delta, pattern)) {
return pattern;
}
}
};
export default {
findPattern,
findEndPattern
};

View File

@ -1,22 +0,0 @@
/**
* Guid.js
*
* Released under LGPL License.
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved
*
* License: http://www.tinymce.com/license
* Contributing: http://www.tinymce.com/contributing
*/
const create = function (prefix) {
let counter = 0;
return function () {
const guid = new Date().getTime().toString(32);
return prefix + guid + (counter++).toString(32);
};
};
export default {
create
};

View File

@ -1,17 +0,0 @@
/**
* Events.js
*
* Released under LGPL License.
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved
*
* License: http://www.tinymce.com/license
* Contributing: http://www.tinymce.com/contributing
*/
const fireVisualBlocks = function (editor, state) {
editor.fire('VisualBlocks', { state });
};
export default {
fireVisualBlocks
};

View File

@ -1,23 +0,0 @@
/**
* Api.js
*
* Released under LGPL License.
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved
*
* License: http://www.tinymce.com/license
* Contributing: http://www.tinymce.com/contributing
*/
const get = function (toggleState) {
const isEnabled = function () {
return toggleState.get();
};
return {
isEnabled
};
};
export default {
get
};

View File

@ -1,17 +0,0 @@
/**
* Events.js
*
* Released under LGPL License.
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved
*
* License: http://www.tinymce.com/license
* Contributing: http://www.tinymce.com/contributing
*/
const fireVisualChars = function (editor, state) {
return editor.fire('VisualChars', { state });
};
export default {
fireVisualChars
};

View File

@ -1,19 +0,0 @@
/**
* Html.js
*
* Released under LGPL License.
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved
*
* License: http://www.tinymce.com/license
* Contributing: http://www.tinymce.com/contributing
*/
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

@ -1,54 +0,0 @@
/**
* Arr.js
*
* Released under LGPL License.
* Copyright (c) 1999-2016 Ephox Corp. All rights reserved
*
* License: http://www.tinymce.com/license
* Contributing: http://www.tinymce.com/contributing
*/
const each = function (o, cb, s?) {
let n, l;
if (!o) {
return 0;
}
s = s || o;
if (o.length !== undefined) {
// Indexed arrays, needed for Safari
for (n = 0, l = o.length; n < l; n++) {
if (cb.call(s, o[n], n, o) === false) {
return 0;
}
}
} else {
// Hashtables
for (n in o) {
if (o.hasOwnProperty(n)) {
if (cb.call(s, o[n], n, o) === false) {
return 0;
}
}
}
}
return 1;
};
const map = function (array, callback) {
const out = [];
each(array, function (item, index) {
out.push(callback(item, index, array));
});
return out;
};
export default {
each,
map
};

View File

@ -1,25 +0,0 @@
/**
* Api.js
*
* Released under LGPL License.
* Copyright (c) 1999-2016 Ephox Corp. All rights reserved
*
* License: http://www.tinymce.com/license
* Contributing: http://www.tinymce.com/contributing
*/
import WordCount from '../text/WordCount';
const get = function (editor) {
const getCount = function () {
return WordCount.getCount(editor);
};
return {
getCount
};
};
export default {
get
};

View File

@ -1,19 +0,0 @@
/**
* Arr.js
*
* Released under LGPL License.
* Copyright (c) 1999-2016 Ephox Corp. All rights reserved
*
* License: http://www.tinymce.com/license
* Contributing: http://www.tinymce.com/contributing
*/
const flatten = function (arr: any[]) {
return arr.reduce(function (results: any[], item) {
return Array.isArray(item) ? results.concat(flatten(item)) : results.concat(item);
}, []);
};
export default {
flatten
};

View File

@ -1,12 +0,0 @@
import { Fun } from '@ephox/katamari';
const prefix = 'tinymce-mobile';
const resolve = function (p) {
return prefix + '-' + p;
};
export default {
resolve,
prefix: Fun.constant(prefix)
};

View File

@ -1,10 +0,0 @@
import { Attr } from '@ephox/sugar';
const safeParse = function (element, attribute) {
const parsed = parseInt(Attr.get(element, attribute), 10);
return isNaN(parsed) ? 0 : parsed;
};
export default {
safeParse
};

View File

@ -1,6 +1,6 @@
{
"name": "tinymce",
"version": "4.8.5",
"version": "4.9.2",
"repository": {
"type": "git",
"url": "https://github.com/tinymce/tinymce.git"
@ -30,7 +30,8 @@
"@ephox/robin": "latest",
"@ephox/sand": "latest",
"@ephox/snooker": "latest",
"@ephox/sugar": "latest"
"@ephox/sugar": "latest",
"tslib": "^1.9.3"
},
"devDependencies": {
"@ephox/agar": "latest",
@ -53,12 +54,13 @@
"less-plugin-autoprefix": "^1.5.1",
"load-grunt-tasks": "^4.0.0",
"moxie-zip": "~0.0.3",
"tslint": "^5.9.1",
"rimraf": "^2.6.2",
"string-replace-loader": "^2.1.1",
"ts-loader": "^5.3.0",
"tslint": "^5.9.1",
"typescript": "^3.1.5",
"webpack": "^4.8.3",
"webpack-dev-server": "^3.1.5",
"webpack-livereload-plugin": "^2.1.1",
"rimraf": "^2.6.2"
"webpack-livereload-plugin": "^2.1.1"
}
}

View File

@ -0,0 +1,15 @@
# trunkImages TinyMCE Plugin
Welcome stranger! This is a repo containing the trunkImages TinyMCE plugin.
## The development server
By running the `npm start` command you start the development server and open a browser window with an instance of TinyMCE with your plugin added to it. This window will reload automatically whenever a change is detected in the `index.html` file in the `static` folder or in one of the TypeScript files in the `src` directory.
## The production build
By running the `npm run build` command Webpack will create a `dist` directory with a child directory with the name of your plugin (trunk-images) containing three files:
* `plugin.js` - the bundled plugin
* `plugin.min.js` - the bundles, uglified and minified plugin
* `LICENSE` - a file explaining the license of your plugin (copied over from `src/LICENSE`)

View File

@ -0,0 +1,35 @@
const path = require("path");
const webpack = require("webpack");
const CopyWebpackPlugin = require("copy-webpack-plugin");
const pluginName = "ccm-cms-images";
module.exports = {
entry: {
plugin: "./src/index.ts",
"plugin.min": "./src/index.ts"
},
output: {
path: path.join(__dirname, "../../../../../ccm-core/web/assets/tinymce/js/tinymce/plugins", pluginName),
filename: "[name].js"
},
resolve: {
extensions: [".webpack.js", ".web.js", ".ts", ".js"]
},
module: {
rules: [{
test: /\.ts$/,
use: "ts-loader"
}]
},
plugins: [
new webpack.optimize.UglifyJsPlugin({
include: /\.min\.js$/,
minimize: true
}),
new CopyWebpackPlugin([{
from: path.join(__dirname, "../src/LICENSE"),
to: path.join(__dirname, "../dist", pluginName)
}])
]
};

View File

@ -0,0 +1,35 @@
const path = require("path");
const webpack = require("webpack");
const CopyWebpackPlugin = require("copy-webpack-plugin");
const pluginName = "ccm-cms-images";
module.exports = {
entry: {
plugin: "./src/index.ts",
"plugin.min": "./src/index.ts"
},
output: {
path: path.join(__dirname, "../../../../../runtime/apache-tomcat-8.5.15/webapps/ROOT/assets/tinymce/js/tinymce/plugins", pluginName),
filename: "[name].js"
},
resolve: {
extensions: [".webpack.js", ".web.js", ".ts", ".js"]
},
module: {
rules: [{
test: /\.ts$/,
use: "ts-loader"
}]
},
plugins: [
new webpack.optimize.UglifyJsPlugin({
include: /\.min\.js$/,
minimize: true
}),
new CopyWebpackPlugin([{
from: path.join(__dirname, "../src/LICENSE"),
to: path.join(__dirname, "../dist", pluginName)
}])
]
};

View File

@ -0,0 +1,33 @@
{
"name": "ccm-cms-images",
"version": "1.0.0",
"description": "CCM CMS Images TinyMCE plugin",
"main": "index.js",
"watch": {
"build": "src/*.ts"
},
"scripts": {
"postinstall": "typings install",
"start": "webpack-dev-server --config config/webpack.config.dev.js --progress --open --inline",
"lint": "tslint 'src/**/*.js'",
"build": "webpack --config config/webpack.config.prod.js --progress",
"test": "webpack --config config/webpack.config.test.js --progress",
"watch": "npm-watch"
},
"author": "",
"license": "MIT",
"devDependencies": {
"copy-webpack-plugin": "^4.0.1",
"html-webpack-plugin": "^2.26.0",
"ts-loader": "^1.3.3",
"tslint": "^4.2.0",
"typescript": "^2.1.4",
"typings": "^2.1.0",
"webpack": "^2.2.0",
"webpack-dev-server": "^1.16.2"
},
"dependencies": {
"lodash": "^4.17.4",
"npm-watch": "^0.4.0"
}
}

View File

@ -0,0 +1,305 @@
declare var tinymce: any;
export default function(editor) {
function getImageData(editor) {
const elem = editor.selection.getNode();
const imgDiv = editor.dom.getParent(elem, "div.image");
const img = editor.dom.select("img", imgDiv)[0];
if (imgDiv != null) {
let imageData = {
file: img.getAttribute("src"),
width: img.getAttribute("width").slice(0, -2),
height: img.getAttribute("height").slice(0, -2),
alt: img.getAttribute("alt"),
align: imgDiv.classList[1],
fancy: imgDiv.childNodes[0].classList[0].slice(0, -1),
title: imgDiv.childNodes[0].title,
caption: imgDiv.childNodes[1].localName == "span",
parent: imgDiv
};
console.log(imageData);
return imageData;
} else {
return null;
}
}
function open() {
const imageData = getImageData(editor);
let image_name = "";
// ================== File Chooser ====================
let fileChooseContainer = new tinymce.ui.Container({
type: "container",
layout: "flex",
direction: "row",
align: "center",
padding: 5,
spacing: 15,
margin: 5
});
let imagePathTextBox = new tinymce.ui.TextBox({
name: "file",
label: "File:",
disabled: true
});
fileChooseContainer.add(imagePathTextBox);
let browseButton = new tinymce.ui.Button({
name: "browse_images",
text: "Browse Images",
onclick: function() {
let baseURL = window.location.href;
let offset = baseURL.lastIndexOf("/");
let destURL = baseURL.slice(0, offset + 1) + "image_select.jsp";
let selectWindow = window.open(
destURL,
"_blank",
"scrollbars=yes,directories=no,toolbar=no,width=800,height=600,status=no,menubar=no"
);
(<any>window).openCCM = new Object();
(<any>window).openCCM.imageSet = selectedImage => {
imagePathTextBox.text(selectedImage.src);
win
.find("#file")
.value(selectedImage.src)
.fire("change");
win
.find("#width")
.value(selectedImage.width)
.fire("change");
win
.find("#height")
.value(selectedImage.height)
.fire("change");
image_name = selectedImage.name;
return true;
};
}
});
fileChooseContainer.add(browseButton);
// ================== File Chooser ====================
// ================== Alternate Text ==================
let alternateTextBox = new tinymce.ui.TextBox({
name: "alternate",
label: "Alternate:"
});
// ================== Alternate Text ==================
// ================== Title Text ======================
let titleTextBox = new tinymce.ui.TextBox({
name: "title",
label: "Title:"
});
// ================== Title Text ======================
// ================== Alignment =======================
let alignmentContainer = new tinymce.ui.Container({
type: "container",
layout: "flex",
direction: "row"
});
let alginLabel = new tinymce.ui.Label({
text: "Alignment:"
});
let alignListBox = new tinymce.ui.ListBox({
name: "alignment",
values: [
{ text: "Not set", value: "" },
{ text: "Left", value: "left" },
{ text: "Center", value: "center" },
{ text: "Right", value: "right" }
]
});
alignmentContainer.add(alginLabel);
alignmentContainer.add(alignListBox);
// ================== Alignment =======================
// ================== Fancy Box =======================
let fancyBoxContainer = new tinymce.ui.Container({
type: "container",
layout: "flex",
direction: "row"
});
let fancyBoxLabel = new tinymce.ui.Label({
text: "Fancy Box:"
});
let fancyBoxListBox = new tinymce.ui.ListBox({
name: "fancybox",
values: [
{ text: "None", value: "" },
{ text: "Zoom", value: "imageZoom" },
{ text: "Gallery", value: "imageGallery" }
]
});
fancyBoxContainer.add(fancyBoxLabel);
fancyBoxContainer.add(fancyBoxListBox);
// ================== Fancy Box =======================
// ================== Caption =========================
let captionCheckBox = new tinymce.ui.Checkbox({
label: "Caption:",
name: "caption"
});
// ================== Caption =========================
// ================== Dimension Box ===================
let dimensionContainer = new tinymce.ui.Container({
label: "Dimension",
layout: "flex",
direction: "row",
align: "center",
padding: 5,
spacing: 15,
margin: 5
});
let widthTextBox = new tinymce.ui.TextBox({
name: "width",
label: "Width"
});
let heightTextBox = new tinymce.ui.TextBox({
name: "height",
label: "Height"
});
dimensionContainer.add(widthTextBox);
dimensionContainer.add({ type: "label", text: "X" });
dimensionContainer.add(heightTextBox);
// ================== Dimension Box ===================
const win = editor.windowManager.open({
title: "Insert/Modify Image",
width: 800,
height: 600,
body: [
fileChooseContainer,
alternateTextBox,
titleTextBox,
alignmentContainer,
fancyBoxContainer,
captionCheckBox,
dimensionContainer
],
onsubmit: function() {
let src = win.find("#file").value();
let alternate = win.find("#alternate").value();
let width = win.find("#width").value();
let height = win.find("#height").value();
let title = win.find("#title").value();
let alignment = win.find("#alignment").value();
let fancy_box = win.find("#fancybox").value();
if (src != null) {
let img =
"<img src=" +
src +
' alt="' +
alternate +
'" name="' +
image_name +
'" width="' +
width +
'px"' +
' height="' +
height +
'px"' +
" />";
let fancy_box_wrap =
"<a class=" +
fancy_box +
'" href="' +
src +
'" title="' +
title +
'" data-mce-href="' +
src +
'"> ' +
img +
"</a>";
let span = "";
if (win.find("#caption").value()) {
span =
'<span class="caption" style="width: ' +
width +
'px;" data-mce-style="width: ' +
width +
'px;">' +
image_name +
"</span>";
}
let img_div =
'<div class="image ' +
alignment +
'">' +
fancy_box_wrap +
span +
"</div>";
if (imageData != null) {
editor.dom.replace(
editor.dom.createFragment(img_div),
imageData.parent
);
} else {
editor.insertContent(img_div);
}
}
}
});
// ================== Fill with selection =============
if (imageData != null) {
win
.find("#file")
.value(imageData.file)
.fire("change");
win
.find("#alternate")
.value(imageData.alt)
.fire("change");
win
.find("#width")
.value(imageData.width)
.fire("change");
win
.find("#height")
.value(imageData.height)
.fire("change");
if (imageData.align != undefined) {
win
.find("#alignment")
.value(imageData.align)
.fire("change");
}
if (imageData.fancy != undefined) {
win
.find("#fancybox")
.value(imageData.fancy)
.fire("change");
}
win
.find("#title")
.value(imageData.title)
.fire("change");
win
.find("#caption")
.value(imageData.caption)
.fire("change");
}
// ================== Fill with selection =============
}
return {
open
};
}

View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2018 Yannick Bülter <yannick.buelter@yabue.de>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@ -0,0 +1,5 @@
import plugin from "./plugin";
declare var tinymce: any;
tinymce.PluginManager.add("ccm-cms-images", plugin);

View File

@ -0,0 +1,21 @@
import Dialog from "./Dialog";
declare var tinymce: any;
const plugin = (editor: any, url: String) => {
editor.addButton("ccm-cms-images-button", {
icon: "image",
tooltip: "Insert/Edit image",
onlick: Dialog(editor).open,
stateSelector: "div.image"
});
editor.addMenuItem("ccm-cms-images-menu", {
icon: "image",
text: "Insert/Edit Images",
onclick: Dialog(editor).open,
stateSelector: "image",
context: "insert",
prependToContext: true
});
};
export default plugin;

View File

@ -0,0 +1,19 @@
<!DOCTYPE html>
<html>
<head>
<script src="//cdn.tinymce.com/4/tinymce.min.js"></script>
<script>
tinymce.init({
selector: 'textarea',
plugins: 'trunk-images',
toolbar: 'trunk-images'
});
</script>
</head>
<body>
<textarea>Testing trunk-images.</textarea>
</body>
</html>

View File

@ -0,0 +1,6 @@
{
"compilerOptions": {
"noImplicitAny": false,
"removeComments": true
}
}

View File

@ -0,0 +1,101 @@
{
"jsRules": {
"class-name": true,
"comment-format": [
true,
"check-space"
],
"indent": [
true,
"spaces"
],
"no-duplicate-variable": true,
"no-eval": true,
"no-trailing-whitespace": true,
"no-unsafe-finally": true,
"one-line": [
true,
"check-open-brace",
"check-whitespace"
],
"quotemark": [
true,
"double"
],
"semicolon": [
true,
"always"
],
"triple-equals": [
true,
"allow-null-check"
],
"variable-name": [
true,
"ban-keywords"
],
"whitespace": [
true,
"check-branch",
"check-decl",
"check-operator",
"check-separator",
"check-type"
]
},
"rules": {
"class-name": true,
"comment-format": [
true,
"check-space"
],
"indent": [
true,
"spaces"
],
"no-eval": true,
"no-internal-module": true,
"no-trailing-whitespace": true,
"no-unsafe-finally": true,
"no-var-keyword": true,
"one-line": [
true,
"check-open-brace",
"check-whitespace"
],
"quotemark": [
true,
"double"
],
"semicolon": [
true,
"always"
],
"triple-equals": [
true,
"allow-null-check"
],
"typedef-whitespace": [
true,
{
"call-signature": "nospace",
"index-signature": "nospace",
"parameter": "nospace",
"property-declaration": "nospace",
"variable-declaration": "nospace"
}
],
"variable-name": [
true,
"ban-keywords"
],
"whitespace": [
true,
"check-branch",
"check-decl",
"check-operator",
"check-separator",
"check-type"
]
}
}

View File

@ -0,0 +1,5 @@
{
"dependencies": {
"lodash": "registry:npm/lodash#4.0.0+20161015015725"
}
}

Some files were not shown because too many files have changed in this diff Show More