44f0b4beef
Originally I changed the compiled output of TinyMCE from es2015 to es6 to try and allow our own requirejs system to serve the content. This is a change in the TinyMCE repository (or our copy of it). Unfortunately this turned out to not be possible, but I never got around to updating the configuration to stop doing so. Whilst working on this issue, I've removed this change to module compilation, which brings us closer in line to Tiny core, and simplifies the upgrade process.
229 lines
7.5 KiB
JavaScript
229 lines
7.5 KiB
JavaScript
/**
|
|
* TinyMCE version 6.6.2 (2023-08-09)
|
|
*/
|
|
|
|
(function () {
|
|
'use strict';
|
|
|
|
var global$1 = tinymce.util.Tools.resolve('tinymce.PluginManager');
|
|
|
|
const link = () => /(?:[A-Za-z][A-Za-z\d.+-]{0,14}:\/\/(?:[-.~*+=!&;:'%@?^${}(),\w]+@)?|www\.|[-;:&=+$,.\w]+@)[A-Za-z\d-]+(?:\.[A-Za-z\d-]+)*(?::\d+)?(?:\/(?:[-.~*+=!;:'%@$(),\/\w]*[-~*+=%@$()\/\w])?)?(?:\?(?:[-.~*+=!&;:'%@?^${}(),\/\w]+))?(?:#(?:[-.~*+=!&;:'%@?^${}(),\/\w]+))?/g;
|
|
|
|
const option = name => editor => editor.options.get(name);
|
|
const register = editor => {
|
|
const registerOption = editor.options.register;
|
|
registerOption('autolink_pattern', {
|
|
processor: 'regexp',
|
|
default: new RegExp('^' + link().source + '$', 'i')
|
|
});
|
|
registerOption('link_default_target', { processor: 'string' });
|
|
registerOption('link_default_protocol', {
|
|
processor: 'string',
|
|
default: 'https'
|
|
});
|
|
};
|
|
const getAutoLinkPattern = option('autolink_pattern');
|
|
const getDefaultLinkTarget = option('link_default_target');
|
|
const getDefaultLinkProtocol = option('link_default_protocol');
|
|
const allowUnsafeLinkTarget = option('allow_unsafe_link_target');
|
|
|
|
const hasProto = (v, constructor, predicate) => {
|
|
var _a;
|
|
if (predicate(v, constructor.prototype)) {
|
|
return true;
|
|
} else {
|
|
return ((_a = v.constructor) === null || _a === void 0 ? void 0 : _a.name) === constructor.name;
|
|
}
|
|
};
|
|
const typeOf = x => {
|
|
const t = typeof x;
|
|
if (x === null) {
|
|
return 'null';
|
|
} else if (t === 'object' && Array.isArray(x)) {
|
|
return 'array';
|
|
} else if (t === 'object' && hasProto(x, String, (o, proto) => proto.isPrototypeOf(o))) {
|
|
return 'string';
|
|
} else {
|
|
return t;
|
|
}
|
|
};
|
|
const isType = type => value => typeOf(value) === type;
|
|
const eq = t => a => t === a;
|
|
const isString = isType('string');
|
|
const isUndefined = eq(undefined);
|
|
const isNullable = a => a === null || a === undefined;
|
|
const isNonNullable = a => !isNullable(a);
|
|
|
|
const not = f => t => !f(t);
|
|
|
|
const hasOwnProperty = Object.hasOwnProperty;
|
|
const has = (obj, key) => hasOwnProperty.call(obj, key);
|
|
|
|
const checkRange = (str, substr, start) => substr === '' || str.length >= substr.length && str.substr(start, start + substr.length) === substr;
|
|
const contains = (str, substr, start = 0, end) => {
|
|
const idx = str.indexOf(substr, start);
|
|
if (idx !== -1) {
|
|
return isUndefined(end) ? true : idx + substr.length <= end;
|
|
} else {
|
|
return false;
|
|
}
|
|
};
|
|
const startsWith = (str, prefix) => {
|
|
return checkRange(str, prefix, 0);
|
|
};
|
|
|
|
const zeroWidth = '\uFEFF';
|
|
const isZwsp = char => char === zeroWidth;
|
|
const removeZwsp = s => s.replace(/\uFEFF/g, '');
|
|
|
|
var global = tinymce.util.Tools.resolve('tinymce.dom.TextSeeker');
|
|
|
|
const isTextNode = node => node.nodeType === 3;
|
|
const isElement = node => node.nodeType === 1;
|
|
const isBracketOrSpace = char => /^[(\[{ \u00a0]$/.test(char);
|
|
const hasProtocol = url => /^([A-Za-z][A-Za-z\d.+-]*:\/\/)|mailto:/.test(url);
|
|
const isPunctuation = char => /[?!,.;:]/.test(char);
|
|
const findChar = (text, index, predicate) => {
|
|
for (let i = index - 1; i >= 0; i--) {
|
|
const char = text.charAt(i);
|
|
if (!isZwsp(char) && predicate(char)) {
|
|
return i;
|
|
}
|
|
}
|
|
return -1;
|
|
};
|
|
const freefallRtl = (container, offset) => {
|
|
let tempNode = container;
|
|
let tempOffset = offset;
|
|
while (isElement(tempNode) && tempNode.childNodes[tempOffset]) {
|
|
tempNode = tempNode.childNodes[tempOffset];
|
|
tempOffset = isTextNode(tempNode) ? tempNode.data.length : tempNode.childNodes.length;
|
|
}
|
|
return {
|
|
container: tempNode,
|
|
offset: tempOffset
|
|
};
|
|
};
|
|
|
|
const parseCurrentLine = (editor, offset) => {
|
|
var _a;
|
|
const voidElements = editor.schema.getVoidElements();
|
|
const autoLinkPattern = getAutoLinkPattern(editor);
|
|
const {dom, selection} = editor;
|
|
if (dom.getParent(selection.getNode(), 'a[href]') !== null) {
|
|
return null;
|
|
}
|
|
const rng = selection.getRng();
|
|
const textSeeker = global(dom, node => {
|
|
return dom.isBlock(node) || has(voidElements, node.nodeName.toLowerCase()) || dom.getContentEditable(node) === 'false';
|
|
});
|
|
const {
|
|
container: endContainer,
|
|
offset: endOffset
|
|
} = freefallRtl(rng.endContainer, rng.endOffset);
|
|
const root = (_a = dom.getParent(endContainer, dom.isBlock)) !== null && _a !== void 0 ? _a : dom.getRoot();
|
|
const endSpot = textSeeker.backwards(endContainer, endOffset + offset, (node, offset) => {
|
|
const text = node.data;
|
|
const idx = findChar(text, offset, not(isBracketOrSpace));
|
|
return idx === -1 || isPunctuation(text[idx]) ? idx : idx + 1;
|
|
}, root);
|
|
if (!endSpot) {
|
|
return null;
|
|
}
|
|
let lastTextNode = endSpot.container;
|
|
const startSpot = textSeeker.backwards(endSpot.container, endSpot.offset, (node, offset) => {
|
|
lastTextNode = node;
|
|
const idx = findChar(node.data, offset, isBracketOrSpace);
|
|
return idx === -1 ? idx : idx + 1;
|
|
}, root);
|
|
const newRng = dom.createRng();
|
|
if (!startSpot) {
|
|
newRng.setStart(lastTextNode, 0);
|
|
} else {
|
|
newRng.setStart(startSpot.container, startSpot.offset);
|
|
}
|
|
newRng.setEnd(endSpot.container, endSpot.offset);
|
|
const rngText = removeZwsp(newRng.toString());
|
|
const matches = rngText.match(autoLinkPattern);
|
|
if (matches) {
|
|
let url = matches[0];
|
|
if (startsWith(url, 'www.')) {
|
|
const protocol = getDefaultLinkProtocol(editor);
|
|
url = protocol + '://' + url;
|
|
} else if (contains(url, '@') && !hasProtocol(url)) {
|
|
url = 'mailto:' + url;
|
|
}
|
|
return {
|
|
rng: newRng,
|
|
url
|
|
};
|
|
} else {
|
|
return null;
|
|
}
|
|
};
|
|
const convertToLink = (editor, result) => {
|
|
const {dom, selection} = editor;
|
|
const {rng, url} = result;
|
|
const bookmark = selection.getBookmark();
|
|
selection.setRng(rng);
|
|
const command = 'createlink';
|
|
const args = {
|
|
command,
|
|
ui: false,
|
|
value: url
|
|
};
|
|
const beforeExecEvent = editor.dispatch('BeforeExecCommand', args);
|
|
if (!beforeExecEvent.isDefaultPrevented()) {
|
|
editor.getDoc().execCommand(command, false, url);
|
|
editor.dispatch('ExecCommand', args);
|
|
const defaultLinkTarget = getDefaultLinkTarget(editor);
|
|
if (isString(defaultLinkTarget)) {
|
|
const anchor = selection.getNode();
|
|
dom.setAttrib(anchor, 'target', defaultLinkTarget);
|
|
if (defaultLinkTarget === '_blank' && !allowUnsafeLinkTarget(editor)) {
|
|
dom.setAttrib(anchor, 'rel', 'noopener');
|
|
}
|
|
}
|
|
}
|
|
selection.moveToBookmark(bookmark);
|
|
editor.nodeChanged();
|
|
};
|
|
const handleSpacebar = editor => {
|
|
const result = parseCurrentLine(editor, -1);
|
|
if (isNonNullable(result)) {
|
|
convertToLink(editor, result);
|
|
}
|
|
};
|
|
const handleBracket = handleSpacebar;
|
|
const handleEnter = editor => {
|
|
const result = parseCurrentLine(editor, 0);
|
|
if (isNonNullable(result)) {
|
|
convertToLink(editor, result);
|
|
}
|
|
};
|
|
const setup = editor => {
|
|
editor.on('keydown', e => {
|
|
if (e.keyCode === 13 && !e.isDefaultPrevented()) {
|
|
handleEnter(editor);
|
|
}
|
|
});
|
|
editor.on('keyup', e => {
|
|
if (e.keyCode === 32) {
|
|
handleSpacebar(editor);
|
|
} else if (e.keyCode === 48 && e.shiftKey || e.keyCode === 221) {
|
|
handleBracket(editor);
|
|
}
|
|
});
|
|
};
|
|
|
|
var Plugin = () => {
|
|
global$1.add('autolink', editor => {
|
|
register(editor);
|
|
setup(editor);
|
|
});
|
|
};
|
|
|
|
Plugin();
|
|
|
|
})();
|