(function () { var defs = {}; // id -> {dependencies, definition, instance (possibly undefined)} // Used when there is no 'main' module. // The name is probably (hopefully) unique so minification removes for releases. var register_3795 = function (id) { var module = dem(id); var fragments = id.split('.'); var target = Function('return this;')(); for (var i = 0; i < fragments.length - 1; ++i) { if (target[fragments[i]] === undefined) target[fragments[i]] = {}; target = target[fragments[i]]; } target[fragments[fragments.length - 1]] = module; }; var instantiate = function (id) { var actual = defs[id]; var dependencies = actual.deps; var definition = actual.defn; var len = dependencies.length; var instances = new Array(len); for (var i = 0; i < len; ++i) instances[i] = dem(dependencies[i]); var defResult = definition.apply(null, instances); if (defResult === undefined) throw 'module [' + id + '] returned undefined'; actual.instance = defResult; }; var def = function (id, dependencies, definition) { if (typeof id !== 'string') throw 'module id must be a string'; else if (dependencies === undefined) throw 'no dependencies for ' + id; else if (definition === undefined) throw 'no definition function for ' + id; defs[id] = { deps: dependencies, defn: definition, instance: undefined }; }; var dem = function (id) { var actual = defs[id]; if (actual === undefined) throw 'module [' + id + '] was undefined'; else if (actual.instance === undefined) instantiate(id); return actual.instance; }; var req = function (ids, callback) { var len = ids.length; var instances = new Array(len); for (var i = 0; i < len; ++i) instances.push(dem(ids[i])); callback.apply(null, callback); }; var ephox = {}; ephox.bolt = { module: { api: { define: def, require: req, demand: dem } } }; var define = def; var require = req; var demand = dem; // this helps with minificiation when using a lot of global references var defineGlobal = function (id, ref) { define(id, [], function () { return ref; }); }; /*jsc ["tinymce.plugins.visualchars.Plugin","tinymce.core.PluginManager","tinymce.core.util.Delay","ephox.katamari.api.Arr","ephox.sugar.api.node.Element","tinymce.plugins.visualchars.core.VisualChars","global!tinymce.util.Tools.resolve","ephox.katamari.api.Option","global!Array","global!Error","global!String","ephox.katamari.api.Fun","global!console","global!document","tinymce.plugins.visualchars.core.Data","tinymce.plugins.visualchars.core.Nodes","ephox.sugar.api.node.Node","global!Object","ephox.sugar.api.node.NodeTypes","tinymce.plugins.visualchars.core.Html"] jsc*/ defineGlobal("global!tinymce.util.Tools.resolve", tinymce.util.Tools.resolve); /** * ResolveGlobal.js * * Released under LGPL License. * Copyright (c) 1999-2017 Ephox Corp. All rights reserved * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing */ define( 'tinymce.core.PluginManager', [ 'global!tinymce.util.Tools.resolve' ], function (resolve) { return resolve('tinymce.PluginManager'); } ); /** * ResolveGlobal.js * * Released under LGPL License. * Copyright (c) 1999-2017 Ephox Corp. All rights reserved * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing */ define( 'tinymce.core.util.Delay', [ 'global!tinymce.util.Tools.resolve' ], function (resolve) { return resolve('tinymce.util.Delay'); } ); defineGlobal("global!Array", Array); defineGlobal("global!Error", Error); define( 'ephox.katamari.api.Fun', [ 'global!Array', 'global!Error' ], function (Array, Error) { var noop = function () { }; var compose = function (fa, fb) { return function () { return fa(fb.apply(null, arguments)); }; }; var constant = function (value) { return function () { return value; }; }; var identity = function (x) { return x; }; var tripleEquals = function(a, b) { return a === b; }; // Don't use array slice(arguments), makes the whole function unoptimisable on Chrome var curry = function (f) { // equivalent to arguments.slice(1) // starting at 1 because 0 is the f, makes things tricky. // Pay attention to what variable is where, and the -1 magic. // thankfully, we have tests for this. var args = new Array(arguments.length - 1); for (var i = 1; i < arguments.length; i++) args[i-1] = arguments[i]; return function () { var newArgs = new Array(arguments.length); for (var j = 0; j < newArgs.length; j++) newArgs[j] = arguments[j]; var all = args.concat(newArgs); return f.apply(null, all); }; }; var not = function (f) { return function () { return !f.apply(null, arguments); }; }; var die = function (msg) { return function () { throw new Error(msg); }; }; var apply = function (f) { return f(); }; var call = function(f) { f(); }; var never = constant(false); var always = constant(true); return { noop: noop, compose: compose, constant: constant, identity: identity, tripleEquals: tripleEquals, curry: curry, not: not, die: die, apply: apply, call: call, never: never, always: always }; } ); defineGlobal("global!Object", Object); define( 'ephox.katamari.api.Option', [ 'ephox.katamari.api.Fun', 'global!Object' ], function (Fun, Object) { var never = Fun.never; var always = Fun.always; /** Option objects support the following methods: fold :: this Option a -> ((() -> b, a -> b)) -> Option b is :: this Option a -> a -> Boolean isSome :: this Option a -> () -> Boolean isNone :: this Option a -> () -> Boolean getOr :: this Option a -> a -> a getOrThunk :: this Option a -> (() -> a) -> a getOrDie :: this Option a -> String -> a or :: this Option a -> Option a -> Option a - if some: return self - if none: return opt orThunk :: this Option a -> (() -> Option a) -> Option a - Same as "or", but uses a thunk instead of a value map :: this Option a -> (a -> b) -> Option b - "fmap" operation on the Option Functor. - same as 'each' ap :: this Option a -> Option (a -> b) -> Option b - "apply" operation on the Option Apply/Applicative. - Equivalent to <*> in Haskell/PureScript. each :: this Option a -> (a -> b) -> Option b - same as 'map' bind :: this Option a -> (a -> Option b) -> Option b - "bind"/"flatMap" operation on the Option Bind/Monad. - Equivalent to >>= in Haskell/PureScript; flatMap in Scala. flatten :: {this Option (Option a))} -> () -> Option a - "flatten"/"join" operation on the Option Monad. exists :: this Option a -> (a -> Boolean) -> Boolean forall :: this Option a -> (a -> Boolean) -> Boolean filter :: this Option a -> (a -> Boolean) -> Option a equals :: this Option a -> Option a -> Boolean equals_ :: this Option a -> (Option a, a -> Boolean) -> Boolean toArray :: this Option a -> () -> [a] */ var none = function () { return NONE; }; var NONE = (function () { var eq = function (o) { return o.isNone(); }; // inlined from peanut, maybe a micro-optimisation? var call = function (thunk) { return thunk(); }; var id = function (n) { return n; }; var noop = function () { }; var me = { fold: function (n, s) { return n(); }, is: never, isSome: never, isNone: always, getOr: id, getOrThunk: call, getOrDie: function (msg) { throw new Error(msg || 'error: getOrDie called on none.'); }, or: id, orThunk: call, map: none, ap: none, each: noop, bind: none, flatten: none, exists: never, forall: always, filter: none, equals: eq, equals_: eq, toArray: function () { return []; }, toString: Fun.constant("none()") }; if (Object.freeze) Object.freeze(me); return me; })(); /** some :: a -> Option a */ var some = function (a) { // inlined from peanut, maybe a micro-optimisation? var constant_a = function () { return a; }; var self = function () { // can't Fun.constant this one return me; }; var map = function (f) { return some(f(a)); }; var bind = function (f) { return f(a); }; var me = { fold: function (n, s) { return s(a); }, is: function (v) { return a === v; }, isSome: always, isNone: never, getOr: constant_a, getOrThunk: constant_a, getOrDie: constant_a, or: self, orThunk: self, map: map, ap: function (optfab) { return optfab.fold(none, function(fab) { return some(fab(a)); }); }, each: function (f) { f(a); }, bind: bind, flatten: constant_a, exists: bind, forall: bind, filter: function (f) { return f(a) ? me : NONE; }, equals: function (o) { return o.is(a); }, equals_: function (o, elementEq) { return o.fold( never, function (b) { return elementEq(a, b); } ); }, toArray: function () { return [a]; }, toString: function () { return 'some(' + a + ')'; } }; return me; }; /** from :: undefined|null|a -> Option a */ var from = function (value) { return value === null || value === undefined ? NONE : some(value); }; return { some: some, none: none, from: from }; } ); defineGlobal("global!String", String); define( 'ephox.katamari.api.Arr', [ 'ephox.katamari.api.Option', 'global!Array', 'global!Error', 'global!String' ], function (Option, Array, Error, String) { // Use the native Array.indexOf if it is available (IE9+) otherwise fall back to manual iteration // https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf var rawIndexOf = (function () { var pIndexOf = Array.prototype.indexOf; var fastIndex = function (xs, x) { return pIndexOf.call(xs, x); }; var slowIndex = function(xs, x) { return slowIndexOf(xs, x); }; return pIndexOf === undefined ? slowIndex : fastIndex; })(); var indexOf = function (xs, x) { // The rawIndexOf method does not wrap up in an option. This is for performance reasons. var r = rawIndexOf(xs, x); return r === -1 ? Option.none() : Option.some(r); }; var contains = function (xs, x) { return rawIndexOf(xs, x) > -1; }; // Using findIndex is likely less optimal in Chrome (dynamic return type instead of bool) // but if we need that micro-optimisation we can inline it later. var exists = function (xs, pred) { return findIndex(xs, pred).isSome(); }; var range = function (num, f) { var r = []; for (var i = 0; i < num; i++) { r.push(f(i)); } return r; }; // It's a total micro optimisation, but these do make some difference. // Particularly for browsers other than Chrome. // - length caching // http://jsperf.com/browser-diet-jquery-each-vs-for-loop/69 // - not using push // http://jsperf.com/array-direct-assignment-vs-push/2 var chunk = function (array, size) { var r = []; for (var i = 0; i < array.length; i += size) { var s = array.slice(i, i + size); r.push(s); } return r; }; var map = function(xs, f) { // pre-allocating array size when it's guaranteed to be known // http://jsperf.com/push-allocated-vs-dynamic/22 var len = xs.length; var r = new Array(len); for (var i = 0; i < len; i++) { var x = xs[i]; r[i] = f(x, i, xs); } return r; }; // Unwound implementing other functions in terms of each. // The code size is roughly the same, and it should allow for better optimisation. var each = function(xs, f) { for (var i = 0, len = xs.length; i < len; i++) { var x = xs[i]; f(x, i, xs); } }; var eachr = function (xs, f) { for (var i = xs.length - 1; i >= 0; i--) { var x = xs[i]; f(x, i, xs); } }; var partition = function(xs, pred) { var pass = []; var fail = []; for (var i = 0, len = xs.length; i < len; i++) { var x = xs[i]; var arr = pred(x, i, xs) ? pass : fail; arr.push(x); } return { pass: pass, fail: fail }; }; var filter = function(xs, pred) { var r = []; for (var i = 0, len = xs.length; i < len; i++) { var x = xs[i]; if (pred(x, i, xs)) { r.push(x); } } return r; }; /* * Groups an array into contiguous arrays of like elements. Whether an element is like or not depends on f. * * f is a function that derives a value from an element - e.g. true or false, or a string. * Elements are like if this function generates the same value for them (according to ===). * * * Order of the elements is preserved. Arr.flatten() on the result will return the original list, as with Haskell groupBy function. * For a good explanation, see the group function (which is a special case of groupBy) * http://hackage.haskell.org/package/base-4.7.0.0/docs/Data-List.html#v:group */ var groupBy = function (xs, f) { if (xs.length === 0) { return []; } else { var wasType = f(xs[0]); // initial case for matching var r = []; var group = []; for (var i = 0, len = xs.length; i < len; i++) { var x = xs[i]; var type = f(x); if (type !== wasType) { r.push(group); group = []; } wasType = type; group.push(x); } if (group.length !== 0) { r.push(group); } return r; } }; var foldr = function (xs, f, acc) { eachr(xs, function (x) { acc = f(acc, x); }); return acc; }; var foldl = function (xs, f, acc) { each(xs, function (x) { acc = f(acc, x); }); return acc; }; var find = function (xs, pred) { for (var i = 0, len = xs.length; i < len; i++) { var x = xs[i]; if (pred(x, i, xs)) { return Option.some(x); } } return Option.none(); }; var findIndex = function (xs, pred) { for (var i = 0, len = xs.length; i < len; i++) { var x = xs[i]; if (pred(x, i, xs)) { return Option.some(i); } } return Option.none(); }; var slowIndexOf = function (xs, x) { for (var i = 0, len = xs.length; i < len; ++i) { if (xs[i] === x) { return i; } } return -1; }; var push = Array.prototype.push; var flatten = function (xs) { // Note, this is possible because push supports multiple arguments: // http://jsperf.com/concat-push/6 // Note that in the past, concat() would silently work (very slowly) for array-like objects. // With this change it will throw an error. var r = []; for (var i = 0, len = xs.length; i < len; ++i) { // Ensure that each value is an array itself if (! Array.prototype.isPrototypeOf(xs[i])) throw new Error('Arr.flatten item ' + i + ' was not an array, input: ' + xs); push.apply(r, xs[i]); } return r; }; var bind = function (xs, f) { var output = map(xs, f); return flatten(output); }; var forall = function (xs, pred) { for (var i = 0, len = xs.length; i < len; ++i) { var x = xs[i]; if (pred(x, i, xs) !== true) { return false; } } return true; }; var equal = function (a1, a2) { return a1.length === a2.length && forall(a1, function (x, i) { return x === a2[i]; }); }; var slice = Array.prototype.slice; var reverse = function (xs) { var r = slice.call(xs, 0); r.reverse(); return r; }; var difference = function (a1, a2) { return filter(a1, function (x) { return !contains(a2, x); }); }; var mapToObject = function(xs, f) { var r = {}; for (var i = 0, len = xs.length; i < len; i++) { var x = xs[i]; r[String(x)] = f(x, i); } return r; }; var pure = function(x) { return [x]; }; var sort = function (xs, comparator) { var copy = slice.call(xs, 0); copy.sort(comparator); return copy; }; return { map: map, each: each, eachr: eachr, partition: partition, filter: filter, groupBy: groupBy, indexOf: indexOf, foldr: foldr, foldl: foldl, find: find, findIndex: findIndex, flatten: flatten, bind: bind, forall: forall, exists: exists, contains: contains, equal: equal, reverse: reverse, chunk: chunk, difference: difference, mapToObject: mapToObject, pure: pure, sort: sort, range: range }; } ); define("global!console", [], function () { if (typeof console === "undefined") console = { log: function () {} }; return console; }); defineGlobal("global!document", document); define( 'ephox.sugar.api.node.Element', [ 'ephox.katamari.api.Fun', 'global!Error', 'global!console', 'global!document' ], function (Fun, Error, console, document) { var fromHtml = function (html, scope) { var doc = scope || document; var div = doc.createElement('div'); div.innerHTML = html; if (!div.hasChildNodes() || div.childNodes.length > 1) { console.error('HTML does not have a single root node', html); throw 'HTML must have a single root node'; } return fromDom(div.childNodes[0]); }; var fromTag = function (tag, scope) { var doc = scope || document; var node = doc.createElement(tag); return fromDom(node); }; var fromText = function (text, scope) { var doc = scope || document; var node = doc.createTextNode(text); return fromDom(node); }; var fromDom = function (node) { if (node === null || node === undefined) throw new Error('Node cannot be null or undefined'); return { dom: Fun.constant(node) }; }; return { fromHtml: fromHtml, fromTag: fromTag, fromText: fromText, fromDom: fromDom }; } ); /** * Plugin.js * * Released under LGPL License. * Copyright (c) 1999-2017 Ephox Corp. All rights reserved * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing */ define( 'tinymce.plugins.visualchars.core.Data', [ ], function () { var charMap = { '\u00a0': 'nbsp', '\u00ad': 'shy' }; var charMapToRegExp = function (charMap, global) { var key, regExp = ''; for (key in charMap) { regExp += key; } return new RegExp('[' + regExp + ']', global ? 'g' : ''); }; var charMapToSelector = function (charMap) { var key, selector = ''; for (key in charMap) { if (selector) { selector += ','; } selector += 'span.mce-' + charMap[key]; } return selector; }; return { charMap: charMap, regExp: charMapToRegExp(charMap), regExpGlobal: charMapToRegExp(charMap, true), selector: charMapToSelector(charMap), charMapToRegExp: charMapToRegExp, charMapToSelector: charMapToSelector }; } ); define( 'ephox.sugar.api.node.NodeTypes', [ ], function () { return { ATTRIBUTE: 2, CDATA_SECTION: 4, COMMENT: 8, DOCUMENT: 9, DOCUMENT_TYPE: 10, DOCUMENT_FRAGMENT: 11, ELEMENT: 1, TEXT: 3, PROCESSING_INSTRUCTION: 7, ENTITY_REFERENCE: 5, ENTITY: 6, NOTATION: 12 }; } ); define( 'ephox.sugar.api.node.Node', [ 'ephox.sugar.api.node.NodeTypes' ], function (NodeTypes) { var name = function (element) { var r = element.dom().nodeName; return r.toLowerCase(); }; var type = function (element) { return element.dom().nodeType; }; var value = function (element) { return element.dom().nodeValue; }; var isType = function (t) { return function (element) { return type(element) === t; }; }; var isComment = function (element) { return type(element) === NodeTypes.COMMENT || name(element) === '#comment'; }; var isElement = isType(NodeTypes.ELEMENT); var isText = isType(NodeTypes.TEXT); var isDocument = isType(NodeTypes.DOCUMENT); return { name: name, type: type, value: value, isElement: isElement, isText: isText, isDocument: isDocument, isComment: isComment }; } ); define( 'tinymce.plugins.visualchars.core.Html', [ 'tinymce.plugins.visualchars.core.Data' ], function (Data) { var wrapCharWithSpan = function (value) { return '' + value + ''; }; return { wrapCharWithSpan: wrapCharWithSpan }; } ); /** * Plugin.js * * Released under LGPL License. * Copyright (c) 1999-2017 Ephox Corp. All rights reserved * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing */ define( 'tinymce.plugins.visualchars.core.Nodes', [ 'ephox.katamari.api.Arr', 'ephox.sugar.api.node.Element', 'ephox.sugar.api.node.Node', 'tinymce.plugins.visualchars.core.Data', 'tinymce.plugins.visualchars.core.Html' ], function (Arr, Element, Node, Data, Html) { var isMatch = function (n) { return Node.isText(n) && Node.value(n) !== undefined && Data.regExp.test(Node.value(n)); }; // inlined sugars PredicateFilter.descendants for file size var filterDescendants = function (scope, predicate) { var result = []; var dom = scope.dom(); var children = Arr.map(dom.childNodes, Element.fromDom); Arr.each(children, function (x) { if (predicate(x)) { result = result.concat([ x ]); } result = result.concat(filterDescendants(x, predicate)); }); return result; }; var findParentElm = function (elm, rootElm) { while (elm.parentNode) { if (elm.parentNode === rootElm) { return elm; } elm = elm.parentNode; } }; var replaceWithSpans = function (html) { return html.replace(Data.regExpGlobal, Html.wrapCharWithSpan); }; return { isMatch: isMatch, filterDescendants: filterDescendants, findParentElm: findParentElm, replaceWithSpans: replaceWithSpans }; } ); /** * Plugin.js * * Released under LGPL License. * Copyright (c) 1999-2017 Ephox Corp. All rights reserved * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing */ define( 'tinymce.plugins.visualchars.core.VisualChars', [ 'tinymce.plugins.visualchars.core.Data', 'tinymce.plugins.visualchars.core.Nodes', 'ephox.katamari.api.Arr', 'ephox.sugar.api.node.Element', 'ephox.sugar.api.node.Node' ], function (Data, Nodes, Arr, Element, Node) { var show = function (editor, rootElm) { var node, div; var nodeList = Nodes.filterDescendants(Element.fromDom(rootElm), Nodes.isMatch); Arr.each(nodeList, function (n) { var withSpans = Nodes.replaceWithSpans(Node.value(n)); div = editor.dom.create('div', null, withSpans); while ((node = div.lastChild)) { editor.dom.insertAfter(node, n.dom()); } editor.dom.remove(n.dom()); }); }; var hide = function (editor, body) { var nodeList = editor.dom.select(Data.selector, body); Arr.each(nodeList, function (node) { editor.dom.remove(node, 1); }); }; var toggle = function (editor) { var body = editor.getBody(); var bookmark = editor.selection.getBookmark(); var parentNode = Nodes.findParentElm(editor.selection.getNode(), body); // if user does select all the parentNode will be undefined parentNode = parentNode !== undefined ? parentNode : body; hide(editor, parentNode); show(editor, parentNode); editor.selection.moveToBookmark(bookmark); }; return { show: show, hide: hide, toggle: toggle }; } ); /** * Plugin.js * * Released under LGPL License. * Copyright (c) 1999-2017 Ephox Corp. All rights reserved * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing */ define( 'tinymce.plugins.visualchars.Plugin', [ 'tinymce.core.PluginManager', 'tinymce.core.util.Delay', 'ephox.katamari.api.Arr', 'ephox.sugar.api.node.Element', 'tinymce.plugins.visualchars.core.VisualChars' ], function (PluginManager, Delay, Arr, Element, VisualChars) { PluginManager.add('visualchars', function (editor) { var self = this, state; var toggleActiveState = function () { var self = this; editor.on('VisualChars', function (e) { self.active(e.state); }); }; var debouncedToggle = Delay.debounce(function () { VisualChars.toggle(editor); }, 300); if (editor.settings.forced_root_block !== false) { editor.on('keydown', function (e) { if (self.state === true) { e.keyCode === 13 ? VisualChars.toggle(editor) : debouncedToggle(); } }); } editor.addCommand('mceVisualChars', function () { var body = editor.getBody(), selection = editor.selection, bookmark; state = !state; self.state = state; editor.fire('VisualChars', { state: state }); bookmark = selection.getBookmark(); if (state === true) { VisualChars.show(editor, body); } else { VisualChars.hide(editor, body); } selection.moveToBookmark(bookmark); }); editor.addButton('visualchars', { title: 'Show invisible characters', cmd: 'mceVisualChars', onPostRender: toggleActiveState }); editor.addMenuItem('visualchars', { text: 'Show invisible characters', cmd: 'mceVisualChars', onPostRender: toggleActiveState, selectable: true, context: 'view', prependToContext: true }); }); return function () {}; } ); dem('tinymce.plugins.visualchars.Plugin')(); })();