plugin.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571
  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.2.0 (2020-02-13)
  8. */
  9. (function (domGlobals) {
  10. 'use strict';
  11. var Cell = function (initial) {
  12. var value = initial;
  13. var get = function () {
  14. return value;
  15. };
  16. var set = function (v) {
  17. value = v;
  18. };
  19. var clone = function () {
  20. return Cell(get());
  21. };
  22. return {
  23. get: get,
  24. set: set,
  25. clone: clone
  26. };
  27. };
  28. var global = tinymce.util.Tools.resolve('tinymce.PluginManager');
  29. var get = function (toggleState) {
  30. var isEnabled = function () {
  31. return toggleState.get();
  32. };
  33. return { isEnabled: isEnabled };
  34. };
  35. var Api = { get: get };
  36. var fireVisualChars = function (editor, state) {
  37. return editor.fire('VisualChars', { state: state });
  38. };
  39. var Events = { fireVisualChars: fireVisualChars };
  40. var noop = function () {
  41. };
  42. var constant = function (value) {
  43. return function () {
  44. return value;
  45. };
  46. };
  47. var never = constant(false);
  48. var always = constant(true);
  49. var none = function () {
  50. return NONE;
  51. };
  52. var NONE = function () {
  53. var eq = function (o) {
  54. return o.isNone();
  55. };
  56. var call = function (thunk) {
  57. return thunk();
  58. };
  59. var id = function (n) {
  60. return n;
  61. };
  62. var me = {
  63. fold: function (n, s) {
  64. return n();
  65. },
  66. is: never,
  67. isSome: never,
  68. isNone: always,
  69. getOr: id,
  70. getOrThunk: call,
  71. getOrDie: function (msg) {
  72. throw new Error(msg || 'error: getOrDie called on none.');
  73. },
  74. getOrNull: constant(null),
  75. getOrUndefined: constant(undefined),
  76. or: id,
  77. orThunk: call,
  78. map: none,
  79. each: noop,
  80. bind: none,
  81. exists: never,
  82. forall: always,
  83. filter: none,
  84. equals: eq,
  85. equals_: eq,
  86. toArray: function () {
  87. return [];
  88. },
  89. toString: constant('none()')
  90. };
  91. if (Object.freeze) {
  92. Object.freeze(me);
  93. }
  94. return me;
  95. }();
  96. var some = function (a) {
  97. var constant_a = constant(a);
  98. var self = function () {
  99. return me;
  100. };
  101. var bind = function (f) {
  102. return f(a);
  103. };
  104. var me = {
  105. fold: function (n, s) {
  106. return s(a);
  107. },
  108. is: function (v) {
  109. return a === v;
  110. },
  111. isSome: always,
  112. isNone: never,
  113. getOr: constant_a,
  114. getOrThunk: constant_a,
  115. getOrDie: constant_a,
  116. getOrNull: constant_a,
  117. getOrUndefined: constant_a,
  118. or: self,
  119. orThunk: self,
  120. map: function (f) {
  121. return some(f(a));
  122. },
  123. each: function (f) {
  124. f(a);
  125. },
  126. bind: bind,
  127. exists: bind,
  128. forall: bind,
  129. filter: function (f) {
  130. return f(a) ? me : NONE;
  131. },
  132. toArray: function () {
  133. return [a];
  134. },
  135. toString: function () {
  136. return 'some(' + a + ')';
  137. },
  138. equals: function (o) {
  139. return o.is(a);
  140. },
  141. equals_: function (o, elementEq) {
  142. return o.fold(never, function (b) {
  143. return elementEq(a, b);
  144. });
  145. }
  146. };
  147. return me;
  148. };
  149. var from = function (value) {
  150. return value === null || value === undefined ? NONE : some(value);
  151. };
  152. var Option = {
  153. some: some,
  154. none: none,
  155. from: from
  156. };
  157. var typeOf = function (x) {
  158. if (x === null) {
  159. return 'null';
  160. }
  161. var t = typeof x;
  162. if (t === 'object' && (Array.prototype.isPrototypeOf(x) || x.constructor && x.constructor.name === 'Array')) {
  163. return 'array';
  164. }
  165. if (t === 'object' && (String.prototype.isPrototypeOf(x) || x.constructor && x.constructor.name === 'String')) {
  166. return 'string';
  167. }
  168. return t;
  169. };
  170. var isType = function (type) {
  171. return function (value) {
  172. return typeOf(value) === type;
  173. };
  174. };
  175. var isString = isType('string');
  176. var isBoolean = isType('boolean');
  177. var isFunction = isType('function');
  178. var isNumber = isType('number');
  179. var nativeSlice = Array.prototype.slice;
  180. var map = function (xs, f) {
  181. var len = xs.length;
  182. var r = new Array(len);
  183. for (var i = 0; i < len; i++) {
  184. var x = xs[i];
  185. r[i] = f(x, i);
  186. }
  187. return r;
  188. };
  189. var each = function (xs, f) {
  190. for (var i = 0, len = xs.length; i < len; i++) {
  191. var x = xs[i];
  192. f(x, i);
  193. }
  194. };
  195. var filter = function (xs, pred) {
  196. var r = [];
  197. for (var i = 0, len = xs.length; i < len; i++) {
  198. var x = xs[i];
  199. if (pred(x, i)) {
  200. r.push(x);
  201. }
  202. }
  203. return r;
  204. };
  205. var from$1 = isFunction(Array.from) ? Array.from : function (x) {
  206. return nativeSlice.call(x);
  207. };
  208. var ATTRIBUTE = domGlobals.Node.ATTRIBUTE_NODE;
  209. var CDATA_SECTION = domGlobals.Node.CDATA_SECTION_NODE;
  210. var COMMENT = domGlobals.Node.COMMENT_NODE;
  211. var DOCUMENT = domGlobals.Node.DOCUMENT_NODE;
  212. var DOCUMENT_TYPE = domGlobals.Node.DOCUMENT_TYPE_NODE;
  213. var DOCUMENT_FRAGMENT = domGlobals.Node.DOCUMENT_FRAGMENT_NODE;
  214. var ELEMENT = domGlobals.Node.ELEMENT_NODE;
  215. var TEXT = domGlobals.Node.TEXT_NODE;
  216. var PROCESSING_INSTRUCTION = domGlobals.Node.PROCESSING_INSTRUCTION_NODE;
  217. var ENTITY_REFERENCE = domGlobals.Node.ENTITY_REFERENCE_NODE;
  218. var ENTITY = domGlobals.Node.ENTITY_NODE;
  219. var NOTATION = domGlobals.Node.NOTATION_NODE;
  220. var Global = typeof domGlobals.window !== 'undefined' ? domGlobals.window : Function('return this;')();
  221. var type = function (element) {
  222. return element.dom().nodeType;
  223. };
  224. var value = function (element) {
  225. return element.dom().nodeValue;
  226. };
  227. var isType$1 = function (t) {
  228. return function (element) {
  229. return type(element) === t;
  230. };
  231. };
  232. var isText = isType$1(TEXT);
  233. var rawSet = function (dom, key, value) {
  234. if (isString(value) || isBoolean(value) || isNumber(value)) {
  235. dom.setAttribute(key, value + '');
  236. } else {
  237. domGlobals.console.error('Invalid call to Attr.set. Key ', key, ':: Value ', value, ':: Element ', dom);
  238. throw new Error('Attribute value was not simple');
  239. }
  240. };
  241. var set = function (element, key, value) {
  242. rawSet(element.dom(), key, value);
  243. };
  244. var get$1 = function (element, key) {
  245. var v = element.dom().getAttribute(key);
  246. return v === null ? undefined : v;
  247. };
  248. var remove = function (element, key) {
  249. element.dom().removeAttribute(key);
  250. };
  251. var read = function (element, attr) {
  252. var value = get$1(element, attr);
  253. return value === undefined || value === '' ? [] : value.split(' ');
  254. };
  255. var add = function (element, attr, id) {
  256. var old = read(element, attr);
  257. var nu = old.concat([id]);
  258. set(element, attr, nu.join(' '));
  259. return true;
  260. };
  261. var remove$1 = function (element, attr, id) {
  262. var nu = filter(read(element, attr), function (v) {
  263. return v !== id;
  264. });
  265. if (nu.length > 0) {
  266. set(element, attr, nu.join(' '));
  267. } else {
  268. remove(element, attr);
  269. }
  270. return false;
  271. };
  272. var supports = function (element) {
  273. return element.dom().classList !== undefined;
  274. };
  275. var get$2 = function (element) {
  276. return read(element, 'class');
  277. };
  278. var add$1 = function (element, clazz) {
  279. return add(element, 'class', clazz);
  280. };
  281. var remove$2 = function (element, clazz) {
  282. return remove$1(element, 'class', clazz);
  283. };
  284. var add$2 = function (element, clazz) {
  285. if (supports(element)) {
  286. element.dom().classList.add(clazz);
  287. } else {
  288. add$1(element, clazz);
  289. }
  290. };
  291. var cleanClass = function (element) {
  292. var classList = supports(element) ? element.dom().classList : get$2(element);
  293. if (classList.length === 0) {
  294. remove(element, 'class');
  295. }
  296. };
  297. var remove$3 = function (element, clazz) {
  298. if (supports(element)) {
  299. var classList = element.dom().classList;
  300. classList.remove(clazz);
  301. } else {
  302. remove$2(element, clazz);
  303. }
  304. cleanClass(element);
  305. };
  306. var fromHtml = function (html, scope) {
  307. var doc = scope || domGlobals.document;
  308. var div = doc.createElement('div');
  309. div.innerHTML = html;
  310. if (!div.hasChildNodes() || div.childNodes.length > 1) {
  311. domGlobals.console.error('HTML does not have a single root node', html);
  312. throw new Error('HTML must have a single root node');
  313. }
  314. return fromDom(div.childNodes[0]);
  315. };
  316. var fromTag = function (tag, scope) {
  317. var doc = scope || domGlobals.document;
  318. var node = doc.createElement(tag);
  319. return fromDom(node);
  320. };
  321. var fromText = function (text, scope) {
  322. var doc = scope || domGlobals.document;
  323. var node = doc.createTextNode(text);
  324. return fromDom(node);
  325. };
  326. var fromDom = function (node) {
  327. if (node === null || node === undefined) {
  328. throw new Error('Node cannot be null or undefined');
  329. }
  330. return { dom: constant(node) };
  331. };
  332. var fromPoint = function (docElm, x, y) {
  333. var doc = docElm.dom();
  334. return Option.from(doc.elementFromPoint(x, y)).map(fromDom);
  335. };
  336. var Element = {
  337. fromHtml: fromHtml,
  338. fromTag: fromTag,
  339. fromText: fromText,
  340. fromDom: fromDom,
  341. fromPoint: fromPoint
  342. };
  343. var charMap = {
  344. '\xA0': 'nbsp',
  345. '\xAD': 'shy'
  346. };
  347. var charMapToRegExp = function (charMap, global) {
  348. var key, regExp = '';
  349. for (key in charMap) {
  350. regExp += key;
  351. }
  352. return new RegExp('[' + regExp + ']', global ? 'g' : '');
  353. };
  354. var charMapToSelector = function (charMap) {
  355. var key, selector = '';
  356. for (key in charMap) {
  357. if (selector) {
  358. selector += ',';
  359. }
  360. selector += 'span.mce-' + charMap[key];
  361. }
  362. return selector;
  363. };
  364. var Data = {
  365. charMap: charMap,
  366. regExp: charMapToRegExp(charMap),
  367. regExpGlobal: charMapToRegExp(charMap, true),
  368. selector: charMapToSelector(charMap),
  369. nbspClass: 'mce-nbsp',
  370. charMapToRegExp: charMapToRegExp,
  371. charMapToSelector: charMapToSelector
  372. };
  373. var wrapCharWithSpan = function (value) {
  374. return '<span data-mce-bogus="1" class="mce-' + Data.charMap[value] + '">' + value + '</span>';
  375. };
  376. var Html = { wrapCharWithSpan: wrapCharWithSpan };
  377. var isMatch = function (n) {
  378. var value$1 = value(n);
  379. return isText(n) && value$1 !== undefined && Data.regExp.test(value$1);
  380. };
  381. var filterDescendants = function (scope, predicate) {
  382. var result = [];
  383. var dom = scope.dom();
  384. var children = map(dom.childNodes, Element.fromDom);
  385. each(children, function (x) {
  386. if (predicate(x)) {
  387. result = result.concat([x]);
  388. }
  389. result = result.concat(filterDescendants(x, predicate));
  390. });
  391. return result;
  392. };
  393. var findParentElm = function (elm, rootElm) {
  394. while (elm.parentNode) {
  395. if (elm.parentNode === rootElm) {
  396. return elm;
  397. }
  398. elm = elm.parentNode;
  399. }
  400. };
  401. var replaceWithSpans = function (text) {
  402. return text.replace(Data.regExpGlobal, Html.wrapCharWithSpan);
  403. };
  404. var Nodes = {
  405. isMatch: isMatch,
  406. filterDescendants: filterDescendants,
  407. findParentElm: findParentElm,
  408. replaceWithSpans: replaceWithSpans
  409. };
  410. var isWrappedNbsp = function (node) {
  411. return node.nodeName.toLowerCase() === 'span' && node.classList.contains('mce-nbsp-wrap');
  412. };
  413. var show = function (editor, rootElm) {
  414. var nodeList = Nodes.filterDescendants(Element.fromDom(rootElm), Nodes.isMatch);
  415. each(nodeList, function (n) {
  416. var parent = n.dom().parentNode;
  417. if (isWrappedNbsp(parent)) {
  418. add$2(Element.fromDom(parent), Data.nbspClass);
  419. } else {
  420. var withSpans = Nodes.replaceWithSpans(editor.dom.encode(value(n)));
  421. var div = editor.dom.create('div', null, withSpans);
  422. var node = void 0;
  423. while (node = div.lastChild) {
  424. editor.dom.insertAfter(node, n.dom());
  425. }
  426. editor.dom.remove(n.dom());
  427. }
  428. });
  429. };
  430. var hide = function (editor, rootElm) {
  431. var nodeList = editor.dom.select(Data.selector, rootElm);
  432. each(nodeList, function (node) {
  433. if (isWrappedNbsp(node)) {
  434. remove$3(Element.fromDom(node), Data.nbspClass);
  435. } else {
  436. editor.dom.remove(node, true);
  437. }
  438. });
  439. };
  440. var toggle = function (editor) {
  441. var body = editor.getBody();
  442. var bookmark = editor.selection.getBookmark();
  443. var parentNode = Nodes.findParentElm(editor.selection.getNode(), body);
  444. parentNode = parentNode !== undefined ? parentNode : body;
  445. hide(editor, parentNode);
  446. show(editor, parentNode);
  447. editor.selection.moveToBookmark(bookmark);
  448. };
  449. var VisualChars = {
  450. show: show,
  451. hide: hide,
  452. toggle: toggle
  453. };
  454. var toggleVisualChars = function (editor, toggleState) {
  455. var body = editor.getBody();
  456. var selection = editor.selection;
  457. var bookmark;
  458. toggleState.set(!toggleState.get());
  459. Events.fireVisualChars(editor, toggleState.get());
  460. bookmark = selection.getBookmark();
  461. if (toggleState.get() === true) {
  462. VisualChars.show(editor, body);
  463. } else {
  464. VisualChars.hide(editor, body);
  465. }
  466. selection.moveToBookmark(bookmark);
  467. };
  468. var Actions = { toggleVisualChars: toggleVisualChars };
  469. var register = function (editor, toggleState) {
  470. editor.addCommand('mceVisualChars', function () {
  471. Actions.toggleVisualChars(editor, toggleState);
  472. });
  473. };
  474. var Commands = { register: register };
  475. var global$1 = tinymce.util.Tools.resolve('tinymce.util.Delay');
  476. var setup = function (editor, toggleState) {
  477. var debouncedToggle = global$1.debounce(function () {
  478. VisualChars.toggle(editor);
  479. }, 300);
  480. if (editor.settings.forced_root_block !== false) {
  481. editor.on('keydown', function (e) {
  482. if (toggleState.get() === true) {
  483. e.keyCode === 13 ? VisualChars.toggle(editor) : debouncedToggle();
  484. }
  485. });
  486. }
  487. };
  488. var Keyboard = { setup: setup };
  489. var isEnabledByDefault = function (editor) {
  490. return editor.getParam('visualchars_default_state', false);
  491. };
  492. var Settings = { isEnabledByDefault: isEnabledByDefault };
  493. var setup$1 = function (editor, toggleState) {
  494. editor.on('init', function () {
  495. var valueForToggling = !Settings.isEnabledByDefault(editor);
  496. toggleState.set(valueForToggling);
  497. Actions.toggleVisualChars(editor, toggleState);
  498. });
  499. };
  500. var Bindings = { setup: setup$1 };
  501. var toggleActiveState = function (editor, enabledStated) {
  502. return function (api) {
  503. api.setActive(enabledStated.get());
  504. var editorEventCallback = function (e) {
  505. return api.setActive(e.state);
  506. };
  507. editor.on('VisualChars', editorEventCallback);
  508. return function () {
  509. return editor.off('VisualChars', editorEventCallback);
  510. };
  511. };
  512. };
  513. var register$1 = function (editor, toggleState) {
  514. editor.ui.registry.addToggleButton('visualchars', {
  515. tooltip: 'Show invisible characters',
  516. icon: 'visualchars',
  517. onAction: function () {
  518. return editor.execCommand('mceVisualChars');
  519. },
  520. onSetup: toggleActiveState(editor, toggleState)
  521. });
  522. editor.ui.registry.addToggleMenuItem('visualchars', {
  523. text: 'Show invisible characters',
  524. onAction: function () {
  525. return editor.execCommand('mceVisualChars');
  526. },
  527. onSetup: toggleActiveState(editor, toggleState)
  528. });
  529. };
  530. function Plugin () {
  531. global.add('visualchars', function (editor) {
  532. var toggleState = Cell(false);
  533. Commands.register(editor, toggleState);
  534. register$1(editor, toggleState);
  535. Keyboard.setup(editor, toggleState);
  536. Bindings.setup(editor, toggleState);
  537. return Api.get(toggleState);
  538. });
  539. }
  540. Plugin();
  541. }(window));