liquid_feedback_frontend
diff static/wysihtml/wysihtml.toolbar.js @ 1309:32cc544d5a5b
Cumulative patch for upcoming frontend version 4
author | bsw/jbe |
---|---|
date | Sun Jul 15 14:07:29 2018 +0200 (2018-07-15) |
parents | |
children |
line diff
1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/static/wysihtml/wysihtml.toolbar.js Sun Jul 15 14:07:29 2018 +0200 1.3 @@ -0,0 +1,850 @@ 1.4 +/** 1.5 + * Toolbar Dialog 1.6 + * 1.7 + * @param {Element} link The toolbar link which causes the dialog to show up 1.8 + * @param {Element} container The dialog container 1.9 + * 1.10 + * @example 1.11 + * <!-- Toolbar link --> 1.12 + * <a data-wysihtml-command="insertImage">insert an image</a> 1.13 + * 1.14 + * <!-- Dialog --> 1.15 + * <div data-wysihtml-dialog="insertImage" style="display: none;"> 1.16 + * <label> 1.17 + * URL: <input data-wysihtml-dialog-field="src" value="http://"> 1.18 + * </label> 1.19 + * <label> 1.20 + * Alternative text: <input data-wysihtml-dialog-field="alt" value=""> 1.21 + * </label> 1.22 + * </div> 1.23 + * 1.24 + * <script> 1.25 + * var dialog = new wysihtml.toolbar.Dialog( 1.26 + * document.querySelector("[data-wysihtml-command='insertImage']"), 1.27 + * document.querySelector("[data-wysihtml-dialog='insertImage']") 1.28 + * ); 1.29 + * dialog.observe("save", function(attributes) { 1.30 + * // do something 1.31 + * }); 1.32 + * </script> 1.33 + */ 1.34 +(function(wysihtml) { 1.35 + var dom = wysihtml.dom, 1.36 + CLASS_NAME_OPENED = "wysihtml-command-dialog-opened", 1.37 + SELECTOR_FORM_ELEMENTS = "input, select, textarea", 1.38 + SELECTOR_FIELDS = "[data-wysihtml-dialog-field]", 1.39 + ATTRIBUTE_FIELDS = "data-wysihtml-dialog-field"; 1.40 + 1.41 + 1.42 + wysihtml.toolbar.Dialog = wysihtml.lang.Dispatcher.extend( 1.43 + /** @scope wysihtml.toolbar.Dialog.prototype */ { 1.44 + constructor: function(link, container) { 1.45 + this.link = link; 1.46 + this.container = container; 1.47 + }, 1.48 + 1.49 + _observe: function() { 1.50 + if (this._observed) { 1.51 + return; 1.52 + } 1.53 + 1.54 + var that = this, 1.55 + callbackWrapper = function(event) { 1.56 + var attributes = that._serialize(); 1.57 + that.fire("save", attributes); 1.58 + that.hide(); 1.59 + event.preventDefault(); 1.60 + event.stopPropagation(); 1.61 + }; 1.62 + 1.63 + dom.observe(that.link, "click", function() { 1.64 + if (dom.hasClass(that.link, CLASS_NAME_OPENED)) { 1.65 + setTimeout(function() { that.hide(); }, 0); 1.66 + } 1.67 + }); 1.68 + 1.69 + dom.observe(this.container, "keydown", function(event) { 1.70 + var keyCode = event.keyCode; 1.71 + if (keyCode === wysihtml.ENTER_KEY) { 1.72 + callbackWrapper(event); 1.73 + } 1.74 + if (keyCode === wysihtml.ESCAPE_KEY) { 1.75 + that.cancel(); 1.76 + } 1.77 + }); 1.78 + 1.79 + dom.delegate(this.container, "[data-wysihtml-dialog-action=save]", "click", callbackWrapper); 1.80 + 1.81 + dom.delegate(this.container, "[data-wysihtml-dialog-action=cancel]", "click", function(event) { 1.82 + that.cancel(); 1.83 + event.preventDefault(); 1.84 + event.stopPropagation(); 1.85 + }); 1.86 + 1.87 + this._observed = true; 1.88 + }, 1.89 + 1.90 + /** 1.91 + * Grabs all fields in the dialog and puts them in key=>value style in an object which 1.92 + * then gets returned 1.93 + */ 1.94 + _serialize: function() { 1.95 + var data = {}, 1.96 + fields = this.container.querySelectorAll(SELECTOR_FIELDS), 1.97 + length = fields.length, 1.98 + i = 0; 1.99 + 1.100 + for (; i<length; i++) { 1.101 + data[fields[i].getAttribute(ATTRIBUTE_FIELDS)] = fields[i].value; 1.102 + } 1.103 + return data; 1.104 + }, 1.105 + 1.106 + /** 1.107 + * Takes the attributes of the "elementToChange" 1.108 + * and inserts them in their corresponding dialog input fields 1.109 + * 1.110 + * Assume the "elementToChange" looks like this: 1.111 + * <a href="http://www.google.com" target="_blank">foo</a> 1.112 + * 1.113 + * and we have the following dialog: 1.114 + * <input type="text" data-wysihtml-dialog-field="href" value=""> 1.115 + * <input type="text" data-wysihtml-dialog-field="target" value=""> 1.116 + * 1.117 + * after calling _interpolate() the dialog will look like this 1.118 + * <input type="text" data-wysihtml-dialog-field="href" value="http://www.google.com"> 1.119 + * <input type="text" data-wysihtml-dialog-field="target" value="_blank"> 1.120 + * 1.121 + * Basically it adopted the attribute values into the corresponding input fields 1.122 + * 1.123 + */ 1.124 + _interpolate: function(avoidHiddenFields) { 1.125 + var field, 1.126 + fieldName, 1.127 + newValue, 1.128 + focusedElement = document.querySelector(":focus"), 1.129 + fields = this.container.querySelectorAll(SELECTOR_FIELDS), 1.130 + length = fields.length, 1.131 + i = 0; 1.132 + for (; i<length; i++) { 1.133 + field = fields[i]; 1.134 + 1.135 + // Never change elements where the user is currently typing in 1.136 + if (field === focusedElement) { 1.137 + continue; 1.138 + } 1.139 + 1.140 + // Don't update hidden fields 1.141 + // See https://github.com/xing/wysihtml5/pull/14 1.142 + if (avoidHiddenFields && field.type === "hidden") { 1.143 + continue; 1.144 + } 1.145 + 1.146 + fieldName = field.getAttribute(ATTRIBUTE_FIELDS); 1.147 + newValue = (this.elementToChange && typeof(this.elementToChange) !== 'boolean') ? (this.elementToChange.getAttribute(fieldName) || "") : field.defaultValue; 1.148 + field.value = newValue; 1.149 + } 1.150 + }, 1.151 + 1.152 + update: function (elementToChange) { 1.153 + this.elementToChange = elementToChange ? elementToChange : this.elementToChange; 1.154 + this._interpolate(); 1.155 + }, 1.156 + 1.157 + /** 1.158 + * Show the dialog element 1.159 + */ 1.160 + show: function(elementToChange) { 1.161 + var firstField = this.container.querySelector(SELECTOR_FORM_ELEMENTS); 1.162 + 1.163 + this._observe(); 1.164 + this.update(elementToChange); 1.165 + 1.166 + dom.addClass(this.link, CLASS_NAME_OPENED); 1.167 + this.container.style.display = ""; 1.168 + this.isOpen = true; 1.169 + this.fire("show"); 1.170 + 1.171 + if (firstField && !elementToChange) { 1.172 + try { 1.173 + firstField.focus(); 1.174 + } catch(e) {} 1.175 + } 1.176 + }, 1.177 + 1.178 + /** 1.179 + * Hide the dialog element 1.180 + */ 1.181 + _hide: function(focus) { 1.182 + this.elementToChange = null; 1.183 + dom.removeClass(this.link, CLASS_NAME_OPENED); 1.184 + this.container.style.display = "none"; 1.185 + this.isOpen = false; 1.186 + }, 1.187 + 1.188 + hide: function() { 1.189 + this._hide(); 1.190 + this.fire("hide"); 1.191 + }, 1.192 + 1.193 + cancel: function() { 1.194 + this._hide(); 1.195 + this.fire("cancel"); 1.196 + } 1.197 + }); 1.198 +})(wysihtml); //jshint ignore:line 1.199 + 1.200 +(function(wysihtml) { 1.201 + var dom = wysihtml.dom, 1.202 + SELECTOR_FIELDS = "[data-wysihtml-dialog-field]", 1.203 + ATTRIBUTE_FIELDS = "data-wysihtml-dialog-field"; 1.204 + 1.205 + wysihtml.toolbar.Dialog_bgColorStyle = wysihtml.toolbar.Dialog.extend({ 1.206 + multiselect: true, 1.207 + 1.208 + _serialize: function() { 1.209 + var data = {}, 1.210 + fields = this.container.querySelectorAll(SELECTOR_FIELDS), 1.211 + length = fields.length, 1.212 + i = 0; 1.213 + 1.214 + for (; i<length; i++) { 1.215 + data[fields[i].getAttribute(ATTRIBUTE_FIELDS)] = fields[i].value; 1.216 + } 1.217 + return data; 1.218 + }, 1.219 + 1.220 + _interpolate: function(avoidHiddenFields) { 1.221 + var field, 1.222 + fieldName, 1.223 + newValue, 1.224 + focusedElement = document.querySelector(":focus"), 1.225 + fields = this.container.querySelectorAll(SELECTOR_FIELDS), 1.226 + length = fields.length, 1.227 + i = 0, 1.228 + firstElement = (this.elementToChange) ? ((wysihtml.lang.object(this.elementToChange).isArray()) ? this.elementToChange[0] : this.elementToChange) : null, 1.229 + colorStr = (firstElement) ? firstElement.getAttribute('style') : null, 1.230 + color = (colorStr) ? wysihtml.quirks.styleParser.parseColor(colorStr, "background-color") : null; 1.231 + 1.232 + for (; i<length; i++) { 1.233 + field = fields[i]; 1.234 + // Never change elements where the user is currently typing in 1.235 + if (field === focusedElement) { 1.236 + continue; 1.237 + } 1.238 + // Don't update hidden fields3 1.239 + if (avoidHiddenFields && field.type === "hidden") { 1.240 + continue; 1.241 + } 1.242 + if (field.getAttribute(ATTRIBUTE_FIELDS) === "color") { 1.243 + if (color) { 1.244 + if (color[3] && color[3] != 1) { 1.245 + field.value = "rgba(" + color[0] + "," + color[1] + "," + color[2] + "," + color[3] + ");"; 1.246 + } else { 1.247 + field.value = "rgb(" + color[0] + "," + color[1] + "," + color[2] + ");"; 1.248 + } 1.249 + } else { 1.250 + field.value = "rgb(0,0,0);"; 1.251 + } 1.252 + } 1.253 + } 1.254 + } 1.255 + 1.256 + }); 1.257 +})(wysihtml); 1.258 + 1.259 +(function(wysihtml) { 1.260 + wysihtml.toolbar.Dialog_createTable = wysihtml.toolbar.Dialog.extend({ 1.261 + show: function(elementToChange) { 1.262 + this.base(elementToChange); 1.263 + } 1.264 + }); 1.265 +})(wysihtml); 1.266 + 1.267 +(function(wysihtml) { 1.268 + var dom = wysihtml.dom, 1.269 + SELECTOR_FIELDS = "[data-wysihtml-dialog-field]", 1.270 + ATTRIBUTE_FIELDS = "data-wysihtml-dialog-field"; 1.271 + 1.272 + wysihtml.toolbar.Dialog_fontSizeStyle = wysihtml.toolbar.Dialog.extend({ 1.273 + multiselect: true, 1.274 + 1.275 + _serialize: function() { 1.276 + return {"size" : this.container.querySelector('[data-wysihtml-dialog-field="size"]').value}; 1.277 + }, 1.278 + 1.279 + _interpolate: function(avoidHiddenFields) { 1.280 + var focusedElement = document.querySelector(":focus"), 1.281 + field = this.container.querySelector("[data-wysihtml-dialog-field='size']"), 1.282 + firstElement = (this.elementToChange) ? ((wysihtml.lang.object(this.elementToChange).isArray()) ? this.elementToChange[0] : this.elementToChange) : null, 1.283 + styleStr = (firstElement) ? firstElement.getAttribute('style') : null, 1.284 + size = (styleStr) ? wysihtml.quirks.styleParser.parseFontSize(styleStr) : null; 1.285 + 1.286 + if (field && field !== focusedElement && size && !(/^\s*$/).test(size)) { 1.287 + field.value = size; 1.288 + } 1.289 + } 1.290 + }); 1.291 +})(wysihtml); 1.292 + 1.293 +(function(wysihtml) { 1.294 + var SELECTOR_FIELDS = "[data-wysihtml-dialog-field]", 1.295 + ATTRIBUTE_FIELDS = "data-wysihtml-dialog-field"; 1.296 + 1.297 + wysihtml.toolbar.Dialog_foreColorStyle = wysihtml.toolbar.Dialog.extend({ 1.298 + multiselect: true, 1.299 + 1.300 + _serialize: function() { 1.301 + var data = {}, 1.302 + fields = this.container.querySelectorAll(SELECTOR_FIELDS), 1.303 + length = fields.length, 1.304 + i = 0; 1.305 + 1.306 + for (; i<length; i++) { 1.307 + data[fields[i].getAttribute(ATTRIBUTE_FIELDS)] = fields[i].value; 1.308 + } 1.309 + return data; 1.310 + }, 1.311 + 1.312 + _interpolate: function(avoidHiddenFields) { 1.313 + var field, colourMode, 1.314 + styleParser = wysihtml.quirks.styleParser, 1.315 + focusedElement = document.querySelector(":focus"), 1.316 + fields = this.container.querySelectorAll(SELECTOR_FIELDS), 1.317 + length = fields.length, 1.318 + i = 0, 1.319 + firstElement = (this.elementToChange) ? ((wysihtml.lang.object(this.elementToChange).isArray()) ? this.elementToChange[0] : this.elementToChange) : null, 1.320 + colourStr = (firstElement) ? firstElement.getAttribute("style") : null, 1.321 + colour = (colourStr) ? styleParser.parseColor(colourStr, "color") : null; 1.322 + 1.323 + for (; i<length; i++) { 1.324 + field = fields[i]; 1.325 + // Never change elements where the user is currently typing in 1.326 + if (field === focusedElement) { 1.327 + continue; 1.328 + } 1.329 + // Don't update hidden fields3 1.330 + if (avoidHiddenFields && field.type === "hidden") { 1.331 + continue; 1.332 + } 1.333 + if (field.getAttribute(ATTRIBUTE_FIELDS) === "color") { 1.334 + colourMode = (field.dataset.colormode || "rgb").toLowerCase(); 1.335 + colourMode = colourMode === "hex" ? "hash" : colourMode; 1.336 + 1.337 + if (colour) { 1.338 + field.value = styleParser.unparseColor(colour, colourMode); 1.339 + } else { 1.340 + field.value = styleParser.unparseColor([0, 0, 0], colourMode); 1.341 + } 1.342 + } 1.343 + } 1.344 + } 1.345 + 1.346 + }); 1.347 +})(wysihtml); 1.348 + 1.349 +/** 1.350 + * Converts speech-to-text and inserts this into the editor 1.351 + * As of now (2011/03/25) this only is supported in Chrome >= 11 1.352 + * 1.353 + * Note that it sends the recorded audio to the google speech recognition api: 1.354 + * http://stackoverflow.com/questions/4361826/does-chrome-have-buil-in-speech-recognition-for-input-type-text-x-webkit-speec 1.355 + * 1.356 + * Current HTML5 draft can be found here 1.357 + * http://lists.w3.org/Archives/Public/public-xg-htmlspeech/2011Feb/att-0020/api-draft.html 1.358 + * 1.359 + * "Accessing Google Speech API Chrome 11" 1.360 + * http://mikepultz.com/2011/03/accessing-google-speech-api-chrome-11/ 1.361 + */ 1.362 +(function(wysihtml) { 1.363 + var dom = wysihtml.dom; 1.364 + 1.365 + var linkStyles = { 1.366 + position: "relative" 1.367 + }; 1.368 + 1.369 + var wrapperStyles = { 1.370 + left: 0, 1.371 + margin: 0, 1.372 + opacity: 0, 1.373 + overflow: "hidden", 1.374 + padding: 0, 1.375 + position: "absolute", 1.376 + top: 0, 1.377 + zIndex: 1 1.378 + }; 1.379 + 1.380 + var inputStyles = { 1.381 + cursor: "inherit", 1.382 + fontSize: "50px", 1.383 + height: "50px", 1.384 + marginTop: "-25px", 1.385 + outline: 0, 1.386 + padding: 0, 1.387 + position: "absolute", 1.388 + right: "-4px", 1.389 + top: "50%" 1.390 + }; 1.391 + 1.392 + var inputAttributes = { 1.393 + "x-webkit-speech": "", 1.394 + "speech": "" 1.395 + }; 1.396 + 1.397 + wysihtml.toolbar.Speech = function(parent, link) { 1.398 + var input = document.createElement("input"); 1.399 + if (!wysihtml.browser.supportsSpeechApiOn(input)) { 1.400 + link.style.display = "none"; 1.401 + return; 1.402 + } 1.403 + var lang = parent.editor.textarea.element.getAttribute("lang"); 1.404 + if (lang) { 1.405 + inputAttributes.lang = lang; 1.406 + } 1.407 + 1.408 + var wrapper = document.createElement("div"); 1.409 + 1.410 + wysihtml.lang.object(wrapperStyles).merge({ 1.411 + width: link.offsetWidth + "px", 1.412 + height: link.offsetHeight + "px" 1.413 + }); 1.414 + 1.415 + dom.insert(input).into(wrapper); 1.416 + dom.insert(wrapper).into(link); 1.417 + 1.418 + dom.setStyles(inputStyles).on(input); 1.419 + dom.setAttributes(inputAttributes).on(input); 1.420 + 1.421 + dom.setStyles(wrapperStyles).on(wrapper); 1.422 + dom.setStyles(linkStyles).on(link); 1.423 + 1.424 + var eventName = "onwebkitspeechchange" in input ? "webkitspeechchange" : "speechchange"; 1.425 + dom.observe(input, eventName, function() { 1.426 + parent.execCommand("insertText", input.value); 1.427 + input.value = ""; 1.428 + }); 1.429 + 1.430 + dom.observe(input, "click", function(event) { 1.431 + if (dom.hasClass(link, "wysihtml-command-disabled")) { 1.432 + event.preventDefault(); 1.433 + } 1.434 + 1.435 + event.stopPropagation(); 1.436 + }); 1.437 + }; 1.438 +})(wysihtml); 1.439 + 1.440 +/** 1.441 + * Toolbar 1.442 + * 1.443 + * @param {Object} parent Reference to instance of Editor instance 1.444 + * @param {Element} container Reference to the toolbar container element 1.445 + * 1.446 + * @example 1.447 + * <div id="toolbar"> 1.448 + * <a data-wysihtml-command="createLink">insert link</a> 1.449 + * <a data-wysihtml-command="formatBlock" data-wysihtml-command-value="h1">insert h1</a> 1.450 + * </div> 1.451 + * 1.452 + * <script> 1.453 + * var toolbar = new wysihtml.toolbar.Toolbar(editor, document.getElementById("toolbar")); 1.454 + * </script> 1.455 + */ 1.456 +(function(wysihtml) { 1.457 + var CLASS_NAME_COMMAND_DISABLED = "wysihtml-command-disabled", 1.458 + CLASS_NAME_COMMANDS_DISABLED = "wysihtml-commands-disabled", 1.459 + CLASS_NAME_COMMAND_ACTIVE = "wysihtml-command-active", 1.460 + CLASS_NAME_ACTION_ACTIVE = "wysihtml-action-active", 1.461 + dom = wysihtml.dom; 1.462 + 1.463 + wysihtml.toolbar.Toolbar = Base.extend( 1.464 + /** @scope wysihtml.toolbar.Toolbar.prototype */ { 1.465 + constructor: function(editor, container, showOnInit) { 1.466 + this.editor = editor; 1.467 + this.container = typeof(container) === "string" ? document.getElementById(container) : container; 1.468 + this.composer = editor.composer; 1.469 + 1.470 + this._getLinks("command"); 1.471 + this._getLinks("action"); 1.472 + 1.473 + this._observe(); 1.474 + if (showOnInit) { this.show(); } 1.475 + 1.476 + if (editor.config.classNameCommandDisabled != null) { 1.477 + CLASS_NAME_COMMAND_DISABLED = editor.config.classNameCommandDisabled; 1.478 + } 1.479 + if (editor.config.classNameCommandsDisabled != null) { 1.480 + CLASS_NAME_COMMANDS_DISABLED = editor.config.classNameCommandsDisabled; 1.481 + } 1.482 + if (editor.config.classNameCommandActive != null) { 1.483 + CLASS_NAME_COMMAND_ACTIVE = editor.config.classNameCommandActive; 1.484 + } 1.485 + if (editor.config.classNameActionActive != null) { 1.486 + CLASS_NAME_ACTION_ACTIVE = editor.config.classNameActionActive; 1.487 + } 1.488 + 1.489 + var speechInputLinks = this.container.querySelectorAll("[data-wysihtml-command=insertSpeech]"), 1.490 + length = speechInputLinks.length, 1.491 + i = 0; 1.492 + for (; i<length; i++) { 1.493 + new wysihtml.toolbar.Speech(this, speechInputLinks[i]); 1.494 + } 1.495 + }, 1.496 + 1.497 + _getLinks: function(type) { 1.498 + var links = this[type + "Links"] = wysihtml.lang.array(this.container.querySelectorAll("[data-wysihtml-" + type + "]")).get(), 1.499 + length = links.length, 1.500 + i = 0, 1.501 + mapping = this[type + "Mapping"] = {}, 1.502 + link, 1.503 + group, 1.504 + name, 1.505 + value, 1.506 + dialog, 1.507 + tracksBlankValue; 1.508 + 1.509 + for (; i<length; i++) { 1.510 + link = links[i]; 1.511 + name = link.getAttribute("data-wysihtml-" + type); 1.512 + value = link.getAttribute("data-wysihtml-" + type + "-value"); 1.513 + tracksBlankValue = link.getAttribute("data-wysihtml-" + type + "-blank-value"); 1.514 + group = this.container.querySelector("[data-wysihtml-" + type + "-group='" + name + "']"); 1.515 + dialog = this._getDialog(link, name); 1.516 + 1.517 + mapping[name + ":" + value] = { 1.518 + link: link, 1.519 + group: group, 1.520 + name: name, 1.521 + value: value, 1.522 + tracksBlankValue: tracksBlankValue, 1.523 + dialog: dialog, 1.524 + state: false 1.525 + }; 1.526 + } 1.527 + }, 1.528 + 1.529 + _getDialog: function(link, command) { 1.530 + var that = this, 1.531 + dialogElement = this.container.querySelector("[data-wysihtml-dialog='" + command + "']"), 1.532 + dialog, caretBookmark; 1.533 + 1.534 + if (dialogElement) { 1.535 + if (wysihtml.toolbar["Dialog_" + command]) { 1.536 + dialog = new wysihtml.toolbar["Dialog_" + command](link, dialogElement); 1.537 + } else { 1.538 + dialog = new wysihtml.toolbar.Dialog(link, dialogElement); 1.539 + } 1.540 + 1.541 + dialog.on("show", function() { 1.542 + caretBookmark = that.composer.selection.getBookmark(); 1.543 + that.editor.fire("show:dialog", { command: command, dialogContainer: dialogElement, commandLink: link }); 1.544 + }); 1.545 + 1.546 + dialog.on("save", function(attributes) { 1.547 + if (caretBookmark) { 1.548 + that.composer.selection.setBookmark(caretBookmark); 1.549 + } 1.550 + that._execCommand(command, attributes); 1.551 + that.editor.fire("save:dialog", { command: command, dialogContainer: dialogElement, commandLink: link }); 1.552 + that._hideAllDialogs(); 1.553 + that._preventInstantFocus(); 1.554 + caretBookmark = undefined; 1.555 + 1.556 + }); 1.557 + 1.558 + dialog.on("cancel", function() { 1.559 + if (caretBookmark) { 1.560 + that.composer.selection.setBookmark(caretBookmark); 1.561 + } 1.562 + that.editor.fire("cancel:dialog", { command: command, dialogContainer: dialogElement, commandLink: link }); 1.563 + caretBookmark = undefined; 1.564 + that._preventInstantFocus(); 1.565 + }); 1.566 + 1.567 + dialog.on("hide", function() { 1.568 + that.editor.fire("hide:dialog", { command: command, dialogContainer: dialogElement, commandLink: link }); 1.569 + caretBookmark = undefined; 1.570 + }); 1.571 + 1.572 + } 1.573 + return dialog; 1.574 + }, 1.575 + 1.576 + /** 1.577 + * @example 1.578 + * var toolbar = new wysihtml.Toolbar(); 1.579 + * // Insert a <blockquote> element or wrap current selection in <blockquote> 1.580 + * toolbar.execCommand("formatBlock", "blockquote"); 1.581 + */ 1.582 + execCommand: function(command, commandValue) { 1.583 + if (this.commandsDisabled) { 1.584 + return; 1.585 + } 1.586 + 1.587 + this._execCommand(command, commandValue); 1.588 + }, 1.589 + 1.590 + _execCommand: function(command, commandValue) { 1.591 + // Make sure that composer is focussed (false => don't move caret to the end) 1.592 + this.editor.focus(false); 1.593 + 1.594 + this.composer.commands.exec(command, commandValue); 1.595 + this._updateLinkStates(); 1.596 + }, 1.597 + 1.598 + execAction: function(action) { 1.599 + var editor = this.editor; 1.600 + if (action === "change_view") { 1.601 + if (editor.currentView === editor.textarea || editor.currentView === "source") { 1.602 + editor.fire("change_view", "composer"); 1.603 + } else { 1.604 + editor.fire("change_view", "textarea"); 1.605 + } 1.606 + } 1.607 + if (action == "showSource") { 1.608 + editor.fire("showSource"); 1.609 + } 1.610 + }, 1.611 + 1.612 + _observe: function() { 1.613 + var that = this, 1.614 + editor = this.editor, 1.615 + container = this.container, 1.616 + links = this.commandLinks.concat(this.actionLinks), 1.617 + length = links.length, 1.618 + i = 0; 1.619 + 1.620 + for (; i<length; i++) { 1.621 + // 'javascript:;' and unselectable=on Needed for IE, but done in all browsers to make sure that all get the same css applied 1.622 + // (you know, a:link { ... } doesn't match anchors with missing href attribute) 1.623 + if (links[i].nodeName === "A") { 1.624 + dom.setAttributes({ 1.625 + href: "javascript:;", 1.626 + unselectable: "on" 1.627 + }).on(links[i]); 1.628 + } else { 1.629 + dom.setAttributes({ unselectable: "on" }).on(links[i]); 1.630 + } 1.631 + } 1.632 + 1.633 + // Needed for opera and chrome 1.634 + dom.delegate(container, "[data-wysihtml-command], [data-wysihtml-action]", "mousedown", function(event) { event.preventDefault(); }); 1.635 + 1.636 + dom.delegate(container, "[data-wysihtml-command]", "click", function(event) { 1.637 + var state, 1.638 + link = this, 1.639 + command = link.getAttribute("data-wysihtml-command"), 1.640 + commandValue = link.getAttribute("data-wysihtml-command-value"), 1.641 + commandObj = that.commandMapping[command + ":" + commandValue]; 1.642 + 1.643 + if (commandValue || !commandObj.dialog) { 1.644 + that.execCommand(command, commandValue); 1.645 + } else { 1.646 + state = getCommandState(that.composer, commandObj); 1.647 + commandObj.dialog.show(state); 1.648 + } 1.649 + 1.650 + event.preventDefault(); 1.651 + }); 1.652 + 1.653 + dom.delegate(container, "[data-wysihtml-action]", "click", function(event) { 1.654 + var action = this.getAttribute("data-wysihtml-action"); 1.655 + that.execAction(action); 1.656 + event.preventDefault(); 1.657 + }); 1.658 + 1.659 + editor.on("interaction:composer", function(event) { 1.660 + if (!that.preventFocus) { 1.661 + that._updateLinkStates(); 1.662 + } 1.663 + }); 1.664 + 1.665 + this._ownerDocumentClick = function(event) { 1.666 + if (!wysihtml.dom.contains(that.container, event.target) && !wysihtml.dom.contains(that.composer.element, event.target)) { 1.667 + that._updateLinkStates(); 1.668 + that._preventInstantFocus(); 1.669 + } 1.670 + }; 1.671 + 1.672 + this.container.ownerDocument.addEventListener("click", this._ownerDocumentClick, false); 1.673 + this.editor.on("destroy:composer", this.destroy.bind(this)); 1.674 + 1.675 + if (this.editor.config.handleTables) { 1.676 + editor.on("tableselect:composer", function() { 1.677 + that.container.querySelectorAll('[data-wysihtml-hiddentools="table"]')[0].style.display = ""; 1.678 + }); 1.679 + editor.on("tableunselect:composer", function() { 1.680 + that.container.querySelectorAll('[data-wysihtml-hiddentools="table"]')[0].style.display = "none"; 1.681 + }); 1.682 + } 1.683 + 1.684 + editor.on("change_view", function(currentView) { 1.685 + // Set timeout needed in order to let the blur event fire first 1.686 + setTimeout(function() { 1.687 + that.commandsDisabled = (currentView !== "composer"); 1.688 + that._updateLinkStates(); 1.689 + if (that.commandsDisabled) { 1.690 + dom.addClass(container, CLASS_NAME_COMMANDS_DISABLED); 1.691 + } else { 1.692 + dom.removeClass(container, CLASS_NAME_COMMANDS_DISABLED); 1.693 + } 1.694 + }, 0); 1.695 + }); 1.696 + }, 1.697 + 1.698 + destroy: function() { 1.699 + this.container.ownerDocument.removeEventListener("click", this._ownerDocumentClick, false); 1.700 + }, 1.701 + 1.702 + _hideAllDialogs: function() { 1.703 + var commandMapping = this.commandMapping; 1.704 + for (var i in commandMapping) { 1.705 + if (commandMapping[i].dialog) { 1.706 + commandMapping[i].dialog.hide(); 1.707 + } 1.708 + } 1.709 + }, 1.710 + 1.711 + _preventInstantFocus: function() { 1.712 + this.preventFocus = true; 1.713 + setTimeout(function() { 1.714 + this.preventFocus = false; 1.715 + }.bind(this),0); 1.716 + }, 1.717 + 1.718 + _updateLinkStates: function() { 1.719 + 1.720 + var i, state, action, command, displayDialogAttributeValue, 1.721 + commandMapping = this.commandMapping, 1.722 + composer = this.composer, 1.723 + actionMapping = this.actionMapping; 1.724 + // every millisecond counts... this is executed quite often 1.725 + for (i in commandMapping) { 1.726 + command = commandMapping[i]; 1.727 + if (this.commandsDisabled) { 1.728 + state = false; 1.729 + dom.removeClass(command.link, CLASS_NAME_COMMAND_ACTIVE); 1.730 + if (command.group) { 1.731 + dom.removeClass(command.group, CLASS_NAME_COMMAND_ACTIVE); 1.732 + } 1.733 + if (command.dialog) { 1.734 + command.dialog.hide(); 1.735 + } 1.736 + } else { 1.737 + state = this.composer.commands.state(command.name, command.value); 1.738 + dom.removeClass(command.link, CLASS_NAME_COMMAND_DISABLED); 1.739 + if (command.group) { 1.740 + dom.removeClass(command.group, CLASS_NAME_COMMAND_DISABLED); 1.741 + } 1.742 + } 1.743 + if (command.state === state && !command.tracksBlankValue) { 1.744 + continue; 1.745 + } 1.746 + 1.747 + command.state = state; 1.748 + if (state) { 1.749 + if (command.tracksBlankValue) { 1.750 + dom.removeClass(command.link, CLASS_NAME_COMMAND_ACTIVE); 1.751 + } else { 1.752 + dom.addClass(command.link, CLASS_NAME_COMMAND_ACTIVE); 1.753 + if (command.group) { 1.754 + dom.addClass(command.group, CLASS_NAME_COMMAND_ACTIVE); 1.755 + } 1.756 + // commands with fixed value can not have a dialog. 1.757 + if (command.dialog && (typeof command.value === "undefined" || command.value === null)) { 1.758 + if (state && typeof state === "object") { 1.759 + state = getCommandState(composer, command); 1.760 + command.state = state; 1.761 + 1.762 + // If dialog has dataset.showdialogonselection set as true, 1.763 + // Dialog displays on text state becoming active regardless of clobal showToolbarDialogsOnSelection options value 1.764 + displayDialogAttributeValue = command.dialog.container.dataset ? command.dialog.container.dataset.showdialogonselection : false; 1.765 + 1.766 + if (composer.config.showToolbarDialogsOnSelection || displayDialogAttributeValue) { 1.767 + command.dialog.show(state); 1.768 + } else { 1.769 + command.dialog.update(state); 1.770 + } 1.771 + } else { 1.772 + command.dialog.hide(); 1.773 + } 1.774 + } 1.775 + } 1.776 + } else { 1.777 + if (command.tracksBlankValue) { 1.778 + dom.addClass(command.link, CLASS_NAME_COMMAND_ACTIVE); 1.779 + } else { 1.780 + dom.removeClass(command.link, CLASS_NAME_COMMAND_ACTIVE); 1.781 + if (command.group) { 1.782 + dom.removeClass(command.group, CLASS_NAME_COMMAND_ACTIVE); 1.783 + } 1.784 + // commands with fixed value can not have a dialog. 1.785 + if (command.dialog && !command.value) { 1.786 + command.dialog.hide(); 1.787 + } 1.788 + } 1.789 + } 1.790 + } 1.791 + 1.792 + for (i in actionMapping) { 1.793 + action = actionMapping[i]; 1.794 + 1.795 + if (action.name === "change_view") { 1.796 + action.state = this.editor.currentView === this.editor.textarea || this.editor.currentView === "source"; 1.797 + if (action.state) { 1.798 + dom.addClass(action.link, CLASS_NAME_ACTION_ACTIVE); 1.799 + } else { 1.800 + dom.removeClass(action.link, CLASS_NAME_ACTION_ACTIVE); 1.801 + } 1.802 + } 1.803 + } 1.804 + }, 1.805 + 1.806 + show: function() { 1.807 + this.container.style.display = ""; 1.808 + }, 1.809 + 1.810 + hide: function() { 1.811 + this.container.style.display = "none"; 1.812 + } 1.813 + }); 1.814 + 1.815 + function getCommandState (composer, command) { 1.816 + var state = composer.commands.state(command.name, command.value); 1.817 + 1.818 + // Grab first and only object/element in state array, otherwise convert state into boolean 1.819 + // to avoid showing a dialog for multiple selected elements which may have different attributes 1.820 + // eg. when two links with different href are selected, the state will be an array consisting of both link elements 1.821 + // but the dialog interface can only update one 1.822 + if (!command.dialog.multiselect && wysihtml.lang.object(state).isArray()) { 1.823 + state = state.length === 1 ? state[0] : true; 1.824 + } 1.825 + 1.826 + return state; 1.827 + } 1.828 + 1.829 + // Extend defaults 1.830 + 1.831 + // Id of the toolbar element, pass falsey value if you don't want any toolbar logic 1.832 + wysihtml.Editor.prototype.defaults.toolbar = undefined; 1.833 + 1.834 + // Whether toolbar is displayed after init by script automatically. 1.835 + // Can be set to false if toolobar is set to display only on editable area focus 1.836 + wysihtml.Editor.prototype.defaults.showToolbarAfterInit = true; 1.837 + 1.838 + // With default toolbar it shows dialogs in toolbar when their related text format state becomes active (click on link in text opens link dialogue) 1.839 + wysihtml.Editor.prototype.defaults.showToolbarDialogsOnSelection= true; 1.840 + 1.841 + // Bind toolbar initiation on editor instance creation 1.842 + wysihtml.extendEditor(function(editor) { 1.843 + if (editor.config.toolbar) { 1.844 + editor.toolbar = new wysihtml.toolbar.Toolbar(editor, editor.config.toolbar, editor.config.showToolbarAfterInit); 1.845 + editor.on('destroy:composer', function() { 1.846 + if (editor && editor.toolbar) { 1.847 + editor.toolbar.destroy(); 1.848 + } 1.849 + }); 1.850 + } 1.851 + }); 1.852 + 1.853 +})(wysihtml);