1 |
efrain |
1 |
/**
|
|
|
2 |
* TinyMCE version 6.8.3 (2024-02-08)
|
|
|
3 |
*/
|
|
|
4 |
|
|
|
5 |
(function () {
|
|
|
6 |
'use strict';
|
|
|
7 |
|
|
|
8 |
var global$1 = tinymce.util.Tools.resolve('tinymce.PluginManager');
|
|
|
9 |
|
|
|
10 |
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;
|
|
|
11 |
|
|
|
12 |
const option = name => editor => editor.options.get(name);
|
|
|
13 |
const register = editor => {
|
|
|
14 |
const registerOption = editor.options.register;
|
|
|
15 |
registerOption('autolink_pattern', {
|
|
|
16 |
processor: 'regexp',
|
|
|
17 |
default: new RegExp('^' + link().source + '$', 'i')
|
|
|
18 |
});
|
|
|
19 |
registerOption('link_default_target', { processor: 'string' });
|
|
|
20 |
registerOption('link_default_protocol', {
|
|
|
21 |
processor: 'string',
|
|
|
22 |
default: 'https'
|
|
|
23 |
});
|
|
|
24 |
};
|
|
|
25 |
const getAutoLinkPattern = option('autolink_pattern');
|
|
|
26 |
const getDefaultLinkTarget = option('link_default_target');
|
|
|
27 |
const getDefaultLinkProtocol = option('link_default_protocol');
|
|
|
28 |
const allowUnsafeLinkTarget = option('allow_unsafe_link_target');
|
|
|
29 |
|
|
|
30 |
const hasProto = (v, constructor, predicate) => {
|
|
|
31 |
var _a;
|
|
|
32 |
if (predicate(v, constructor.prototype)) {
|
|
|
33 |
return true;
|
|
|
34 |
} else {
|
|
|
35 |
return ((_a = v.constructor) === null || _a === void 0 ? void 0 : _a.name) === constructor.name;
|
|
|
36 |
}
|
|
|
37 |
};
|
|
|
38 |
const typeOf = x => {
|
|
|
39 |
const t = typeof x;
|
|
|
40 |
if (x === null) {
|
|
|
41 |
return 'null';
|
|
|
42 |
} else if (t === 'object' && Array.isArray(x)) {
|
|
|
43 |
return 'array';
|
|
|
44 |
} else if (t === 'object' && hasProto(x, String, (o, proto) => proto.isPrototypeOf(o))) {
|
|
|
45 |
return 'string';
|
|
|
46 |
} else {
|
|
|
47 |
return t;
|
|
|
48 |
}
|
|
|
49 |
};
|
|
|
50 |
const isType = type => value => typeOf(value) === type;
|
|
|
51 |
const eq = t => a => t === a;
|
|
|
52 |
const isString = isType('string');
|
|
|
53 |
const isUndefined = eq(undefined);
|
|
|
54 |
const isNullable = a => a === null || a === undefined;
|
|
|
55 |
const isNonNullable = a => !isNullable(a);
|
|
|
56 |
|
|
|
57 |
const not = f => t => !f(t);
|
|
|
58 |
|
|
|
59 |
const hasOwnProperty = Object.hasOwnProperty;
|
|
|
60 |
const has = (obj, key) => hasOwnProperty.call(obj, key);
|
|
|
61 |
|
|
|
62 |
const checkRange = (str, substr, start) => substr === '' || str.length >= substr.length && str.substr(start, start + substr.length) === substr;
|
|
|
63 |
const contains = (str, substr, start = 0, end) => {
|
|
|
64 |
const idx = str.indexOf(substr, start);
|
|
|
65 |
if (idx !== -1) {
|
|
|
66 |
return isUndefined(end) ? true : idx + substr.length <= end;
|
|
|
67 |
} else {
|
|
|
68 |
return false;
|
|
|
69 |
}
|
|
|
70 |
};
|
|
|
71 |
const startsWith = (str, prefix) => {
|
|
|
72 |
return checkRange(str, prefix, 0);
|
|
|
73 |
};
|
|
|
74 |
|
|
|
75 |
const zeroWidth = '\uFEFF';
|
|
|
76 |
const isZwsp = char => char === zeroWidth;
|
|
|
77 |
const removeZwsp = s => s.replace(/\uFEFF/g, '');
|
|
|
78 |
|
|
|
79 |
var global = tinymce.util.Tools.resolve('tinymce.dom.TextSeeker');
|
|
|
80 |
|
|
|
81 |
const isTextNode = node => node.nodeType === 3;
|
|
|
82 |
const isElement = node => node.nodeType === 1;
|
|
|
83 |
const isBracketOrSpace = char => /^[(\[{ \u00a0]$/.test(char);
|
|
|
84 |
const hasProtocol = url => /^([A-Za-z][A-Za-z\d.+-]*:\/\/)|mailto:/.test(url);
|
|
|
85 |
const isPunctuation = char => /[?!,.;:]/.test(char);
|
|
|
86 |
const findChar = (text, index, predicate) => {
|
|
|
87 |
for (let i = index - 1; i >= 0; i--) {
|
|
|
88 |
const char = text.charAt(i);
|
|
|
89 |
if (!isZwsp(char) && predicate(char)) {
|
|
|
90 |
return i;
|
|
|
91 |
}
|
|
|
92 |
}
|
|
|
93 |
return -1;
|
|
|
94 |
};
|
|
|
95 |
const freefallRtl = (container, offset) => {
|
|
|
96 |
let tempNode = container;
|
|
|
97 |
let tempOffset = offset;
|
|
|
98 |
while (isElement(tempNode) && tempNode.childNodes[tempOffset]) {
|
|
|
99 |
tempNode = tempNode.childNodes[tempOffset];
|
|
|
100 |
tempOffset = isTextNode(tempNode) ? tempNode.data.length : tempNode.childNodes.length;
|
|
|
101 |
}
|
|
|
102 |
return {
|
|
|
103 |
container: tempNode,
|
|
|
104 |
offset: tempOffset
|
|
|
105 |
};
|
|
|
106 |
};
|
|
|
107 |
|
|
|
108 |
const parseCurrentLine = (editor, offset) => {
|
|
|
109 |
var _a;
|
|
|
110 |
const voidElements = editor.schema.getVoidElements();
|
|
|
111 |
const autoLinkPattern = getAutoLinkPattern(editor);
|
|
|
112 |
const {dom, selection} = editor;
|
|
|
113 |
if (dom.getParent(selection.getNode(), 'a[href]') !== null) {
|
|
|
114 |
return null;
|
|
|
115 |
}
|
|
|
116 |
const rng = selection.getRng();
|
|
|
117 |
const textSeeker = global(dom, node => {
|
|
|
118 |
return dom.isBlock(node) || has(voidElements, node.nodeName.toLowerCase()) || dom.getContentEditable(node) === 'false';
|
|
|
119 |
});
|
|
|
120 |
const {
|
|
|
121 |
container: endContainer,
|
|
|
122 |
offset: endOffset
|
|
|
123 |
} = freefallRtl(rng.endContainer, rng.endOffset);
|
|
|
124 |
const root = (_a = dom.getParent(endContainer, dom.isBlock)) !== null && _a !== void 0 ? _a : dom.getRoot();
|
|
|
125 |
const endSpot = textSeeker.backwards(endContainer, endOffset + offset, (node, offset) => {
|
|
|
126 |
const text = node.data;
|
|
|
127 |
const idx = findChar(text, offset, not(isBracketOrSpace));
|
|
|
128 |
return idx === -1 || isPunctuation(text[idx]) ? idx : idx + 1;
|
|
|
129 |
}, root);
|
|
|
130 |
if (!endSpot) {
|
|
|
131 |
return null;
|
|
|
132 |
}
|
|
|
133 |
let lastTextNode = endSpot.container;
|
|
|
134 |
const startSpot = textSeeker.backwards(endSpot.container, endSpot.offset, (node, offset) => {
|
|
|
135 |
lastTextNode = node;
|
|
|
136 |
const idx = findChar(node.data, offset, isBracketOrSpace);
|
|
|
137 |
return idx === -1 ? idx : idx + 1;
|
|
|
138 |
}, root);
|
|
|
139 |
const newRng = dom.createRng();
|
|
|
140 |
if (!startSpot) {
|
|
|
141 |
newRng.setStart(lastTextNode, 0);
|
|
|
142 |
} else {
|
|
|
143 |
newRng.setStart(startSpot.container, startSpot.offset);
|
|
|
144 |
}
|
|
|
145 |
newRng.setEnd(endSpot.container, endSpot.offset);
|
|
|
146 |
const rngText = removeZwsp(newRng.toString());
|
|
|
147 |
const matches = rngText.match(autoLinkPattern);
|
|
|
148 |
if (matches) {
|
|
|
149 |
let url = matches[0];
|
|
|
150 |
if (startsWith(url, 'www.')) {
|
|
|
151 |
const protocol = getDefaultLinkProtocol(editor);
|
|
|
152 |
url = protocol + '://' + url;
|
|
|
153 |
} else if (contains(url, '@') && !hasProtocol(url)) {
|
|
|
154 |
url = 'mailto:' + url;
|
|
|
155 |
}
|
|
|
156 |
return {
|
|
|
157 |
rng: newRng,
|
|
|
158 |
url
|
|
|
159 |
};
|
|
|
160 |
} else {
|
|
|
161 |
return null;
|
|
|
162 |
}
|
|
|
163 |
};
|
|
|
164 |
const convertToLink = (editor, result) => {
|
|
|
165 |
const {dom, selection} = editor;
|
|
|
166 |
const {rng, url} = result;
|
|
|
167 |
const bookmark = selection.getBookmark();
|
|
|
168 |
selection.setRng(rng);
|
|
|
169 |
const command = 'createlink';
|
|
|
170 |
const args = {
|
|
|
171 |
command,
|
|
|
172 |
ui: false,
|
|
|
173 |
value: url
|
|
|
174 |
};
|
|
|
175 |
const beforeExecEvent = editor.dispatch('BeforeExecCommand', args);
|
|
|
176 |
if (!beforeExecEvent.isDefaultPrevented()) {
|
|
|
177 |
editor.getDoc().execCommand(command, false, url);
|
|
|
178 |
editor.dispatch('ExecCommand', args);
|
|
|
179 |
const defaultLinkTarget = getDefaultLinkTarget(editor);
|
|
|
180 |
if (isString(defaultLinkTarget)) {
|
|
|
181 |
const anchor = selection.getNode();
|
|
|
182 |
dom.setAttrib(anchor, 'target', defaultLinkTarget);
|
|
|
183 |
if (defaultLinkTarget === '_blank' && !allowUnsafeLinkTarget(editor)) {
|
|
|
184 |
dom.setAttrib(anchor, 'rel', 'noopener');
|
|
|
185 |
}
|
|
|
186 |
}
|
|
|
187 |
}
|
|
|
188 |
selection.moveToBookmark(bookmark);
|
|
|
189 |
editor.nodeChanged();
|
|
|
190 |
};
|
|
|
191 |
const handleSpacebar = editor => {
|
|
|
192 |
const result = parseCurrentLine(editor, -1);
|
|
|
193 |
if (isNonNullable(result)) {
|
|
|
194 |
convertToLink(editor, result);
|
|
|
195 |
}
|
|
|
196 |
};
|
|
|
197 |
const handleBracket = handleSpacebar;
|
|
|
198 |
const handleEnter = editor => {
|
|
|
199 |
const result = parseCurrentLine(editor, 0);
|
|
|
200 |
if (isNonNullable(result)) {
|
|
|
201 |
convertToLink(editor, result);
|
|
|
202 |
}
|
|
|
203 |
};
|
|
|
204 |
const setup = editor => {
|
|
|
205 |
editor.on('keydown', e => {
|
|
|
206 |
if (e.keyCode === 13 && !e.isDefaultPrevented()) {
|
|
|
207 |
handleEnter(editor);
|
|
|
208 |
}
|
|
|
209 |
});
|
|
|
210 |
editor.on('keyup', e => {
|
|
|
211 |
if (e.keyCode === 32) {
|
|
|
212 |
handleSpacebar(editor);
|
|
|
213 |
} else if (e.keyCode === 48 && e.shiftKey || e.keyCode === 221) {
|
|
|
214 |
handleBracket(editor);
|
|
|
215 |
}
|
|
|
216 |
});
|
|
|
217 |
};
|
|
|
218 |
|
|
|
219 |
var Plugin = () => {
|
|
|
220 |
global$1.add('autolink', editor => {
|
|
|
221 |
register(editor);
|
|
|
222 |
setup(editor);
|
|
|
223 |
});
|
|
|
224 |
};
|
|
|
225 |
|
|
|
226 |
Plugin();
|
|
|
227 |
|
|
|
228 |
})();
|