plugin.js 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. /**
  2. * TinyMCE version 6.4.2 (2023-04-26)
  3. */
  4. (function () {
  5. 'use strict';
  6. var global$2 = tinymce.util.Tools.resolve('tinymce.PluginManager');
  7. var global$1 = tinymce.util.Tools.resolve('tinymce.dom.RangeUtils');
  8. var global = tinymce.util.Tools.resolve('tinymce.util.Tools');
  9. const option = name => editor => editor.options.get(name);
  10. const register$2 = editor => {
  11. const registerOption = editor.options.register;
  12. registerOption('allow_html_in_named_anchor', {
  13. processor: 'boolean',
  14. default: false
  15. });
  16. };
  17. const allowHtmlInNamedAnchor = option('allow_html_in_named_anchor');
  18. const namedAnchorSelector = 'a:not([href])';
  19. const isEmptyString = str => !str;
  20. const getIdFromAnchor = elm => {
  21. const id = elm.getAttribute('id') || elm.getAttribute('name');
  22. return id || '';
  23. };
  24. const isAnchor = elm => elm.nodeName.toLowerCase() === 'a';
  25. const isNamedAnchor = elm => isAnchor(elm) && !elm.getAttribute('href') && getIdFromAnchor(elm) !== '';
  26. const isEmptyNamedAnchor = elm => isNamedAnchor(elm) && !elm.firstChild;
  27. const removeEmptyNamedAnchorsInSelection = editor => {
  28. const dom = editor.dom;
  29. global$1(dom).walk(editor.selection.getRng(), nodes => {
  30. global.each(nodes, node => {
  31. if (isEmptyNamedAnchor(node)) {
  32. dom.remove(node, false);
  33. }
  34. });
  35. });
  36. };
  37. const isValidId = id => /^[A-Za-z][A-Za-z0-9\-:._]*$/.test(id);
  38. const getNamedAnchor = editor => editor.dom.getParent(editor.selection.getStart(), namedAnchorSelector);
  39. const getId = editor => {
  40. const anchor = getNamedAnchor(editor);
  41. if (anchor) {
  42. return getIdFromAnchor(anchor);
  43. } else {
  44. return '';
  45. }
  46. };
  47. const createAnchor = (editor, id) => {
  48. editor.undoManager.transact(() => {
  49. if (!allowHtmlInNamedAnchor(editor)) {
  50. editor.selection.collapse(true);
  51. }
  52. if (editor.selection.isCollapsed()) {
  53. editor.insertContent(editor.dom.createHTML('a', { id }));
  54. } else {
  55. removeEmptyNamedAnchorsInSelection(editor);
  56. editor.formatter.remove('namedAnchor', undefined, undefined, true);
  57. editor.formatter.apply('namedAnchor', { value: id });
  58. editor.addVisual();
  59. }
  60. });
  61. };
  62. const updateAnchor = (editor, id, anchorElement) => {
  63. anchorElement.removeAttribute('name');
  64. anchorElement.id = id;
  65. editor.addVisual();
  66. editor.undoManager.add();
  67. };
  68. const insert = (editor, id) => {
  69. const anchor = getNamedAnchor(editor);
  70. if (anchor) {
  71. updateAnchor(editor, id, anchor);
  72. } else {
  73. createAnchor(editor, id);
  74. }
  75. editor.focus();
  76. };
  77. const insertAnchor = (editor, newId) => {
  78. if (!isValidId(newId)) {
  79. editor.windowManager.alert('ID should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores.');
  80. return false;
  81. } else {
  82. insert(editor, newId);
  83. return true;
  84. }
  85. };
  86. const open = editor => {
  87. const currentId = getId(editor);
  88. editor.windowManager.open({
  89. title: 'Anchor',
  90. size: 'normal',
  91. body: {
  92. type: 'panel',
  93. items: [{
  94. name: 'id',
  95. type: 'input',
  96. label: 'ID',
  97. placeholder: 'example'
  98. }]
  99. },
  100. buttons: [
  101. {
  102. type: 'cancel',
  103. name: 'cancel',
  104. text: 'Cancel'
  105. },
  106. {
  107. type: 'submit',
  108. name: 'save',
  109. text: 'Save',
  110. primary: true
  111. }
  112. ],
  113. initialData: { id: currentId },
  114. onSubmit: api => {
  115. if (insertAnchor(editor, api.getData().id)) {
  116. api.close();
  117. }
  118. }
  119. });
  120. };
  121. const register$1 = editor => {
  122. editor.addCommand('mceAnchor', () => {
  123. open(editor);
  124. });
  125. };
  126. const isNamedAnchorNode = node => isEmptyString(node.attr('href')) && !isEmptyString(node.attr('id') || node.attr('name'));
  127. const isEmptyNamedAnchorNode = node => isNamedAnchorNode(node) && !node.firstChild;
  128. const setContentEditable = state => nodes => {
  129. for (let i = 0; i < nodes.length; i++) {
  130. const node = nodes[i];
  131. if (isEmptyNamedAnchorNode(node)) {
  132. node.attr('contenteditable', state);
  133. }
  134. }
  135. };
  136. const setup = editor => {
  137. editor.on('PreInit', () => {
  138. editor.parser.addNodeFilter('a', setContentEditable('false'));
  139. editor.serializer.addNodeFilter('a', setContentEditable(null));
  140. });
  141. };
  142. const registerFormats = editor => {
  143. editor.formatter.register('namedAnchor', {
  144. inline: 'a',
  145. selector: namedAnchorSelector,
  146. remove: 'all',
  147. split: true,
  148. deep: true,
  149. attributes: { id: '%value' },
  150. onmatch: (node, _fmt, _itemName) => {
  151. return isNamedAnchor(node);
  152. }
  153. });
  154. };
  155. const register = editor => {
  156. const onAction = () => editor.execCommand('mceAnchor');
  157. editor.ui.registry.addToggleButton('anchor', {
  158. icon: 'bookmark',
  159. tooltip: 'Anchor',
  160. onAction,
  161. onSetup: buttonApi => editor.selection.selectorChangedWithUnbind('a:not([href])', buttonApi.setActive).unbind
  162. });
  163. editor.ui.registry.addMenuItem('anchor', {
  164. icon: 'bookmark',
  165. text: 'Anchor...',
  166. onAction
  167. });
  168. };
  169. var Plugin = () => {
  170. global$2.add('anchor', editor => {
  171. register$2(editor);
  172. setup(editor);
  173. register$1(editor);
  174. register(editor);
  175. editor.on('PreInit', () => {
  176. registerFormats(editor);
  177. });
  178. });
  179. };
  180. Plugin();
  181. })();