@tinymce/tinymce-webcomponent
Version:
Official TinyMCE Web Component
573 lines (561 loc) • 18.7 kB
JavaScript
(function () {
'use strict';
const Global = typeof window !== 'undefined' ? window : Function('return this;')();
const path = (parts, scope) => {
let o = scope !== undefined && scope !== null ? scope : Global;
for (let i = 0; i < parts.length && o !== undefined && o !== null; ++i) {
o = o[parts[i]];
}
return o;
};
const resolve = (p, scope) => {
const parts = p.split('.');
return path(parts, scope);
};
const identity = x => {
return x;
};
const keys = Object.keys;
const hasOwnProperty = Object.hasOwnProperty;
const each = (obj, f) => {
const props = keys(obj);
for (let k = 0, len = props.length; k < len; k++) {
const i = props[k];
const x = obj[i];
f(x, i);
}
};
const has = (obj, key) => hasOwnProperty.call(obj, key);
let unique = 0;
const generate = prefix => {
const date = new Date();
const time = date.getTime();
const random = Math.floor(Math.random() * 1000000000);
unique++;
return prefix + '_' + random + unique + String(time);
};
const createState = () => ({
listeners: [],
scriptId: generate('tiny-script'),
scriptLoaded: false
});
const CreateScriptLoader = () => {
let state = createState();
const injectScriptTag = (scriptId, doc, url, callback) => {
const scriptTag = doc.createElement('script');
scriptTag.referrerPolicy = 'origin';
scriptTag.type = 'application/javascript';
scriptTag.id = scriptId;
scriptTag.src = url;
const handler = () => {
scriptTag.removeEventListener('load', handler);
callback();
};
scriptTag.addEventListener('load', handler);
if (doc.head) {
doc.head.appendChild(scriptTag);
}
};
const load = (doc, url, callback) => {
if (state.scriptLoaded) {
callback();
} else {
state.listeners.push(callback);
if (!doc.getElementById(state.scriptId)) {
injectScriptTag(state.scriptId, doc, url, () => {
state.listeners.forEach(fn => fn());
state.scriptLoaded = true;
});
}
}
};
const reinitialize = () => {
state = createState();
};
return {
load,
reinitialize
};
};
const ScriptLoader = CreateScriptLoader();
const toInt = str => parseInt(str, 10);
const cmp = (a, b) => {
const delta = a - b;
if (delta === 0) {
return 0;
}
return delta > 0 ? 1 : -1;
};
const nu = (major, minor, patch) => ({
major,
minor,
patch
});
const parse = versionString => {
const parts = /([0-9]+)\.([0-9]+)\.([0-9]+)(?:(\-.+)?)/.exec(versionString);
return parts ? nu(toInt(parts[1]), toInt(parts[2]), toInt(parts[3])) : nu(0, 0, 0);
};
const compare = (version1, version2) => {
const cmp1 = cmp(version1.major, version2.major);
if (cmp1 !== 0) {
return cmp1;
}
const cmp2 = cmp(version1.minor, version2.minor);
if (cmp2 !== 0) {
return cmp2;
}
const cmp3 = cmp(version1.patch, version2.patch);
if (cmp3 !== 0) {
return cmp3;
}
return 0;
};
const createSemVer = tinymce => {
const semver = [
tinymce.majorVersion,
tinymce.minorVersion
].join('.');
return semver.split('.').slice(0, 3).join('.');
};
const getVersion = tinymce => parse(createSemVer(tinymce));
const isLessThan = (tinymce, version) => !tinymce ? false : compare(getVersion(tinymce), parse(version)) === -1;
var Status;
(function (Status) {
Status[Status['Raw'] = 0] = 'Raw';
Status[Status['Initializing'] = 1] = 'Initializing';
Status[Status['Ready'] = 2] = 'Ready';
}(Status || (Status = {})));
const closestRecursive = (selector, element) => {
const found = element.closest(selector);
if (found !== null) {
return found;
}
const next = element.getRootNode().host;
if (next !== null && next !== undefined) {
return closestRecursive(selector, next);
}
return null;
};
const isLookupKey = (values, key) => has(values, key);
const lookup = values => key => isLookupKey(values, key) ? values[key] : key;
const parseGlobal = resolve;
const parseString = identity;
const parseFalseOrString = lookup({ 'false': false });
const parseBooleanOrString = lookup({
'true': true,
'false': false
});
const parseNumberOrString = value => /^\d+$/.test(value) ? Number.parseInt(value, 10) : value;
const configAttributes = {
setup: parseGlobal,
statusbar: parseBooleanOrString,
toolbar: parseFalseOrString,
menubar: parseFalseOrString,
plugins: parseString,
content_css: parseString,
content_style: parseString,
width: parseNumberOrString,
height: parseNumberOrString,
toolbar_mode: parseString,
contextmenu: parseFalseOrString,
quickbars_insert_toolbar: parseFalseOrString,
quickbars_selection_toolbar: parseFalseOrString,
powerpaste_word_import: parseString,
powerpaste_html_import: parseString,
powerpaste_allow_local_images: parseBooleanOrString,
resize: parseBooleanOrString,
skin: parseString,
skin_url: parseString,
images_upload_url: parseString,
images_upload_handler: parseGlobal,
images_upload_base_path: parseString,
images_upload_credentials: parseBooleanOrString,
images_reuse_filename: parseBooleanOrString,
icons: parseString,
icons_url: parseString,
promotion: parseBooleanOrString
};
const configRenames = {};
const isDisabledOptionSupported = tinymce => !isLessThan(tinymce, '7.6.0');
class TinyMceEditor extends HTMLElement {
static get formAssociated() {
return true;
}
static get observedAttributes() {
const nativeEvents = [
'on-BeforePaste',
'on-Blur',
'on-Click',
'on-ContextMenu',
'on-Copy',
'on-Cut',
'on-Dblclick',
'on-Drag',
'on-DragDrop',
'on-DragEnd',
'on-DragGesture',
'on-DragOver',
'on-Drop',
'on-Focus',
'on-FocusIn',
'on-FocusOut',
'on-KeyDown',
'on-KeyPress',
'on-KeyUp',
'on-MouseDown',
'on-MouseEnter',
'on-MouseLeave',
'on-MouseMove',
'on-MouseOut',
'on-MouseOver',
'on-MouseUp',
'on-Paste',
'on-SelectionChange'
];
const tinyEvents = [
'on-Activate',
'on-AddUndo',
'on-BeforeAddUndo',
'on-BeforeExecCommand',
'on-BeforeGetContent',
'on-BeforeRenderUI',
'on-BeforeSetContent',
'on-Change',
'on-ClearUndos',
'on-Deactivate',
'on-Dirty',
'on-ExecCommand',
'on-GetContent',
'on-Hide',
'on-Init',
'on-LoadContent',
'on-NodeChange',
'on-PostProcess',
'on-PostRender',
'on-PreProcess',
'on-ProgressState',
'on-Redo',
'on-Remove',
'on-Reset',
'on-SaveContent',
'on-SetAttrib',
'on-ObjectResizeStart',
'on-ObjectResized',
'on-ObjectSelected',
'on-SetContent',
'on-Show',
'on-Submit',
'on-Undo',
'on-VisualAid'
];
return [
'form',
'readonly',
'autofocus',
'placeholder',
'disabled'
].concat(nativeEvents).concat(tinyEvents);
}
constructor() {
super();
this._eventAttrHandler = records => {
records.forEach(record => {
var _a;
if (record.type === 'attributes' && record.target === this && ((_a = record.attributeName) === null || _a === void 0 ? void 0 : _a.toLowerCase().startsWith('on-'))) {
this._updateEventAttr(record.attributeName, this.getAttribute(record.attributeName));
}
});
};
this._formDataHandler = evt => {
const name = this.name;
if (name != null) {
const value = this.value;
if (value != null) {
const data = evt.formData;
data.append(name, value);
}
}
};
this._status = Status.Raw;
this._shadowDom = this.attachShadow({ mode: 'open' });
this._form = null;
this._eventHandlers = {};
this._mutationObserver = new MutationObserver(this._eventAttrHandler);
}
_updateEventAttr(attrKey, attrValue) {
const event = attrKey.substring('on-'.length).toLowerCase();
const resolved = attrValue !== null ? resolve(attrValue) : undefined;
const handler = typeof resolved === 'function' ? resolved : undefined;
if (this._eventHandlers[event] !== handler) {
if (this._editor && this._eventHandlers[event]) {
this._editor.off(event, this._eventHandlers[event]);
}
if (handler) {
if (this._editor) {
this._editor.on(event, handler);
}
this._eventHandlers[event] = handler;
} else {
delete this._eventHandlers[event];
}
}
}
_updateForm() {
if (this.isConnected) {
const formId = this.getAttribute('form');
const form = formId !== null ? this.ownerDocument.querySelector('form#' + formId) : closestRecursive('form', this);
if (this._form !== form) {
if (this._form !== null) {
this._form.removeEventListener('formdata', this._formDataHandler);
}
this._form = form;
if (this._form !== null) {
this._form.addEventListener('formdata', this._formDataHandler);
}
}
} else {
if (this._form !== null) {
this._form.removeEventListener('formdata', this._formDataHandler);
this._form = null;
}
}
}
_getTinymce() {
return Global.tinymce;
}
_getConfig() {
var _a, _b;
const config = (_b = parseGlobal((_a = this.getAttribute('config')) !== null && _a !== void 0 ? _a : '')) !== null && _b !== void 0 ? _b : {};
for (let i = 0; i < this.attributes.length; i++) {
const attr = this.attributes.item(i);
if (attr !== null) {
if (has(configAttributes, attr.name)) {
const prop = has(configRenames, attr.name) ? configRenames[attr.name] : attr.name;
config[prop] = configAttributes[attr.name](attr.value);
}
}
}
if (this.readonly) {
config.readonly = true;
}
if (this.disabled) {
config.disabled = true;
}
if (this.autofocus) {
config.auto_focus = true;
}
delete config.target;
delete config.selector;
return config;
}
_getEventHandlers() {
const handlers = {};
for (let i = 0; i < this.attributes.length; i++) {
const attr = this.attributes.item(i);
if (attr !== null) {
if (attr.name.toLowerCase().startsWith('on-')) {
const event = attr.name.toLowerCase().substring('on-'.length);
const handler = resolve(attr.value);
if (typeof handler === 'function') {
handlers[event] = handler;
}
}
}
}
return handlers;
}
_doInit() {
var _a, _b;
this._status = Status.Initializing;
const target = document.createElement('textarea');
target.value = (_a = this.textContent) !== null && _a !== void 0 ? _a : '';
const attrId = (_b = this.attributes.getNamedItem('id')) === null || _b === void 0 ? void 0 : _b.value;
if (attrId) {
target.id = attrId;
}
if (this.placeholder !== null) {
target.placeholder = this.placeholder;
}
this._shadowDom.appendChild(target);
const baseConfig = this._getConfig();
const conf = Object.assign(Object.assign(Object.assign({}, baseConfig), {
disabled: this.hasAttribute('disabled'),
readonly: this.hasAttribute('readonly')
}), {
target,
setup: editor => {
this._editor = editor;
editor.on('init', _e => {
this._status = Status.Ready;
});
editor.on('SwitchMode', _e => {
this.readonly = this.readonly;
});
editor.on('DisabledStateChange', _e => {
this.disabled = this.disabled;
});
each(this._eventHandlers, (handler, event) => {
if (handler !== undefined) {
editor.on(event, handler);
}
});
if (typeof baseConfig.setup === 'function') {
baseConfig.setup(editor);
}
}
});
this._getTinymce().init(conf);
}
_getTinymceSrc() {
var _a;
const src = this.getAttribute('src');
if (src) {
return src;
}
const channel = (_a = this.getAttribute('channel')) !== null && _a !== void 0 ? _a : '8';
const apiKey = this.hasAttribute('api-key') ? this.getAttribute('api-key') : 'no-api-key';
return `https://cdn.tiny.cloud/1/${ apiKey }/tinymce/${ channel }/tinymce.min.js`;
}
_loadTinyDoInit() {
if (this._getTinymce()) {
this._doInit();
} else {
ScriptLoader.load(this.ownerDocument, this._getTinymceSrc(), () => this._doInit());
}
}
attributeChangedCallback(attribute, oldValue, newValue) {
if (oldValue !== newValue) {
if (attribute === 'form') {
this._updateForm();
} else if (attribute === 'disabled') {
this.disabled = newValue !== null;
} else if (attribute === 'readonly') {
this.readonly = newValue !== null;
} else if (attribute === 'autofocus') {
this.autofocus = newValue !== null;
} else if (attribute === 'placeholder') {
this.placeholder = newValue;
} else if (attribute.toLowerCase().startsWith('on-')) {
this._updateEventAttr(attribute, newValue);
}
}
}
connectedCallback() {
this._eventHandlers = this._getEventHandlers();
this._mutationObserver.observe(this, {
attributes: true,
childList: false,
subtree: false
});
this._updateForm();
if (this._status === Status.Raw) {
this._loadTinyDoInit();
}
}
disconnectedCallback() {
this._mutationObserver.disconnect();
this._updateForm();
}
get value() {
var _a, _b;
return (_b = this._status === Status.Ready ? (_a = this._editor) === null || _a === void 0 ? void 0 : _a.getContent() : undefined) !== null && _b !== void 0 ? _b : null;
}
set value(newValue) {
var _a;
if (this._status === Status.Ready && newValue != null) {
(_a = this._editor) === null || _a === void 0 ? void 0 : _a.setContent(newValue);
}
}
get readonly() {
if (this._editor) {
return this._editor.mode.get() === 'readonly';
} else {
return this.hasAttribute('readonly');
}
}
set readonly(value) {
if (value) {
if (this._editor && this._editor.mode.get() !== 'readonly') {
this._editor.mode.set('readonly');
}
if (!this.hasAttribute('readonly')) {
this.setAttribute('readonly', '');
}
} else {
if (this._editor && this._editor.mode.get() === 'readonly') {
this._editor.mode.set('design');
}
if (this.hasAttribute('readonly')) {
this.removeAttribute('readonly');
}
}
}
get disabled() {
return this._editor ? this._editor.options.get('disabled') : this.hasAttribute('disabled');
}
set disabled(value) {
var _a;
const tinymce = (_a = this._getTinymce) === null || _a === void 0 ? void 0 : _a.call(this);
const isVersionNewer = tinymce ? isDisabledOptionSupported(tinymce) : true;
if (this._editor && this._status === Status.Ready && isVersionNewer) {
this._editor.options.set('disabled', value);
}
if (value && !this.hasAttribute('disabled')) {
this.setAttribute('disabled', '');
} else if (!value && this.hasAttribute('disabled')) {
this.removeAttribute('disabled');
}
}
get placeholder() {
return this.getAttribute('placeholder');
}
set placeholder(value) {
if (this._editor) {
const target = this._editor.getElement();
if (target !== null) {
if (value !== null) {
target.setAttribute('placeholder', value);
} else {
target.removeAttribute('placeholder');
}
}
}
if (value !== null) {
if (this.getAttribute('placeholder') !== value) {
this.setAttribute('placeholder', value);
}
} else {
if (this.hasAttribute('placeholder')) {
this.removeAttribute('placeholder');
}
}
}
get autofocus() {
return this.hasAttribute('autofocus');
}
set autofocus(value) {
if (value) {
if (!this.hasAttribute('autofocus')) {
this.setAttribute('autofocus', '');
}
} else {
if (this.hasAttribute('autofocus')) {
this.removeAttribute('autofocus');
}
}
}
get form() {
return this._form;
}
get name() {
return this.getAttribute('name');
}
get type() {
return this.localName;
}
}
var Editor = () => {
window.customElements.define('tinymce-editor', TinyMceEditor);
};
Editor();
})();