plugin.js 7.5 KB

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