mirror of
https://github.com/tinymce/tinymce.git
synced 2025-07-21 05:47:56 +00:00
TINY-10654: Remove deprecated template plugin (#9415)
This commit is contained in:
6
.changes/unreleased/tinymce-TINY-10654-2024-02-23.yaml
Normal file
6
.changes/unreleased/tinymce-TINY-10654-2024-02-23.yaml
Normal file
@ -0,0 +1,6 @@
|
||||
project: tinymce
|
||||
kind: Removed
|
||||
body: Removed deprecated `template` plugin.
|
||||
time: 2024-02-23T12:25:39.49598509+10:00
|
||||
custom:
|
||||
Issue: TINY-10654
|
@ -15,7 +15,6 @@ import { createPreviewDialog } from './dialogs/PreviewDialog';
|
||||
import { createTableCellDialog } from './dialogs/TableCellDialog';
|
||||
import { createTableDialog } from './dialogs/TableDialog';
|
||||
import { createTableRowDialog } from './dialogs/TableRowDialog';
|
||||
import { createTemplateDialog } from './dialogs/TemplateDialog';
|
||||
import { createWordcountDialog } from './dialogs/WordcountDialog';
|
||||
import { registerDemoContextMenus } from './menus/ContextMenuDemo';
|
||||
import { registerDemoMenuItems } from './menus/MenuItemDemo';
|
||||
@ -36,7 +35,6 @@ createPreviewDialog();
|
||||
createTableCellDialog();
|
||||
createTableDialog();
|
||||
createTableRowDialog();
|
||||
createTemplateDialog();
|
||||
registerDemoButtons();
|
||||
registerDemoMenuItems();
|
||||
registerDemoContextMenus();
|
||||
|
@ -1,63 +0,0 @@
|
||||
import { openDemoDialog } from './DemoDialogHelpers';
|
||||
|
||||
export const createTemplateDialog = (): void => {
|
||||
openDemoDialog(
|
||||
{
|
||||
title: 'Insert template',
|
||||
size: 'large',
|
||||
body: {
|
||||
type: 'panel',
|
||||
items: [
|
||||
{
|
||||
name: 'template',
|
||||
type: 'selectbox',
|
||||
label: 'Template',
|
||||
items: [
|
||||
{
|
||||
text: 'Some template 1',
|
||||
value: 'url1.html'
|
||||
},
|
||||
{
|
||||
text: 'Some template 2',
|
||||
value: 'url2.html'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'preview',
|
||||
type: 'iframe',
|
||||
label: 'Preview of template',
|
||||
sandboxed: false
|
||||
}
|
||||
]
|
||||
},
|
||||
buttons: [
|
||||
{
|
||||
type: 'submit',
|
||||
name: 'ok',
|
||||
text: 'Ok',
|
||||
primary: true
|
||||
},
|
||||
{
|
||||
type: 'cancel',
|
||||
name: 'cancel',
|
||||
text: 'Cancel'
|
||||
}
|
||||
],
|
||||
initialData: {
|
||||
template: 'url2.html',
|
||||
preview: 'some html url'
|
||||
},
|
||||
onSubmit: (api) => {
|
||||
const data = api.getData();
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
console.log({
|
||||
template: data.template
|
||||
});
|
||||
|
||||
api.close();
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
@ -1,11 +0,0 @@
|
||||
import { getDemoRegistry } from '../buttons/DemoRegistry';
|
||||
|
||||
export const registerTemplateItems = (): void => {
|
||||
getDemoRegistry().addButton('template', {
|
||||
type: 'button',
|
||||
enabled: true,
|
||||
onAction: (_buttonApi) => {
|
||||
// show dialog
|
||||
}
|
||||
});
|
||||
};
|
@ -14,7 +14,7 @@ let plugins = [
|
||||
'accordion', 'advlist', 'anchor', 'autolink', 'autoresize', 'autosave', 'charmap', 'code', 'codesample',
|
||||
'directionality', 'emoticons', 'help', 'fullscreen', 'image', 'importcss', 'insertdatetime',
|
||||
'link', 'lists', 'media', 'nonbreaking', 'pagebreak', 'preview', 'save', 'searchreplace',
|
||||
'table', 'template', 'visualblocks', 'visualchars', 'wordcount', 'quickbars'
|
||||
'table', 'visualblocks', 'visualchars', 'wordcount', 'quickbars'
|
||||
];
|
||||
|
||||
let themes = [
|
||||
|
@ -66,7 +66,6 @@ export default (): void => {
|
||||
cmd('mceSave'),
|
||||
cmd('SearchReplace'),
|
||||
cmd('mceSpellcheck'),
|
||||
cmd('mceInsertTemplate', '{$user}'),
|
||||
cmd('mceVisualBlocks'),
|
||||
cmd('mceVisualChars'),
|
||||
cmd('mceMedia'),
|
||||
@ -107,7 +106,7 @@ export default (): void => {
|
||||
plugins: [
|
||||
'advlist', 'autolink', 'link', 'image', 'lists', 'charmap', 'preview', 'anchor', 'pagebreak',
|
||||
'searchreplace', 'wordcount', 'visualblocks', 'visualchars', 'code', 'fullscreen', 'insertdatetime', 'media', 'nonbreaking',
|
||||
'save', 'table', 'directionality', 'emoticons', 'template', 'importcss', 'codesample'
|
||||
'save', 'table', 'directionality', 'emoticons', 'importcss', 'codesample'
|
||||
],
|
||||
toolbar1: 'bold italic',
|
||||
menubar: false
|
||||
|
@ -73,12 +73,6 @@ const settings: RawEditorOptions = {
|
||||
callback('movie.mp4', { embed: '<p>test</p>' });
|
||||
}
|
||||
},
|
||||
templates: [
|
||||
{ title: 'Some title 1', description: 'Some desc 1', content: 'My content' },
|
||||
{ title: 'Some title 2', description: 'Some desc 2', content: '<div class="mceTmpl"><span class="cdate">cdate</span><span class="mdate">mdate</span>My content2</div>' }
|
||||
],
|
||||
template_cdate_format: '[CDATE: %m/%d/%Y : %H:%M:%S]',
|
||||
template_mdate_format: '[MDATE: %m/%d/%Y : %H:%M:%S]',
|
||||
image_caption: true,
|
||||
theme: 'silver',
|
||||
setup: (ed) => {
|
||||
@ -87,7 +81,7 @@ const settings: RawEditorOptions = {
|
||||
plugins: [
|
||||
'autosave', 'advlist', 'autolink', 'link', 'image', 'lists', 'charmap', 'preview', 'anchor', 'pagebreak',
|
||||
'searchreplace', 'wordcount', 'visualblocks', 'visualchars', 'code', 'fullscreen', 'insertdatetime', 'media', 'nonbreaking',
|
||||
'save', 'table', 'directionality', 'emoticons', 'template', 'importcss', 'codesample', 'help'
|
||||
'save', 'table', 'directionality', 'emoticons', 'importcss', 'codesample', 'help'
|
||||
],
|
||||
// rtl_ui: true,
|
||||
add_unload_trigger: false,
|
||||
|
@ -7,6 +7,5 @@ export default (): void => {
|
||||
selector: '#editor',
|
||||
inline: true,
|
||||
fixed_toolbar_container: '#toolbar',
|
||||
plugins: 'template' // lets you check notification positioning
|
||||
});
|
||||
};
|
||||
|
@ -107,12 +107,6 @@ export default (): void => {
|
||||
callback('movie.mp4', { embed: '<p>test</p>' });
|
||||
}
|
||||
},
|
||||
templates: [
|
||||
{ title: 'Some title 1', description: 'Some desc 1', content: 'My content' },
|
||||
{ title: 'Some title 2', description: 'Some desc 2', content: '<div class="mceTmpl"><span class="cdate">cdate</span><span class="mdate">mdate</span>My content2</div>' }
|
||||
],
|
||||
template_cdate_format: '[CDATE: %m/%d/%Y : %H:%M:%S]',
|
||||
template_mdate_format: '[MDATE: %m/%d/%Y : %H:%M:%S]',
|
||||
image_caption: true,
|
||||
theme: 'silver',
|
||||
setup: (ed) => {
|
||||
@ -123,7 +117,7 @@ export default (): void => {
|
||||
plugins: [
|
||||
'autosave', 'advlist', 'autolink', 'link', 'image', 'lists', 'charmap', 'preview', 'anchor', 'pagebreak',
|
||||
'searchreplace', 'wordcount', 'visualblocks', 'visualchars', 'code', 'fullscreen', 'insertdatetime', 'media', 'nonbreaking',
|
||||
'save', 'table', 'directionality', 'emoticons', 'template', 'importcss', 'codesample', 'help', 'accordion'
|
||||
'save', 'table', 'directionality', 'emoticons', 'importcss', 'codesample', 'help', 'accordion'
|
||||
],
|
||||
// rtl_ui: true,
|
||||
add_unload_trigger: false,
|
||||
|
@ -21,14 +21,10 @@ export default (): void => {
|
||||
{ title: 'None', value: '' },
|
||||
{ title: 'Some class', value: 'class-name' }
|
||||
],
|
||||
templates: [
|
||||
{ title: 'Some title 1', description: 'Some desc 1', content: 'My content' },
|
||||
{ title: 'Some title 2', description: 'Some desc 2', content: '<div class="mceTmpl"><span class="cdate">cdate</span><span class="mdate">mdate</span>My content2</div>' }
|
||||
],
|
||||
plugins: [
|
||||
'autosave', 'advlist', 'autolink', 'link', 'image', 'lists', 'charmap', 'preview', 'anchor', 'pagebreak',
|
||||
'searchreplace', 'wordcount', 'visualblocks', 'visualchars', 'code', 'fullscreen', 'insertdatetime', 'media', 'nonbreaking',
|
||||
'save', 'table', 'directionality', 'emoticons', 'template', 'codesample', 'help'
|
||||
'save', 'table', 'directionality', 'emoticons', 'codesample', 'help'
|
||||
]
|
||||
};
|
||||
|
||||
|
@ -58,12 +58,6 @@ export default (): void => {
|
||||
callback('movie.mp4', { source2: 'alt.ogg', poster: 'https://www.google.com/logos/google.jpg' });
|
||||
}
|
||||
},
|
||||
templates: [
|
||||
{ title: 'Some title 1', description: 'Some desc 1', content: 'My content' },
|
||||
{ title: 'Some title 2', description: 'Some desc 2', content: '<div class="mceTmpl"><span class="cdate">cdate</span><span class="mdate">mdate</span>My content2</div>' }
|
||||
],
|
||||
template_cdate_format: '[CDATE: %m/%d/%Y : %H:%M:%S]',
|
||||
template_mdate_format: '[MDATE: %m/%d/%Y : %H:%M:%S]',
|
||||
image_caption: true,
|
||||
theme: 'silver',
|
||||
mobile: {
|
||||
@ -80,7 +74,7 @@ export default (): void => {
|
||||
plugins: [
|
||||
'fullscreen', 'help', 'autosave', 'advlist', 'autolink', 'link', 'image', 'lists', 'charmap', 'preview', 'anchor', 'pagebreak',
|
||||
'searchreplace', 'wordcount', 'visualblocks', 'visualchars', 'code', 'fullscreen', 'insertdatetime', 'media', 'nonbreaking',
|
||||
'save', 'table', 'directionality', 'emoticons', 'template', 'importcss', 'codesample', 'help'
|
||||
'save', 'table', 'directionality', 'emoticons', 'importcss', 'codesample', 'help'
|
||||
],
|
||||
// rtl_ui: true,
|
||||
add_unload_trigger: false,
|
||||
|
@ -9,15 +9,11 @@ export default (): void => {
|
||||
selector: 'textarea#editor',
|
||||
skin_url: '../../../../js/tinymce/skins/ui/oxide',
|
||||
content_css: '../../../../js/tinymce/skins/content/default/content.css',
|
||||
templates: [
|
||||
{ title: 'Some title 1', description: 'Some desc 1', content: 'My content' },
|
||||
{ title: 'Some title 2', description: 'Some desc 2', content: '<div class="mceTmpl"><span class="cdate">cdate</span><span class="mdate">mdate</span>My content2</div>' }
|
||||
],
|
||||
image_caption: true,
|
||||
plugins: [
|
||||
'autosave', 'advlist', 'autolink', 'link', 'image', 'lists', 'charmap', 'preview', 'anchor', 'pagebreak',
|
||||
'searchreplace', 'wordcount', 'visualblocks', 'visualchars', 'code', 'fullscreen', 'insertdatetime', 'media', 'nonbreaking',
|
||||
'save', 'table', 'directionality', 'emoticons', 'template', 'importcss', 'codesample', 'help'
|
||||
'save', 'table', 'directionality', 'emoticons', 'importcss', 'codesample', 'help'
|
||||
],
|
||||
add_unload_trigger: false,
|
||||
autosave_ask_before_unload: false,
|
||||
|
@ -62,12 +62,6 @@ export default (): void => {
|
||||
callback('movie.mp4', { source2: 'alt.ogg', poster: 'https://www.google.com/logos/google.jpg' });
|
||||
}
|
||||
},
|
||||
templates: [
|
||||
{ title: 'Some title 1', description: 'Some desc 1', content: 'My content' },
|
||||
{ title: 'Some title 2', description: 'Some desc 2', content: '<div class="mceTmpl"><span class="cdate">cdate</span><span class="mdate">mdate</span>My content2</div>' }
|
||||
],
|
||||
template_cdate_format: '[CDATE: %m/%d/%Y : %H:%M:%S]',
|
||||
template_mdate_format: '[MDATE: %m/%d/%Y : %H:%M:%S]',
|
||||
image_caption: true,
|
||||
theme: 'silver',
|
||||
setup: (ed) => {
|
||||
@ -76,7 +70,7 @@ export default (): void => {
|
||||
plugins: [
|
||||
'autosave', 'advlist', 'autolink', 'link', 'image', 'lists', 'charmap', 'preview', 'anchor', 'pagebreak',
|
||||
'searchreplace', 'wordcount', 'visualblocks', 'visualchars', 'code', 'fullscreen', 'insertdatetime', 'media', 'nonbreaking',
|
||||
'save', 'table', 'directionality', 'emoticons', 'template', 'importcss', 'codesample', 'help'
|
||||
'save', 'table', 'directionality', 'emoticons', 'importcss', 'codesample', 'help'
|
||||
],
|
||||
// rtl_ui: true,
|
||||
add_unload_trigger: false,
|
||||
|
@ -46,7 +46,6 @@ const urls = Arr.map<PartialPluginUrl, PluginUrl>([
|
||||
{ key: 'save', name: 'Save' },
|
||||
{ key: 'searchreplace', name: 'Search and Replace' },
|
||||
{ key: 'table', name: 'Table' },
|
||||
{ key: 'template', name: 'Template' },
|
||||
{ key: 'textcolor', name: 'Text Color' },
|
||||
{ key: 'visualblocks', name: 'Visual Blocks' },
|
||||
{ key: 'visualchars', name: 'Visual Characters' },
|
||||
|
@ -1,15 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Plugin: template Demo Page</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h2>Plugin: template Demo Page</h2>
|
||||
<div id="ephox-ui">
|
||||
<textarea cols="30" rows="10" class="tinymce"></textarea>
|
||||
</div>
|
||||
<script src="../../../../../js/tinymce/tinymce.js"></script>
|
||||
<script src="../../../../../scratch/demos/plugins/template/demo.js"></script>
|
||||
</body>
|
||||
</html>
|
@ -1,13 +0,0 @@
|
||||
<!-- This will not be inserted -->
|
||||
<div class="mceTmpl">
|
||||
<table width="98%" border="1" cellspacing="0" cellpadding="0">
|
||||
<tr>
|
||||
<th scope="col">Header 1</th>
|
||||
<th scope="col">Header 2</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Cell 1</td>
|
||||
<td>Cell 2</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
@ -1,25 +0,0 @@
|
||||
import { TinyMCE } from 'tinymce/core/api/PublicApi';
|
||||
|
||||
declare let tinymce: TinyMCE;
|
||||
|
||||
tinymce.init({
|
||||
selector: 'textarea.tinymce',
|
||||
plugins: 'template',
|
||||
toolbar: 'template',
|
||||
height: 600,
|
||||
template_preview_replace_values: {
|
||||
username: '<em>username here</em>'
|
||||
},
|
||||
template_replace_values: {
|
||||
username: 'Jack',
|
||||
staffid: '991234'
|
||||
},
|
||||
templates: [
|
||||
{ title: 'Some title 1', description: 'Some desc 1', content: 'My content {$username}' },
|
||||
{ title: 'Some title 2', description: 'Some desc 2', content: 'My other content' },
|
||||
{ title: 'Some remote file', description: 'Some desc 3', url: 'development.html' },
|
||||
{ title: 'Nonexistent remote file', description: 'Some desc 4', url: 'invalid.html' }
|
||||
]
|
||||
});
|
||||
|
||||
export {};
|
@ -1,9 +0,0 @@
|
||||
import Plugin from './Plugin';
|
||||
|
||||
Plugin();
|
||||
|
||||
/** *****
|
||||
* DO NOT EXPORT ANYTHING
|
||||
*
|
||||
* IF YOU DO ROLLUP WILL LEAVE A GLOBAL ON THE PAGE
|
||||
*******/
|
@ -1,15 +0,0 @@
|
||||
import PluginManager from 'tinymce/core/api/PluginManager';
|
||||
|
||||
import * as Commands from './api/Commands';
|
||||
import * as Options from './api/Options';
|
||||
import * as FilterContent from './core/FilterContent';
|
||||
import * as Buttons from './ui/Buttons';
|
||||
|
||||
export default (): void => {
|
||||
PluginManager.add('template', (editor) => {
|
||||
Options.register(editor);
|
||||
Buttons.register(editor);
|
||||
Commands.register(editor);
|
||||
FilterContent.setup(editor);
|
||||
});
|
||||
};
|
@ -1,20 +0,0 @@
|
||||
import { Fun } from '@ephox/katamari';
|
||||
|
||||
import Editor from 'tinymce/core/api/Editor';
|
||||
|
||||
import * as Templates from '../core/Templates';
|
||||
import { ExternalTemplate } from '../core/Types';
|
||||
import * as Dialog from '../ui/Dialog';
|
||||
|
||||
const showDialog = (editor: Editor) => (templates: ExternalTemplate[]): void => {
|
||||
Dialog.open(editor, templates);
|
||||
};
|
||||
|
||||
const register = (editor: Editor): void => {
|
||||
editor.addCommand('mceInsertTemplate', Fun.curry(Templates.insertTemplate, editor));
|
||||
editor.addCommand('mceTemplate', Templates.createTemplateList(editor, showDialog(editor)));
|
||||
};
|
||||
|
||||
export {
|
||||
register
|
||||
};
|
@ -1,83 +0,0 @@
|
||||
import { Type } from '@ephox/katamari';
|
||||
|
||||
import Editor from 'tinymce/core/api/Editor';
|
||||
import { EditorOptions } from 'tinymce/core/api/OptionTypes';
|
||||
|
||||
import { ExternalTemplate, TemplateValues } from '../core/Types';
|
||||
|
||||
type TemplateCallback = (callback: (templates: ExternalTemplate[]) => void) => void;
|
||||
|
||||
const option: {
|
||||
<K extends keyof EditorOptions>(name: K): (editor: Editor) => EditorOptions[K];
|
||||
<T>(name: string): (editor: Editor) => T;
|
||||
} = (name: string) => (editor: Editor) =>
|
||||
editor.options.get(name);
|
||||
|
||||
const register = (editor: Editor): void => {
|
||||
const registerOption = editor.options.register;
|
||||
|
||||
registerOption('template_cdate_classes', {
|
||||
processor: 'string',
|
||||
default: 'cdate'
|
||||
});
|
||||
|
||||
registerOption('template_mdate_classes', {
|
||||
processor: 'string',
|
||||
default: 'mdate'
|
||||
});
|
||||
|
||||
registerOption('template_selected_content_classes', {
|
||||
processor: 'string',
|
||||
default: 'selcontent'
|
||||
});
|
||||
|
||||
registerOption('template_preview_replace_values', {
|
||||
processor: 'object'
|
||||
});
|
||||
|
||||
registerOption('template_replace_values', {
|
||||
processor: 'object'
|
||||
});
|
||||
|
||||
registerOption('templates', {
|
||||
processor: (value) => Type.isString(value) || Type.isArrayOf(value, Type.isObject) || Type.isFunction(value),
|
||||
default: []
|
||||
});
|
||||
|
||||
registerOption('template_cdate_format', {
|
||||
processor: 'string',
|
||||
default: editor.translate('%Y-%m-%d')
|
||||
});
|
||||
|
||||
registerOption('template_mdate_format', {
|
||||
processor: 'string',
|
||||
default: editor.translate('%Y-%m-%d')
|
||||
});
|
||||
};
|
||||
|
||||
const getCreationDateClasses = option<string>('template_cdate_classes');
|
||||
const getModificationDateClasses = option<string>('template_mdate_classes');
|
||||
const getSelectedContentClasses = option<string>('template_selected_content_classes');
|
||||
const getPreviewReplaceValues = option<TemplateValues | undefined>('template_preview_replace_values');
|
||||
const getTemplateReplaceValues = option<TemplateValues | undefined>('template_replace_values');
|
||||
const getTemplates = option<string | ExternalTemplate[] | TemplateCallback>('templates');
|
||||
const getCdateFormat = option<string>('template_cdate_format');
|
||||
const getMdateFormat = option<string>('template_mdate_format');
|
||||
const getContentStyle = option('content_style');
|
||||
const shouldUseContentCssCors = option('content_css_cors');
|
||||
const getBodyClass = option('body_class');
|
||||
|
||||
export {
|
||||
register,
|
||||
getCreationDateClasses,
|
||||
getModificationDateClasses,
|
||||
getSelectedContentClasses,
|
||||
getPreviewReplaceValues,
|
||||
getTemplateReplaceValues,
|
||||
getTemplates,
|
||||
getCdateFormat,
|
||||
getMdateFormat,
|
||||
getBodyClass,
|
||||
getContentStyle,
|
||||
shouldUseContentCssCors
|
||||
};
|
@ -1,43 +0,0 @@
|
||||
import Editor from 'tinymce/core/api/Editor';
|
||||
|
||||
const addZeros = (value: string | number, len: number): string => {
|
||||
value = '' + value;
|
||||
|
||||
if (value.length < len) {
|
||||
for (let i = 0; i < (len - value.length); i++) {
|
||||
value = '0' + value;
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
};
|
||||
|
||||
const getDateTime = (editor: Editor, fmt: string, date: Date = new Date()): string => {
|
||||
const daysShort = 'Sun Mon Tue Wed Thu Fri Sat Sun'.split(' ');
|
||||
const daysLong = 'Sunday Monday Tuesday Wednesday Thursday Friday Saturday Sunday'.split(' ');
|
||||
const monthsShort = 'Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec'.split(' ');
|
||||
const monthsLong = 'January February March April May June July August September October November December'.split(' ');
|
||||
|
||||
fmt = fmt.replace('%D', '%m/%d/%Y');
|
||||
fmt = fmt.replace('%r', '%I:%M:%S %p');
|
||||
fmt = fmt.replace('%Y', '' + date.getFullYear());
|
||||
fmt = fmt.replace('%y', '' + (date as any).getYear());
|
||||
fmt = fmt.replace('%m', addZeros(date.getMonth() + 1, 2));
|
||||
fmt = fmt.replace('%d', addZeros(date.getDate(), 2));
|
||||
fmt = fmt.replace('%H', '' + addZeros(date.getHours(), 2));
|
||||
fmt = fmt.replace('%M', '' + addZeros(date.getMinutes(), 2));
|
||||
fmt = fmt.replace('%S', '' + addZeros(date.getSeconds(), 2));
|
||||
fmt = fmt.replace('%I', '' + ((date.getHours() + 11) % 12 + 1));
|
||||
fmt = fmt.replace('%p', '' + (date.getHours() < 12 ? 'AM' : 'PM'));
|
||||
fmt = fmt.replace('%B', '' + editor.translate(monthsLong[date.getMonth()]));
|
||||
fmt = fmt.replace('%b', '' + editor.translate(monthsShort[date.getMonth()]));
|
||||
fmt = fmt.replace('%A', '' + editor.translate(daysLong[date.getDay()]));
|
||||
fmt = fmt.replace('%a', '' + editor.translate(daysShort[date.getDay()]));
|
||||
fmt = fmt.replace('%%', '%');
|
||||
|
||||
return fmt;
|
||||
};
|
||||
|
||||
export {
|
||||
getDateTime
|
||||
};
|
@ -1,29 +0,0 @@
|
||||
import Editor from 'tinymce/core/api/Editor';
|
||||
import Tools from 'tinymce/core/api/util/Tools';
|
||||
|
||||
import * as Options from '../api/Options';
|
||||
import * as DateTimeHelper from './DateTimeHelper';
|
||||
import * as Templates from './Templates';
|
||||
import { hasAnyClasses } from './Utils';
|
||||
|
||||
const setup = (editor: Editor): void => {
|
||||
editor.on('PreProcess', (o) => {
|
||||
const dom = editor.dom, dateFormat = Options.getMdateFormat(editor);
|
||||
|
||||
Tools.each(dom.select('div', o.node), (e) => {
|
||||
if (dom.hasClass(e, 'mceTmpl')) {
|
||||
Tools.each(dom.select('*', e), (e) => {
|
||||
if (hasAnyClasses(dom, e, Options.getModificationDateClasses(editor))) {
|
||||
e.innerHTML = DateTimeHelper.getDateTime(editor, dateFormat);
|
||||
}
|
||||
});
|
||||
|
||||
Templates.replaceVals(editor, e);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export {
|
||||
setup
|
||||
};
|
@ -1,101 +0,0 @@
|
||||
import { Regex, Type } from '@ephox/katamari';
|
||||
|
||||
import Editor from 'tinymce/core/api/Editor';
|
||||
import Tools from 'tinymce/core/api/util/Tools';
|
||||
|
||||
import * as Options from '../api/Options';
|
||||
import * as DateTimeHelper from './DateTimeHelper';
|
||||
import { ExternalTemplate, TemplateValues } from './Types';
|
||||
import { hasAnyClasses, parseAndSerialize } from './Utils';
|
||||
|
||||
const createTemplateList = (editor: Editor, callback: (templates: ExternalTemplate[]) => void) => {
|
||||
return (): void => {
|
||||
const templateList = Options.getTemplates(editor);
|
||||
|
||||
if (Type.isFunction(templateList)) {
|
||||
templateList(callback);
|
||||
} else if (Type.isString(templateList)) {
|
||||
fetch(templateList)
|
||||
.then((res) => {
|
||||
if (res.ok) {
|
||||
res.json().then(callback);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
callback(templateList);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
const replaceTemplateValues = (html: string, templateValues: TemplateValues | undefined): string => {
|
||||
Tools.each(templateValues, (v, k) => {
|
||||
if (Type.isFunction(v)) {
|
||||
v = v(k);
|
||||
}
|
||||
|
||||
html = html.replace(new RegExp('\\{\\$' + Regex.escape(k) + '\\}', 'g'), v);
|
||||
});
|
||||
|
||||
return html;
|
||||
};
|
||||
|
||||
const replaceVals = (editor: Editor, scope: HTMLElement): void => {
|
||||
const dom = editor.dom, vl = Options.getTemplateReplaceValues(editor);
|
||||
|
||||
Tools.each(dom.select('*', scope), (e) => {
|
||||
Tools.each(vl, (v, k) => {
|
||||
if (dom.hasClass(e, k)) {
|
||||
if (Type.isFunction(v)) {
|
||||
// TODO: TINY-7792: Investigate as this appears to be a bug as "replaceTemplateValues" above uses
|
||||
// the same values here and it expects a string and return value so this is not compatible.
|
||||
v(e as any);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const insertTemplate = (editor: Editor, _ui: boolean, html: string): void => {
|
||||
// Note: ui is unused here but is required since this can be called by execCommand
|
||||
const dom = editor.dom;
|
||||
const sel = editor.selection.getContent();
|
||||
|
||||
html = replaceTemplateValues(html, Options.getTemplateReplaceValues(editor));
|
||||
let el = dom.create('div', {}, parseAndSerialize(editor, html));
|
||||
|
||||
// Find template element within div
|
||||
const n = dom.select('.mceTmpl', el);
|
||||
if (n && n.length > 0) {
|
||||
el = dom.create('div');
|
||||
el.appendChild(n[0].cloneNode(true));
|
||||
}
|
||||
|
||||
Tools.each(dom.select('*', el), (n) => {
|
||||
// Replace cdate
|
||||
if (hasAnyClasses(dom, n, Options.getCreationDateClasses(editor))) {
|
||||
n.innerHTML = DateTimeHelper.getDateTime(editor, Options.getCdateFormat(editor));
|
||||
}
|
||||
|
||||
// Replace mdate
|
||||
if (hasAnyClasses(dom, n, Options.getModificationDateClasses(editor))) {
|
||||
n.innerHTML = DateTimeHelper.getDateTime(editor, Options.getMdateFormat(editor));
|
||||
}
|
||||
|
||||
// Replace selection
|
||||
if (hasAnyClasses(dom, n, Options.getSelectedContentClasses(editor))) {
|
||||
n.innerHTML = sel;
|
||||
}
|
||||
});
|
||||
|
||||
replaceVals(editor, el);
|
||||
|
||||
editor.execCommand('mceInsertContent', false, el.innerHTML);
|
||||
editor.addVisual();
|
||||
};
|
||||
|
||||
export {
|
||||
createTemplateList,
|
||||
replaceTemplateValues,
|
||||
replaceVals,
|
||||
insertTemplate
|
||||
};
|
@ -1,32 +0,0 @@
|
||||
import { Optional } from '@ephox/katamari';
|
||||
|
||||
export interface UrlTemplate {
|
||||
readonly title: string;
|
||||
readonly description: string;
|
||||
readonly url: string;
|
||||
}
|
||||
|
||||
export interface ContentTemplate {
|
||||
readonly title: string;
|
||||
readonly description: string;
|
||||
readonly content: string;
|
||||
}
|
||||
|
||||
export type ExternalTemplate = UrlTemplate | ContentTemplate;
|
||||
|
||||
export interface InternalTemplate {
|
||||
readonly selected: boolean;
|
||||
readonly text: string;
|
||||
readonly value: {
|
||||
readonly url: Optional<string>;
|
||||
readonly content: Optional<string>;
|
||||
readonly description: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface DialogData {
|
||||
readonly template: string;
|
||||
readonly preview: string;
|
||||
}
|
||||
|
||||
export type TemplateValues = Record<string, string | ((name: string) => string)>;
|
@ -1,30 +0,0 @@
|
||||
import { Arr, Obj } from '@ephox/katamari';
|
||||
|
||||
import DOMUtils from 'tinymce/core/api/dom/DOMUtils';
|
||||
import Editor from 'tinymce/core/api/Editor';
|
||||
import HtmlSerializer from 'tinymce/core/api/html/Serializer';
|
||||
|
||||
const entitiesAttr: Record<string, string> = {
|
||||
'"': '"',
|
||||
'<': '<',
|
||||
'>': '>',
|
||||
'&': '&',
|
||||
'\'': '''
|
||||
};
|
||||
|
||||
const htmlEscape = (html: string): string =>
|
||||
html.replace(/["'<>&]/g, (match) => Obj.get(entitiesAttr, match).getOr(match));
|
||||
|
||||
const hasAnyClasses = (dom: DOMUtils, n: Element, classes: string): boolean =>
|
||||
Arr.exists(classes.split(/\s+/), (c) => dom.hasClass(n, c));
|
||||
|
||||
const parseAndSerialize = (editor: Editor, html: string): string =>
|
||||
HtmlSerializer({ validate: true }, editor.schema).serialize(
|
||||
editor.parser.parse(html, { insert: true })
|
||||
);
|
||||
|
||||
export {
|
||||
hasAnyClasses,
|
||||
htmlEscape,
|
||||
parseAndSerialize
|
||||
};
|
@ -1,37 +0,0 @@
|
||||
import Editor from 'tinymce/core/api/Editor';
|
||||
import { Menu, Toolbar } from 'tinymce/core/api/ui/Ui';
|
||||
|
||||
const onSetupEditable = (editor: Editor) => (api: Toolbar.ToolbarButtonInstanceApi | Menu.MenuItemInstanceApi): VoidFunction => {
|
||||
const nodeChanged = () => {
|
||||
api.setEnabled(editor.selection.isEditable());
|
||||
};
|
||||
|
||||
editor.on('NodeChange', nodeChanged);
|
||||
nodeChanged();
|
||||
|
||||
return () => {
|
||||
editor.off('NodeChange', nodeChanged);
|
||||
};
|
||||
};
|
||||
|
||||
const register = (editor: Editor): void => {
|
||||
const onAction = () => editor.execCommand('mceTemplate');
|
||||
|
||||
editor.ui.registry.addButton('template', {
|
||||
icon: 'template',
|
||||
tooltip: 'Insert template',
|
||||
onSetup: onSetupEditable(editor),
|
||||
onAction
|
||||
});
|
||||
|
||||
editor.ui.registry.addMenuItem('template', {
|
||||
icon: 'template',
|
||||
text: 'Insert template...',
|
||||
onSetup: onSetupEditable(editor),
|
||||
onAction
|
||||
});
|
||||
};
|
||||
|
||||
export {
|
||||
register
|
||||
};
|
@ -1,220 +0,0 @@
|
||||
import { Arr, Optional } from '@ephox/katamari';
|
||||
|
||||
import Editor from 'tinymce/core/api/Editor';
|
||||
import Env from 'tinymce/core/api/Env';
|
||||
import { Dialog } from 'tinymce/core/api/ui/Ui';
|
||||
import Tools from 'tinymce/core/api/util/Tools';
|
||||
|
||||
import * as Options from '../api/Options';
|
||||
import * as Templates from '../core/Templates';
|
||||
import { DialogData, ExternalTemplate, InternalTemplate, UrlTemplate } from '../core/Types';
|
||||
import * as Utils from '../core/Utils';
|
||||
|
||||
type UpdateDialogCallback = (dialogApi: Dialog.DialogInstanceApi<DialogData>, template: InternalTemplate, previewHtml: string) => void;
|
||||
|
||||
const getPreviewContent = (editor: Editor, html: string): string => {
|
||||
let previewHtml = Utils.parseAndSerialize(editor, html);
|
||||
if (html.indexOf('<html>') === -1) {
|
||||
let contentCssEntries = '';
|
||||
const contentStyle = Options.getContentStyle(editor) ?? '';
|
||||
|
||||
const cors = Options.shouldUseContentCssCors(editor) ? ' crossorigin="anonymous"' : '';
|
||||
|
||||
Tools.each(editor.contentCSS, (url) => {
|
||||
contentCssEntries += '<link type="text/css" rel="stylesheet" href="' +
|
||||
editor.documentBaseURI.toAbsolute(url) +
|
||||
'"' + cors + '>';
|
||||
});
|
||||
|
||||
if (contentStyle) {
|
||||
contentCssEntries += '<style type="text/css">' + contentStyle + '</style>';
|
||||
}
|
||||
|
||||
const bodyClass = Options.getBodyClass(editor);
|
||||
|
||||
const encode = editor.dom.encode;
|
||||
|
||||
const isMetaKeyPressed = Env.os.isMacOS() || Env.os.isiOS() ? 'e.metaKey' : 'e.ctrlKey && !e.altKey';
|
||||
|
||||
const preventClicksOnLinksScript = (
|
||||
'<script>' +
|
||||
'document.addEventListener && document.addEventListener("click", function(e) {' +
|
||||
'for (var elm = e.target; elm; elm = elm.parentNode) {' +
|
||||
'if (elm.nodeName === "A" && !(' + isMetaKeyPressed + ')) {' +
|
||||
'e.preventDefault();' +
|
||||
'}' +
|
||||
'}' +
|
||||
'}, false);' +
|
||||
'</script> '
|
||||
);
|
||||
|
||||
const directionality = editor.getBody().dir;
|
||||
const dirAttr = directionality ? ' dir="' + encode(directionality) + '"' : '';
|
||||
|
||||
previewHtml = (
|
||||
'<!DOCTYPE html>' +
|
||||
'<html>' +
|
||||
'<head>' +
|
||||
'<base href="' + encode(editor.documentBaseURI.getURI()) + '">' +
|
||||
contentCssEntries +
|
||||
preventClicksOnLinksScript +
|
||||
'</head>' +
|
||||
'<body class="' + encode(bodyClass) + '"' + dirAttr + '>' +
|
||||
previewHtml +
|
||||
'</body>' +
|
||||
'</html>'
|
||||
);
|
||||
}
|
||||
|
||||
return Templates.replaceTemplateValues(previewHtml, Options.getPreviewReplaceValues(editor));
|
||||
};
|
||||
|
||||
const open = (editor: Editor, templateList: ExternalTemplate[]): void => {
|
||||
const createTemplates = (): Optional<Array<InternalTemplate>> => {
|
||||
if (!templateList || templateList.length === 0) {
|
||||
const message = editor.translate('No templates defined.');
|
||||
editor.notificationManager.open({ text: message, type: 'info' });
|
||||
return Optional.none();
|
||||
}
|
||||
|
||||
return Optional.from(Tools.map(templateList, (template: ExternalTemplate, index) => {
|
||||
const isUrlTemplate = (t: ExternalTemplate): t is UrlTemplate => (t as UrlTemplate).url !== undefined;
|
||||
return {
|
||||
selected: index === 0,
|
||||
text: template.title,
|
||||
value: {
|
||||
url: isUrlTemplate(template) ? Optional.from(template.url) : Optional.none(),
|
||||
content: !isUrlTemplate(template) ? Optional.from(template.content) : Optional.none(),
|
||||
description: template.description
|
||||
}
|
||||
};
|
||||
}));
|
||||
};
|
||||
|
||||
const createSelectBoxItems = (templates: InternalTemplate[]) => Arr.map(templates, (t) => ({
|
||||
text: t.text,
|
||||
value: t.text
|
||||
}));
|
||||
|
||||
const findTemplate = (templates: InternalTemplate[], templateTitle: string) => Arr.find(templates, (t) => t.text === templateTitle);
|
||||
|
||||
const loadFailedAlert = (api: Dialog.DialogInstanceApi<DialogData>) => {
|
||||
editor.windowManager.alert('Could not load the specified template.', () => api.focus('template'));
|
||||
};
|
||||
|
||||
const getTemplateContent = (t: InternalTemplate): Promise<string> =>
|
||||
t.value.url.fold(
|
||||
() => Promise.resolve(t.value.content.getOr('')),
|
||||
(url) => fetch(url).then((res) => res.ok ? res.text() : Promise.reject())
|
||||
);
|
||||
|
||||
const onChange = (templates: InternalTemplate[], updateDialog: UpdateDialogCallback) =>
|
||||
(api: Dialog.DialogInstanceApi<DialogData>, change: { name: string }) => {
|
||||
if (change.name === 'template') {
|
||||
const newTemplateTitle = api.getData().template;
|
||||
findTemplate(templates, newTemplateTitle).each((t) => {
|
||||
api.block('Loading...');
|
||||
getTemplateContent(t).then((previewHtml) => {
|
||||
updateDialog(api, t, previewHtml);
|
||||
}).catch(() => {
|
||||
updateDialog(api, t, '');
|
||||
api.setEnabled('save', false);
|
||||
loadFailedAlert(api);
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const onSubmit = (templates: InternalTemplate[]) => (api: Dialog.DialogInstanceApi<DialogData>) => {
|
||||
const data = api.getData();
|
||||
findTemplate(templates, data.template).each((t) => {
|
||||
getTemplateContent(t).then((previewHtml) => {
|
||||
editor.execCommand('mceInsertTemplate', false, previewHtml);
|
||||
api.close();
|
||||
}).catch(() => {
|
||||
api.setEnabled('save', false);
|
||||
loadFailedAlert(api);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const openDialog = (templates: InternalTemplate[]) => {
|
||||
const selectBoxItems = createSelectBoxItems(templates);
|
||||
|
||||
const buildDialogSpec = (bodyItems: Dialog.BodyComponentSpec[], initialData: DialogData): Dialog.DialogSpec<DialogData> => ({
|
||||
title: 'Insert Template',
|
||||
size: 'large',
|
||||
body: {
|
||||
type: 'panel',
|
||||
items: bodyItems
|
||||
},
|
||||
initialData,
|
||||
buttons: [
|
||||
{
|
||||
type: 'cancel',
|
||||
name: 'cancel',
|
||||
text: 'Cancel'
|
||||
},
|
||||
{
|
||||
type: 'submit',
|
||||
name: 'save',
|
||||
text: 'Save',
|
||||
primary: true
|
||||
}
|
||||
],
|
||||
onSubmit: onSubmit(templates),
|
||||
onChange: onChange(templates, updateDialog)
|
||||
});
|
||||
|
||||
const updateDialog = (dialogApi: Dialog.DialogInstanceApi<DialogData>, template: InternalTemplate, previewHtml: string) => {
|
||||
const content = getPreviewContent(editor, previewHtml);
|
||||
const bodyItems: Dialog.BodyComponentSpec[] = [
|
||||
{
|
||||
type: 'listbox',
|
||||
name: 'template',
|
||||
label: 'Templates',
|
||||
items: selectBoxItems
|
||||
},
|
||||
{
|
||||
type: 'htmlpanel',
|
||||
html: `<p aria-live="polite">${Utils.htmlEscape(template.value.description)}</p>`
|
||||
},
|
||||
{
|
||||
label: 'Preview',
|
||||
type: 'iframe',
|
||||
name: 'preview',
|
||||
sandboxed: false,
|
||||
transparent: false
|
||||
}
|
||||
];
|
||||
|
||||
const initialData = {
|
||||
template: template.text,
|
||||
preview: content
|
||||
};
|
||||
|
||||
dialogApi.unblock();
|
||||
dialogApi.redial(buildDialogSpec(bodyItems, initialData));
|
||||
dialogApi.focus('template');
|
||||
};
|
||||
|
||||
const dialogApi = editor.windowManager.open(buildDialogSpec([], { template: '', preview: '' }));
|
||||
dialogApi.block('Loading...');
|
||||
|
||||
getTemplateContent(templates[0]).then((previewHtml) => {
|
||||
updateDialog(dialogApi, templates[0], previewHtml);
|
||||
}).catch(() => {
|
||||
updateDialog(dialogApi, templates[0], '');
|
||||
dialogApi.setEnabled('save', false);
|
||||
loadFailedAlert(dialogApi);
|
||||
});
|
||||
};
|
||||
|
||||
const optTemplates: Optional<InternalTemplate[]> = createTemplates();
|
||||
optTemplates.each(openDialog);
|
||||
};
|
||||
|
||||
export {
|
||||
open,
|
||||
getPreviewContent
|
||||
};
|
@ -1 +0,0 @@
|
||||
<p><em>this is external</em></p>
|
@ -1,140 +0,0 @@
|
||||
import { afterEach, describe, it } from '@ephox/bedrock-client';
|
||||
import { TinyAssertions, TinyHooks, TinySelections } from '@ephox/wrap-mcagar';
|
||||
|
||||
import Editor from 'tinymce/core/api/Editor';
|
||||
import Plugin from 'tinymce/plugins/template/Plugin';
|
||||
|
||||
import { pInsertTemplate } from '../module/InsertTemplate';
|
||||
import { Settings } from '../module/Settings';
|
||||
|
||||
describe('browser.tinymce.plugins.template.DatesTest', () => {
|
||||
const hook = TinyHooks.bddSetupLight<Editor>({
|
||||
plugins: 'template',
|
||||
toolbar: 'template',
|
||||
base_url: '/project/tinymce/js/tinymce'
|
||||
}, [ Plugin ]);
|
||||
|
||||
const { addSettings, cleanupSettings } = Settings(hook);
|
||||
|
||||
afterEach(() => {
|
||||
const editor = hook.editor();
|
||||
cleanupSettings();
|
||||
editor.setContent('');
|
||||
});
|
||||
|
||||
it('TBA: Test cdate in snippet with default class', async () => {
|
||||
const editor = hook.editor();
|
||||
addSettings({
|
||||
templates: [{ title: 'a', description: 'b', content: '<p class="cdate">x</p>' }],
|
||||
template_cdate_format: 'fake date',
|
||||
});
|
||||
await pInsertTemplate(editor);
|
||||
TinyAssertions.assertContent(editor, '<p class="cdate">fake date</p>');
|
||||
});
|
||||
|
||||
it('TBA: Test cdate in snippet with custom class', async () => {
|
||||
const editor = hook.editor();
|
||||
addSettings({
|
||||
template_cdate_classes: 'customCdateClass',
|
||||
templates: [{ title: 'a', description: 'b', content: '<p class="customCdateClass">x</p>' }],
|
||||
template_cdate_format: 'fake date'
|
||||
});
|
||||
await pInsertTemplate(editor);
|
||||
TinyAssertions.assertContent(editor,
|
||||
'<p class="customCdateClass">fake date</p>'
|
||||
);
|
||||
});
|
||||
|
||||
it('TBA: Test mdate updates with each serialization', async () => {
|
||||
const editor = hook.editor();
|
||||
addSettings({
|
||||
template_mdate_format: 'fake modified date',
|
||||
template_cdate_format: 'fake created date',
|
||||
templates: [{ title: 'a', description: 'b', content: '<div class="mceTmpl"><p class="mdate"></p><p class="cdate"></p></div>' }]
|
||||
});
|
||||
await pInsertTemplate(editor);
|
||||
TinyAssertions.assertContent(editor, [
|
||||
'<div class="mceTmpl">',
|
||||
'<p class="mdate">fake modified date</p>',
|
||||
'<p class="cdate">fake created date</p>',
|
||||
'</div>'
|
||||
].join('\n'));
|
||||
addSettings({ template_mdate_format: 'changed modified date' });
|
||||
TinyAssertions.assertContent(editor, [
|
||||
'<div class="mceTmpl">',
|
||||
'<p class="mdate">changed modified date</p>',
|
||||
'<p class="cdate">fake created date</p>',
|
||||
'</div>'
|
||||
].join('\n'));
|
||||
});
|
||||
|
||||
it('TBA: Test mdate updates with each serialization with custom class', async () => {
|
||||
const editor = hook.editor();
|
||||
addSettings({
|
||||
template_mdate_classes: 'modified',
|
||||
template_mdate_format: 'fake modified date',
|
||||
template_cdate_format: 'fake created date',
|
||||
templates: [{ title: 'a', description: 'b', content: '<div class="mceTmpl"><p class="modified"></p><p class="cdate"></p></div>' }]
|
||||
});
|
||||
await pInsertTemplate(editor);
|
||||
TinyAssertions.assertContent(editor, [
|
||||
'<div class="mceTmpl">',
|
||||
'<p class="modified">fake modified date</p>',
|
||||
'<p class="cdate">fake created date</p>',
|
||||
'</div>'
|
||||
].join('\n'));
|
||||
addSettings({ template_mdate_format: 'changed modified date' });
|
||||
TinyAssertions.assertContent(editor, [
|
||||
'<div class="mceTmpl">',
|
||||
'<p class="modified">changed modified date</p>',
|
||||
'<p class="cdate">fake created date</p>',
|
||||
'</div>'
|
||||
].join('\n'));
|
||||
});
|
||||
|
||||
it('TBA: Multiple replacement classes provided via options', async () => {
|
||||
const editor = hook.editor();
|
||||
addSettings({
|
||||
templates: [{ title: 'a', description: 'b', content: '<div class="mceTmpl"><p class="cdate1">x</p><p class="mdate2">y</p></div>' }],
|
||||
template_cdate_classes: 'cdate1 cdate2',
|
||||
template_cdate_format: 'fake created date',
|
||||
template_mdate_classes: 'mdate1 mdate2',
|
||||
template_mdate_format: 'fake modified date',
|
||||
});
|
||||
await pInsertTemplate(editor);
|
||||
TinyAssertions.assertContent(editor, [
|
||||
'<div class="mceTmpl">',
|
||||
'<p class="cdate1">fake created date</p>',
|
||||
'<p class="mdate2">fake modified date</p>',
|
||||
'</div>'
|
||||
].join('\n'));
|
||||
TinySelections.setCursor(editor, [ 0, 1, 0 ], 'fake modified date'.length);
|
||||
editor.insertContent('<p class="mdate1">inserted modified date</p>');
|
||||
addSettings({ template_mdate_format: 'changed modified date' });
|
||||
TinyAssertions.assertContent(editor, [
|
||||
'<div class="mceTmpl">',
|
||||
'<p class="cdate1">fake created date</p>',
|
||||
'<p class="mdate2">changed modified date</p>',
|
||||
'<p class="mdate1">changed modified date</p>',
|
||||
'</div>'
|
||||
].join('\n'));
|
||||
});
|
||||
|
||||
it('TINY-7433: replacement classes with regex like names', async () => {
|
||||
const editor = hook.editor();
|
||||
addSettings({
|
||||
templates: [{ title: 'a', description: 'b', content: '<div class="mceTmpl"><p class="custom+cdate">x</p><p class="custom+mdate">y</p></div>' }],
|
||||
template_cdate_classes: 'custom+cdate',
|
||||
template_cdate_format: 'fake created date',
|
||||
template_mdate_classes: 'custom+mdate',
|
||||
template_mdate_format: 'fake modified date',
|
||||
});
|
||||
await pInsertTemplate(editor);
|
||||
TinyAssertions.assertContent(editor, [
|
||||
'<div class="mceTmpl">',
|
||||
'<p class="custom+cdate">fake created date</p>',
|
||||
'<p class="custom+mdate">fake modified date</p>',
|
||||
'</div>'
|
||||
].join('\n'));
|
||||
});
|
||||
});
|
@ -1,119 +0,0 @@
|
||||
import { afterEach, describe, it } from '@ephox/bedrock-client';
|
||||
import { TinyHooks } from '@ephox/wrap-mcagar';
|
||||
import { assert } from 'chai';
|
||||
|
||||
import Editor from 'tinymce/core/api/Editor';
|
||||
import Env from 'tinymce/core/api/Env';
|
||||
import Plugin from 'tinymce/plugins/template/Plugin';
|
||||
import { getPreviewContent } from 'tinymce/plugins/template/ui/Dialog';
|
||||
|
||||
import { Settings } from '../module/Settings';
|
||||
|
||||
const metaKey = Env.os.isMacOS() || Env.os.isiOS() ? 'e.metaKey' : 'e.ctrlKey && !e.altKey';
|
||||
const host = document.location.host;
|
||||
|
||||
const noCorsNoStyle = '<!DOCTYPE html><html><head>' +
|
||||
`<base href="http://${host}/">` +
|
||||
`<link type="text/css" rel="stylesheet" href="http://${host}/project/tinymce/js/tinymce/skins/ui/oxide/content.min.css">` +
|
||||
`<link type="text/css" rel="stylesheet" href="http://${host}/project/tinymce/js/tinymce/skins/content/default/content.css">` +
|
||||
'<script>document.addEventListener && document.addEventListener("click", function(e) {for (var elm = e.target; elm; elm = elm.parentNode) {if (elm.nodeName === "A" && !(' +
|
||||
metaKey +
|
||||
')) {e.preventDefault();}}}, false);</script> ' +
|
||||
'</head><body class=""></body></html>';
|
||||
|
||||
const corsNoStyle = '<!DOCTYPE html><html><head>' +
|
||||
`<base href="http://${host}/">` +
|
||||
`<link type="text/css" rel="stylesheet" href="http://${host}/project/tinymce/js/tinymce/skins/ui/oxide/content.min.css" crossorigin="anonymous">` +
|
||||
`<link type="text/css" rel="stylesheet" href="http://${host}/project/tinymce/js/tinymce/skins/content/default/content.css" crossorigin="anonymous">` +
|
||||
'<script>document.addEventListener && document.addEventListener("click", function(e) {for (var elm = e.target; elm; elm = elm.parentNode) {if (elm.nodeName === "A" && !(' +
|
||||
metaKey +
|
||||
')) {e.preventDefault();}}}, false);</script> </head><body class=""></body></html>';
|
||||
|
||||
const noCorsStyle = '<!DOCTYPE html><html><head>' +
|
||||
`<base href="http://${host}/">` +
|
||||
`<link type="text/css" rel="stylesheet" href="http://${host}/project/tinymce/js/tinymce/skins/ui/oxide/content.min.css">` +
|
||||
`<link type="text/css" rel="stylesheet" href="http://${host}/project/tinymce/js/tinymce/skins/content/default/content.css">` +
|
||||
'<style type="text/css">This is the style inserted into the document</style>' +
|
||||
'<script>document.addEventListener && document.addEventListener("click", function(e) {for (var elm = e.target; elm; elm = elm.parentNode) {if (elm.nodeName === "A" && !(' +
|
||||
metaKey +
|
||||
')) {e.preventDefault();}}}, false);</script> ' +
|
||||
'</head><body class=""></body></html>';
|
||||
|
||||
const corsStyle = '<!DOCTYPE html><html><head>' +
|
||||
`<base href="http://${host}/">` +
|
||||
`<link type="text/css" rel="stylesheet" href="http://${host}/project/tinymce/js/tinymce/skins/ui/oxide/content.min.css" crossorigin="anonymous">` +
|
||||
`<link type="text/css" rel="stylesheet" href="http://${host}/project/tinymce/js/tinymce/skins/content/default/content.css" crossorigin="anonymous">` +
|
||||
'<style type="text/css">This is the style inserted into the document</style>' +
|
||||
'<script>document.addEventListener && document.addEventListener("click", function(e) {for (var elm = e.target; elm; elm = elm.parentNode) {if (elm.nodeName === "A" && !(' +
|
||||
metaKey +
|
||||
')) {e.preventDefault();}}}, false);</script> ' +
|
||||
'</head><body class=""></body></html>';
|
||||
|
||||
const corsStyleAndContent = '<!DOCTYPE html><html><head>' +
|
||||
`<base href="http://${host}/">` +
|
||||
`<link type="text/css" rel="stylesheet" href="http://${host}/project/tinymce/js/tinymce/skins/ui/oxide/content.min.css" crossorigin="anonymous">` +
|
||||
`<link type="text/css" rel="stylesheet" href="http://${host}/project/tinymce/js/tinymce/skins/content/default/content.css" crossorigin="anonymous">` +
|
||||
'<style type="text/css">This is the style inserted into the document</style>' +
|
||||
'<script>document.addEventListener && document.addEventListener("click", function(e) {for (var elm = e.target; elm; elm = elm.parentNode) {if (elm.nodeName === "A" && !(' +
|
||||
metaKey +
|
||||
')) {e.preventDefault();}}}, false);</script> ' +
|
||||
'</head>' +
|
||||
'<body class=""><p>Custom content which was provided</p></body></html>';
|
||||
|
||||
// TODO TINY-10480: Investigate flaky tests
|
||||
describe.skip('browser.tinymce.plugins.template.Dialog.getPreviewContent', () => {
|
||||
const hook = TinyHooks.bddSetupLight<Editor>({
|
||||
plugins: 'template',
|
||||
base_url: '/project/tinymce/js/tinymce'
|
||||
}, [ Plugin ]);
|
||||
|
||||
const checkPreview = (expected: string, html: string = '') => {
|
||||
const editor = hook.editor();
|
||||
assert.equal(getPreviewContent(editor, html), expected);
|
||||
};
|
||||
|
||||
const { addSettings, cleanupSettings } = Settings(hook);
|
||||
|
||||
afterEach(() => {
|
||||
cleanupSettings();
|
||||
});
|
||||
|
||||
it('TINY-6115: Dialog.getPreviewContent: No CORS or content style, no previous HTML', () => {
|
||||
checkPreview(noCorsNoStyle);
|
||||
});
|
||||
|
||||
it('TINY-6115: Dialog.getPreviewContent: CORS but no content style, no previous HTML', () => {
|
||||
addSettings({ content_css_cors: true });
|
||||
checkPreview(corsNoStyle);
|
||||
});
|
||||
|
||||
it('TINY-6115: Dialog.getPreviewcontent: No CORS but content style, no previous HTML', () => {
|
||||
addSettings({ content_style: 'This is the style inserted into the document' });
|
||||
checkPreview(noCorsStyle);
|
||||
});
|
||||
|
||||
it('TINY-6115: Dialog.getPreviewContent: No CORS but content style, no previous HTML', () => {
|
||||
addSettings({
|
||||
content_css_cors: true,
|
||||
content_style: 'This is the style inserted into the document'
|
||||
});
|
||||
checkPreview(corsStyle);
|
||||
});
|
||||
|
||||
it('TINY-6115: Dialog.getPreviewContent: with provided content', () => {
|
||||
addSettings({
|
||||
content_css_cors: true,
|
||||
content_style: 'This is the style inserted into the document'
|
||||
});
|
||||
checkPreview(corsStyleAndContent, 'Custom content which was provided');
|
||||
});
|
||||
|
||||
it('TINY-6115: Dialog.getPreviewContent: with provided html', () => {
|
||||
addSettings({
|
||||
content_css_cors: true,
|
||||
content_style: 'This is the style inserted into the document'
|
||||
});
|
||||
// TINY-9867: Preview content is parsed to minimise visual discrepancy with inserted content
|
||||
checkPreview('<p>Custom content here</p>', '<html>Custom content here');
|
||||
});
|
||||
});
|
@ -1,36 +0,0 @@
|
||||
import { UiFinder, Waiter } from '@ephox/agar';
|
||||
import { describe, it } from '@ephox/bedrock-client';
|
||||
import { SugarBody } from '@ephox/sugar';
|
||||
import { TinyAssertions, TinyHooks, TinyUiActions } from '@ephox/wrap-mcagar';
|
||||
|
||||
import Editor from 'tinymce/core/api/Editor';
|
||||
import Plugin from 'tinymce/plugins/template/Plugin';
|
||||
|
||||
const dialogSelector = 'div.tox-dialog';
|
||||
const alertDialogSelector = 'div.tox-dialog.tox-alert-dialog';
|
||||
const toolbarButtonSelector = '[role="toolbar"] button[aria-label="Insert template"]';
|
||||
|
||||
describe('browser.tinymce.plugins.template.InvalidUrlTest', () => {
|
||||
const hook = TinyHooks.bddSetupLight<Editor>({
|
||||
plugins: 'template',
|
||||
toolbar: 'template',
|
||||
base_url: '/project/tinymce/js/tinymce'
|
||||
}, [ Plugin ]);
|
||||
|
||||
it('TBA: Test loading in snippet from file that does not exist', async () => {
|
||||
const editor = hook.editor();
|
||||
editor.setContent('');
|
||||
editor.options.set('templates', [{ title: 'invalid', description: 'b', url: '/custom/404' }, { title: 'a', description: 'a', content: '<strong>c</strong>' }]);
|
||||
TinyUiActions.clickOnToolbar(editor, toolbarButtonSelector);
|
||||
await TinyUiActions.pWaitForDialog(editor);
|
||||
await TinyUiActions.pWaitForPopup(editor, alertDialogSelector);
|
||||
// Click on Save button (should be disabled)
|
||||
TinyUiActions.clickOnUi(editor, 'button.tox-button:contains(OK)');
|
||||
await Waiter.pTryUntil('Alert dialog should close', () => UiFinder.notExists(SugarBody.body(), alertDialogSelector));
|
||||
TinyUiActions.submitDialog(editor);
|
||||
await Waiter.pTryUntil('Dialog should not close', () => UiFinder.exists(SugarBody.body(), dialogSelector));
|
||||
TinyUiActions.cancelDialog(editor);
|
||||
await Waiter.pTryUntil('Dialog should close', () => UiFinder.notExists(SugarBody.body(), dialogSelector));
|
||||
TinyAssertions.assertContent(editor, '');
|
||||
});
|
||||
});
|
@ -1,40 +0,0 @@
|
||||
import { Keys, UiFinder } from '@ephox/agar';
|
||||
import { describe, it } from '@ephox/bedrock-client';
|
||||
import { SugarBody } from '@ephox/sugar';
|
||||
import { TinyHooks, TinySelections, TinyState, TinyUiActions } from '@ephox/wrap-mcagar';
|
||||
|
||||
import Editor from 'tinymce/core/api/Editor';
|
||||
import Plugin from 'tinymce/plugins/template/Plugin';
|
||||
|
||||
describe('browser.tinymce.plugins.template.NoneditableRootTest', () => {
|
||||
const hook = TinyHooks.bddSetup<Editor>({
|
||||
plugins: 'template',
|
||||
toolbar: 'template',
|
||||
base_url: '/project/tinymce/js/tinymce'
|
||||
}, [ Plugin ], true);
|
||||
|
||||
it('TINY-9669: Disable template button on noneditable content', () => {
|
||||
TinyState.withNoneditableRootEditor(hook.editor(), (editor) => {
|
||||
editor.setContent('<div>Noneditable content</div><div contenteditable="true">Editable content</div>');
|
||||
TinySelections.setSelection(editor, [ 0, 0 ], 0, [ 0, 0 ], 2);
|
||||
UiFinder.exists(SugarBody.body(), '[aria-label="Insert template"][aria-disabled="true"]');
|
||||
TinySelections.setSelection(editor, [ 1, 0 ], 0, [ 1, 0 ], 2);
|
||||
UiFinder.exists(SugarBody.body(), '[aria-label="Insert template"][aria-disabled="false"]');
|
||||
});
|
||||
});
|
||||
|
||||
it('TINY-9669: Disable template menuitem on noneditable content', async () => {
|
||||
await TinyState.withNoneditableRootEditorAsync(hook.editor(), async (editor) => {
|
||||
editor.setContent('<div>Noneditable content</div><div contenteditable="true">Editable content</div>');
|
||||
TinySelections.setSelection(editor, [ 0, 0 ], 0, [ 0, 0 ], 2);
|
||||
TinyUiActions.clickOnMenu(editor, 'button:contains("Insert")');
|
||||
await TinyUiActions.pWaitForUi(editor, '[role="menuitem"][aria-label="Insert template..."][aria-disabled="true"]');
|
||||
TinyUiActions.keystroke(editor, Keys.escape());
|
||||
TinySelections.setSelection(editor, [ 1, 0 ], 0, [ 1, 0 ], 2);
|
||||
TinyUiActions.clickOnMenu(editor, 'button:contains("Insert")');
|
||||
await TinyUiActions.pWaitForUi(editor, '[role="menuitem"][aria-label="Insert template..."][aria-disabled="false"]');
|
||||
TinyUiActions.keystroke(editor, Keys.escape());
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -1,48 +0,0 @@
|
||||
import { afterEach, describe, it } from '@ephox/bedrock-client';
|
||||
import { TinyAssertions, TinyHooks, TinySelections } from '@ephox/wrap-mcagar';
|
||||
|
||||
import Editor from 'tinymce/core/api/Editor';
|
||||
import Plugin from 'tinymce/plugins/template/Plugin';
|
||||
|
||||
import { pInsertTemplate } from '../module/InsertTemplate';
|
||||
import { Settings } from '../module/Settings';
|
||||
|
||||
describe('browser.tinymce.plugins.template.SelectedContentTest', () => {
|
||||
const hook = TinyHooks.bddSetupLight<Editor>({
|
||||
plugins: 'template',
|
||||
toolbar: 'template',
|
||||
base_url: '/project/tinymce/js/tinymce'
|
||||
}, [ Plugin ]);
|
||||
|
||||
const { addSettings, cleanupSettings } = Settings(hook);
|
||||
|
||||
afterEach(() => {
|
||||
const editor = hook.editor();
|
||||
cleanupSettings();
|
||||
editor.setContent('');
|
||||
});
|
||||
|
||||
it('TBA: Test selected content replacement with default class', async () => {
|
||||
const editor = hook.editor();
|
||||
editor.setContent('Text');
|
||||
TinySelections.setSelection(editor, [ 0, 0 ], 0, [ 0, 0 ], 4);
|
||||
addSettings({
|
||||
templates: [{ title: 'a', description: 'b', content: '<h1 class="selcontent">This will be replaced</h1>' }],
|
||||
});
|
||||
await pInsertTemplate(editor);
|
||||
TinyAssertions.assertContent(editor, '<h1 class="selcontent">Text</h1>');
|
||||
});
|
||||
|
||||
it('TBA: Test selected content replacement with custom class', async () => {
|
||||
const editor = hook.editor();
|
||||
editor.setContent('Text');
|
||||
TinySelections.setSelection(editor, [ 0, 0 ], 0, [ 0, 0 ], 4);
|
||||
addSettings({
|
||||
template_selected_content_classes: 'customSelected',
|
||||
templates: [{ title: 'a', description: 'b', content: '<h1 class="customSelected">This will be replaced/h1>' }],
|
||||
});
|
||||
await pInsertTemplate(editor);
|
||||
TinyAssertions.assertContent(editor, '<h1 class="customSelected">Text</h1>'
|
||||
);
|
||||
});
|
||||
});
|
@ -1,151 +0,0 @@
|
||||
import { UiFinder, Waiter } from '@ephox/agar';
|
||||
import { afterEach, beforeEach, context, describe, it } from '@ephox/bedrock-client';
|
||||
import { Cell } from '@ephox/katamari';
|
||||
import { SugarElement } from '@ephox/sugar';
|
||||
import { TinyAssertions, TinyHooks } from '@ephox/wrap-mcagar';
|
||||
import { assert } from 'chai';
|
||||
|
||||
import Editor from 'tinymce/core/api/Editor';
|
||||
import Plugin from 'tinymce/plugins/template/Plugin';
|
||||
|
||||
import { pInsertTemplate, pPreviewTemplate } from '../module/InsertTemplate';
|
||||
import { Settings } from '../module/Settings';
|
||||
|
||||
describe('browser.tinymce.plugins.template.TemplateSanityTest', () => {
|
||||
const hook = TinyHooks.bddSetupLight<Editor>({
|
||||
plugins: 'template',
|
||||
toolbar: 'template',
|
||||
base_url: '/project/tinymce/js/tinymce'
|
||||
}, [ Plugin ]);
|
||||
|
||||
const { addSettings, cleanupSettings } = Settings(hook);
|
||||
|
||||
beforeEach(() => {
|
||||
const editor = hook.editor();
|
||||
editor.setContent('');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
cleanupSettings();
|
||||
});
|
||||
|
||||
it('TBA: Test basic template insertion', async () => {
|
||||
const editor = hook.editor();
|
||||
addSettings({
|
||||
templates: [{ title: 'a', description: 'b', content: '<strong>c</strong>' }],
|
||||
});
|
||||
await pInsertTemplate(editor);
|
||||
TinyAssertions.assertContent(editor, '<p><strong>c</strong></p>');
|
||||
});
|
||||
|
||||
it('TBA: Test basic content replacement', async () => {
|
||||
const editor = hook.editor();
|
||||
addSettings({
|
||||
template_replace_values: { name: 'Tester', email: 'test@test.com' },
|
||||
templates: [{ title: 'a', description: 'b', content: '<p>{$name} {$email}</p>' }]
|
||||
});
|
||||
await pInsertTemplate(editor);
|
||||
TinyAssertions.assertContent(editor, '<p>Tester test@test.com</p>');
|
||||
});
|
||||
|
||||
it('TBA: Test loading in snippet from other file', async () => {
|
||||
const editor = hook.editor();
|
||||
addSettings({
|
||||
templates: [{ title: 'a', description: '<strong>b</strong>', url: '/project/tinymce/src/plugins/template/test/html/test_template.html' }]
|
||||
});
|
||||
await pInsertTemplate(editor, (dialogEl) => {
|
||||
UiFinder.exists(dialogEl, 'p:contains("<strong>b</strong>")');
|
||||
});
|
||||
TinyAssertions.assertContent(editor, '<p><em>this is external</em></p>');
|
||||
});
|
||||
|
||||
it('TBA: Test command', () => {
|
||||
const editor = hook.editor();
|
||||
addSettings({
|
||||
template_replace_values: { name: 'Tester', email: 'test@test.com' },
|
||||
});
|
||||
editor.execCommand('mceInsertTemplate', false, '<p>{$name}</p>');
|
||||
TinyAssertions.assertContent(editor, '<p>Tester</p>');
|
||||
});
|
||||
|
||||
it('TINY-7433: Replace template values with regex like keys', () => {
|
||||
const editor = hook.editor();
|
||||
addSettings({
|
||||
template_replace_values: { 'first+name': 'Tester', 'email': 'test@test.com' },
|
||||
});
|
||||
editor.execCommand('mceInsertTemplate', false, '<p>{$first+name}</p>');
|
||||
TinyAssertions.assertContent(editor, '<p>Tester</p>');
|
||||
});
|
||||
|
||||
context('Previewing unparsed content', () => {
|
||||
const unparsedHtml = '<img src="error" onerror="throw new Error();">';
|
||||
const unparsedPreviewHtmlSelector = 'p > img[src="error"][onerror="throw new Error();"]';
|
||||
const parsedPreviewHtmlSelector = 'p > img[src="error"][data-mce-src="error"]';
|
||||
|
||||
const pPreviewAndAssertNoUnparsedContent = async (editor: Editor): Promise<void> => {
|
||||
const assertNoUnparsedContent = (dialogEl: SugarElement<Node>): void => {
|
||||
UiFinder.findIn<HTMLIFrameElement>(dialogEl, 'iframe').fold(
|
||||
() => assert.fail('Preview iframe not found'),
|
||||
(iframe) => {
|
||||
const iframeDoc = iframe.dom.contentDocument;
|
||||
const iframeBody = SugarElement.fromDom(iframeDoc?.body as Node);
|
||||
UiFinder.exists(iframeBody, parsedPreviewHtmlSelector);
|
||||
UiFinder.notExists(iframeBody, unparsedPreviewHtmlSelector);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
try {
|
||||
await pPreviewTemplate(editor, assertNoUnparsedContent);
|
||||
} catch {
|
||||
assert.fail('Unparsed html read');
|
||||
}
|
||||
};
|
||||
|
||||
it('TINY-9244: Parsed html should be shown when previewing template', async () => {
|
||||
const editor = hook.editor();
|
||||
addSettings({
|
||||
templates: [{ title: 'a', description: 'b', content: unparsedHtml }],
|
||||
});
|
||||
await pPreviewAndAssertNoUnparsedContent(editor);
|
||||
});
|
||||
|
||||
it('TINY-9867: Parsed html should be shown when previewing template containing <html> tags', async () => {
|
||||
const editor = hook.editor();
|
||||
addSettings({
|
||||
templates: [{ title: 'a', description: 'b', content: `<html>${unparsedHtml}</html>` }],
|
||||
});
|
||||
await pPreviewAndAssertNoUnparsedContent(editor);
|
||||
});
|
||||
});
|
||||
|
||||
context('Inserting unparsed content', () => {
|
||||
const unparsedHtml = '<img src="error" onerror="window.document.unparsedHtmlFn();">';
|
||||
const assertFnDoesNotReadUnParsedHtmlInDom = async (editor: Editor, fn: (unparsedHtml: string) => void | Promise<void>): Promise<void> => {
|
||||
const isUnParsedHtmlRead = Cell(false);
|
||||
(editor.getDoc() as any).unparsedHtmlFn = () => {
|
||||
isUnParsedHtmlRead.set(true);
|
||||
};
|
||||
await fn(unparsedHtml);
|
||||
// wait for any unparsed html to be read and error to be thrown if it is
|
||||
await Waiter.pWait(1);
|
||||
assert.isFalse(isUnParsedHtmlRead.get(), 'Unparsed html read');
|
||||
(editor.getDoc() as any).unparsedHtmlFn = null;
|
||||
};
|
||||
|
||||
it('TINY-9244: Unparsed html should not be read when inserting template via command', async () => {
|
||||
const editor = hook.editor();
|
||||
await assertFnDoesNotReadUnParsedHtmlInDom(editor, (unparsedHtml) => {
|
||||
editor.execCommand('mceInsertTemplate', false, unparsedHtml);
|
||||
});
|
||||
});
|
||||
|
||||
it('TINY-9244: Unparsed html should not be read when inserting template via dialog', async () => {
|
||||
const editor = hook.editor();
|
||||
addSettings({ templates: [{ title: 'a', description: 'b', content: unparsedHtml }] });
|
||||
await assertFnDoesNotReadUnParsedHtmlInDom(editor, async () => {
|
||||
await pInsertTemplate(editor);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -1,45 +0,0 @@
|
||||
import { UiFinder, Waiter } from '@ephox/agar';
|
||||
import { Type } from '@ephox/katamari';
|
||||
import { SugarBody, SugarElement } from '@ephox/sugar';
|
||||
import { TinyUiActions } from '@ephox/wrap-mcagar';
|
||||
|
||||
import Editor from 'tinymce/core/api/Editor';
|
||||
|
||||
const toolbarButtonSelector = '[role="toolbar"] button[aria-label="Insert template"]';
|
||||
const dialogSelector = 'div.tox-dialog';
|
||||
|
||||
const waitUntilIframeLoaded = async (dialogEl: SugarElement<Node>): Promise<void> => {
|
||||
await UiFinder.pWaitForState<HTMLIFrameElement>('iframe is loaded', dialogEl, 'iframe', (elm) => {
|
||||
// fallback for pre-IE 8 using contentWindow.document
|
||||
const iframeDoc = elm.dom.contentDocument || elm.dom.contentWindow?.document;
|
||||
return Type.isNonNullable(iframeDoc?.body.firstChild);
|
||||
});
|
||||
};
|
||||
|
||||
const pUseTemplateDialog = async (editor: Editor, submit: boolean, assertFn?: (elm: SugarElement<Node>) => void): Promise<void> => {
|
||||
TinyUiActions.clickOnToolbar(editor, toolbarButtonSelector);
|
||||
const dialogEl = await TinyUiActions.pWaitForDialog(editor);
|
||||
if (Type.isFunction(assertFn)) {
|
||||
await waitUntilIframeLoaded(dialogEl);
|
||||
assertFn(dialogEl);
|
||||
}
|
||||
if (submit) {
|
||||
TinyUiActions.submitDialog(editor);
|
||||
} else {
|
||||
TinyUiActions.closeDialog(editor);
|
||||
}
|
||||
await Waiter.pTryUntil('Dialog should close', () => UiFinder.notExists(SugarBody.body(), dialogSelector));
|
||||
};
|
||||
|
||||
const pInsertTemplate = async (editor: Editor, assertFn?: (elm: SugarElement<Node>) => void): Promise<void> => {
|
||||
await pUseTemplateDialog(editor, true, assertFn);
|
||||
};
|
||||
|
||||
const pPreviewTemplate = async (editor: Editor, assertFn?: (elm: SugarElement<Node>) => void): Promise<void> => {
|
||||
await pUseTemplateDialog(editor, false, assertFn);
|
||||
};
|
||||
|
||||
export {
|
||||
pInsertTemplate,
|
||||
pPreviewTemplate
|
||||
};
|
@ -1,31 +0,0 @@
|
||||
import { Obj } from '@ephox/katamari';
|
||||
import { TinyHooks } from '@ephox/wrap-mcagar';
|
||||
|
||||
import Editor from 'tinymce/core/api/Editor';
|
||||
|
||||
interface Settings {
|
||||
readonly addSettings: (config: Record<string, any>) => void;
|
||||
readonly cleanupSettings: () => void;
|
||||
}
|
||||
|
||||
const Settings = (hook: TinyHooks.Hook<Editor>): Settings => {
|
||||
let settings = new Set<string>();
|
||||
|
||||
const addSettings = (config: Record<string, any>) => {
|
||||
const editor = hook.editor();
|
||||
Obj.each(config, (val, key) => {
|
||||
editor.options.set(key, val);
|
||||
settings.add(key);
|
||||
});
|
||||
};
|
||||
|
||||
const cleanupSettings = () => {
|
||||
const editor = hook.editor();
|
||||
settings.forEach((key) => editor.options.unset(key));
|
||||
settings = new Set<string>();
|
||||
};
|
||||
|
||||
return { addSettings, cleanupSettings };
|
||||
};
|
||||
|
||||
export { Settings };
|
@ -23,7 +23,7 @@ const defaultMenus: Record<string, MenuSpec> = {
|
||||
file: { title: 'File', items: 'newdocument restoredraft | preview | export print | deleteallconversations' },
|
||||
edit: { title: 'Edit', items: 'undo redo | cut copy paste pastetext | selectall | searchreplace' },
|
||||
view: { title: 'View', items: 'code revisionhistory | visualaid visualchars visualblocks | spellchecker | preview fullscreen | showcomments' },
|
||||
insert: { title: 'Insert', items: 'image link media addcomment pageembed template inserttemplate codesample inserttable accordion | charmap emoticons hr | pagebreak nonbreaking anchor tableofcontents footnotes | mergetags | insertdatetime' },
|
||||
insert: { title: 'Insert', items: 'image link media addcomment pageembed inserttemplate codesample inserttable accordion | charmap emoticons hr | pagebreak nonbreaking anchor tableofcontents footnotes | mergetags | insertdatetime' },
|
||||
format: { title: 'Format', items: 'bold italic underline strikethrough superscript subscript codeformat | styles blocks fontfamily fontsize align lineheight | forecolor backcolor | language | removeformat' },
|
||||
tools: { title: 'Tools', items: 'aidialog aishortcuts | spellchecker spellcheckerlanguage | autocorrect capitalization | a11ycheck code typography wordcount addtemplate' },
|
||||
table: { title: 'Table', items: 'inserttable | cell row column | advtablesort | tableprops deletetable' },
|
||||
|
@ -40,7 +40,6 @@
|
||||
"tinymce/plugins/save/*": ["src/plugins/save/main/ts/*"],
|
||||
"tinymce/plugins/searchreplace/*": ["src/plugins/searchreplace/main/ts/*"],
|
||||
"tinymce/plugins/table/*": ["src/plugins/table/main/ts/*"],
|
||||
"tinymce/plugins/template/*": ["src/plugins/template/main/ts/*"],
|
||||
"tinymce/plugins/visualblocks/*": ["src/plugins/visualblocks/main/ts/*"],
|
||||
"tinymce/plugins/visualchars/*": ["src/plugins/visualchars/main/ts/*"],
|
||||
"tinymce/plugins/wordcount/*": ["src/plugins/wordcount/main/ts/*"],
|
||||
|
@ -41,7 +41,6 @@
|
||||
"tinymce/plugins/save/*": ["modules/tinymce/src/plugins/save/main/ts/*"],
|
||||
"tinymce/plugins/searchreplace/*": ["modules/tinymce/src/plugins/searchreplace/main/ts/*"],
|
||||
"tinymce/plugins/table/*": ["modules/tinymce/src/plugins/table/main/ts/*"],
|
||||
"tinymce/plugins/template/*": ["modules/tinymce/src/plugins/template/main/ts/*"],
|
||||
"tinymce/plugins/visualblocks/*": ["modules/tinymce/src/plugins/visualblocks/main/ts/*"],
|
||||
"tinymce/plugins/visualchars/*": ["modules/tinymce/src/plugins/visualchars/main/ts/*"],
|
||||
"tinymce/plugins/wordcount/*": ["modules/tinymce/src/plugins/wordcount/main/ts/*"],
|
||||
|
@ -63,7 +63,6 @@
|
||||
"tinymce/plugins/save/*": ["modules/tinymce/src/plugins/save/main/ts/*"],
|
||||
"tinymce/plugins/searchreplace/*": ["modules/tinymce/src/plugins/searchreplace/main/ts/*"],
|
||||
"tinymce/plugins/table/*": ["modules/tinymce/src/plugins/table/main/ts/*"],
|
||||
"tinymce/plugins/template/*": ["modules/tinymce/src/plugins/template/main/ts/*"],
|
||||
"tinymce/plugins/visualblocks/*": ["modules/tinymce/src/plugins/visualblocks/main/ts/*"],
|
||||
"tinymce/plugins/visualchars/*": ["modules/tinymce/src/plugins/visualchars/main/ts/*"],
|
||||
"tinymce/plugins/wordcount/*": ["modules/tinymce/src/plugins/wordcount/main/ts/*"],
|
||||
|
Reference in New Issue
Block a user