plugin.js 6.2 KB

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