/* Copyright (c) 2004-2011, The Dojo Foundation All Rights Reserved. Available via Academic Free License >= 2.1 OR the modified BSD license. see: http://dojotoolkit.org/license for details */ if(!dojo._hasResource["dijit._editor.plugins.FontChoice"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. dojo._hasResource["dijit._editor.plugins.FontChoice"] = true; dojo.provide("dijit._editor.plugins.FontChoice"); dojo.require("dijit._editor._Plugin"); dojo.require("dijit._editor.range"); dojo.require("dijit._editor.selection"); dojo.require("dijit.form.FilteringSelect"); dojo.require("dojo.data.ItemFileReadStore"); dojo.require("dojo.i18n"); dojo.requireLocalization("dijit._editor", "FontChoice", null, "ROOT,ar,ca,cs,da,de,el,es,fi,fr,he,hu,it,ja,kk,ko,nb,nl,pl,pt,pt-pt,ro,ru,sk,sl,sv,th,tr,zh,zh-tw"); dojo.declare("dijit._editor.plugins._FontDropDown", [dijit._Widget, dijit._Templated],{ // summary: // Base class for widgets that contains a label (like "Font:") // and a FilteringSelect drop down to pick a value. // Used as Toolbar entry. // label: [public] String // The label to apply to this particular FontDropDown. label: "", // widgetsInTemplate: [public] boolean // Over-ride denoting the template has widgets to parse. widgetsInTemplate: true, // plainText: [public] boolean // Flag to indicate that the returned label should be plain text // instead of an example. plainText: false, // templateString: [public] String // The template used to construct the labeled dropdown. templateString: "" + "" + "" + "", postMixInProperties: function(){ // summary: // Over-ride to set specific properties. this.inherited(arguments); this.strings = dojo.i18n.getLocalization("dijit._editor", "FontChoice"); // Set some substitution variables used in the template this.label = this.strings[this.command]; this.id = dijit.getUniqueId(this.declaredClass.replace(/\./g,"_")); this.selectId = this.id + "_select"; this.inherited(arguments); }, postCreate: function(){ // summary: // Over-ride for the default postCreate action // This establishes the filtering selects and the like. // Initialize the list of items in the drop down by creating data store with items like: // {value: 1, name: "xx-small", label: "xx-small" } var items = dojo.map(this.values, function(value){ var name = this.strings[value] || value; return { label: this.getLabel(value, name), name: name, value: value }; }, this); this.select.store = new dojo.data.ItemFileReadStore({ data: { identifier: "value", items: items } }); this.select.set("value", "", false); this.disabled = this.select.get("disabled"); }, _setValueAttr: function(value, priorityChange){ // summary: // Over-ride for the default action of setting the // widget value, maps the input to known values // value: Object|String // The value to set in the select. // priorityChange: // Optional parameter used to tell the select whether or not to fire // onChange event. //if the value is not a permitted value, just set empty string to prevent showing the warning icon priorityChange = priorityChange !== false?true:false; this.select.set('value', dojo.indexOf(this.values,value) < 0 ? "" : value, priorityChange); if(!priorityChange){ // Clear the last state in case of updateState calls. Ref: #10466 this.select._lastValueReported=null; } }, _getValueAttr: function(){ // summary: // Allow retreiving the value from the composite select on // call to button.get("value"); return this.select.get('value'); }, focus: function(){ // summary: // Over-ride for focus control of this widget. Delegates focus down to the // filtering select. this.select.focus(); }, _setDisabledAttr: function(value){ // summary: // Over-ride for the button's 'disabled' attribute so that it can be // disabled programmatically. // Save off ths disabled state so the get retrieves it correctly //without needing to have a function proxy it. this.disabled = value; this.select.set("disabled", value); } }); dojo.declare("dijit._editor.plugins._FontNameDropDown", dijit._editor.plugins._FontDropDown, { // summary: // Dropdown to select a font; goes in editor toolbar. // generic: Boolean // Use generic (web standard) font names generic: false, // command: [public] String // The editor 'command' implemented by this plugin. command: "fontName", postMixInProperties: function(){ // summary: // Over-ride for the default posr mixin control if(!this.values){ this.values = this.generic ? ["serif", "sans-serif", "monospace", "cursive", "fantasy"] : // CSS font-family generics ["Arial", "Times New Roman", "Comic Sans MS", "Courier New"]; } this.inherited(arguments); }, getLabel: function(value, name){ // summary: // Function used to generate the labels of the format dropdown // will return a formatted, or plain label based on the value // of the plainText option. // value: String // The 'insert value' associated with a name // name: String // The text name of the value if(this.plainText){ return name; }else{ return "
" + name + "
"; } }, _setValueAttr: function(value, priorityChange){ // summary: // Over-ride for the default action of setting the // widget value, maps the input to known values priorityChange = priorityChange !== false?true:false; if(this.generic){ var map = { "Arial": "sans-serif", "Helvetica": "sans-serif", "Myriad": "sans-serif", "Times": "serif", "Times New Roman": "serif", "Comic Sans MS": "cursive", "Apple Chancery": "cursive", "Courier": "monospace", "Courier New": "monospace", "Papyrus": "fantasy" // ,"????": "fantasy" TODO: IE doesn't map fantasy font-family? }; value = map[value] || value; } this.inherited(arguments, [value, priorityChange]); } }); dojo.declare("dijit._editor.plugins._FontSizeDropDown", dijit._editor.plugins._FontDropDown, { // summary: // Dropdown to select a font size; goes in editor toolbar. // command: [public] String // The editor 'command' implemented by this plugin. command: "fontSize", // values: [public] Number[] // The HTML font size values supported by this plugin values: [1,2,3,4,5,6,7], // sizes according to the old HTML FONT SIZE getLabel: function(value, name){ // summary: // Function used to generate the labels of the format dropdown // will return a formatted, or plain label based on the value // of the plainText option. // We're stuck using the deprecated FONT tag to correspond // with the size measurements used by the editor // value: String // The 'insert value' associated with a name // name: String // The text name of the value if(this.plainText){ return name; }else{ return "" + name + ""; } }, _setValueAttr: function(value, priorityChange){ // summary: // Over-ride for the default action of setting the // widget value, maps the input to known values priorityChange = priorityChange !== false?true:false; if(value.indexOf && value.indexOf("px") != -1){ var pixels = parseInt(value, 10); value = {10:1, 13:2, 16:3, 18:4, 24:5, 32:6, 48:7}[pixels] || value; } this.inherited(arguments, [value, priorityChange]); } }); dojo.declare("dijit._editor.plugins._FormatBlockDropDown", dijit._editor.plugins._FontDropDown, { // summary: // Dropdown to select a format (like paragraph or heading); goes in editor toolbar. // command: [public] String // The editor 'command' implemented by this plugin. command: "formatBlock", // values: [public] Array // The HTML format tags supported by this plugin values: ["noFormat", "p", "h1", "h2", "h3", "pre"], postCreate: function(){ // Init and set the default value to no formatting. Update state will adjust it // as needed. this.inherited(arguments); this.set("value", "noFormat", false); }, getLabel: function(value, name){ // summary: // Function used to generate the labels of the format dropdown // will return a formatted, or plain label based on the value // of the plainText option. // value: String // The 'insert value' associated with a name // name: String // The text name of the value if(this.plainText || value == "noFormat"){ return name; }else{ return "<" + value + ">" + name + ""; } }, _execCommand: function(editor, command, choice){ // summary: // Over-ride for default exec-command label. // Allows us to treat 'none' as special. if(choice === "noFormat"){ var start; var end; var sel = dijit.range.getSelection(editor.window); if(sel && sel.rangeCount > 0){ var range = sel.getRangeAt(0); var node, tag; if(range){ start = range.startContainer; end = range.endContainer; // find containing nodes of start/end. while(start && start !== editor.editNode && start !== editor.document.body && start.nodeType !== 1){ start = start.parentNode; } while(end && end !== editor.editNode && end !== editor.document.body && end.nodeType !== 1){ end = end.parentNode; } var processChildren = dojo.hitch(this, function(node, array){ if(node.childNodes && node.childNodes.length){ var i; for(i = 0; i < node.childNodes.length; i++){ var c = node.childNodes[i]; if(c.nodeType == 1){ if(dojo.withGlobal(editor.window, "inSelection", dijit._editor.selection, [c])){ var tag = c.tagName? c.tagName.toLowerCase(): ""; if(dojo.indexOf(this.values, tag) !== -1){ array.push(c); } processChildren(c,array); } } } } }); var unformatNodes = dojo.hitch(this, function(nodes){ // summary: // Internal function to clear format nodes. // nodes: // The array of nodes to strip formatting from. if(nodes && nodes.length){ editor.beginEditing(); while(nodes.length){ this._removeFormat(editor, nodes.pop()); } editor.endEditing(); } }); var clearNodes = []; if(start == end){ //Contained within the same block, may be collapsed, but who cares, see if we // have a block element to remove. var block; node = start; while(node && node !== editor.editNode && node !== editor.document.body){ if(node.nodeType == 1){ tag = node.tagName? node.tagName.toLowerCase(): ""; if(dojo.indexOf(this.values, tag) !== -1){ block = node; break; } } node = node.parentNode; } //Also look for all child nodes in the selection that may need to be //cleared of formatting processChildren(start, clearNodes); if(block) { clearNodes = [block].concat(clearNodes); } unformatNodes(clearNodes); }else{ // Probably a multi select, so we have to process it. Whee. node = start; while(dojo.withGlobal(editor.window, "inSelection", dijit._editor.selection, [node])){ if(node.nodeType == 1){ tag = node.tagName? node.tagName.toLowerCase(): ""; if(dojo.indexOf(this.values, tag) !== -1){ clearNodes.push(node); } processChildren(node,clearNodes); } node = node.nextSibling; } unformatNodes(clearNodes); } editor.onDisplayChanged(); } } }else{ editor.execCommand(command, choice); } }, _removeFormat: function(editor, node){ // summary: // function to remove the block format node. // node: // The block format node to remove (and leave the contents behind) if(editor.customUndo){ // So of course IE doesn't work right with paste-overs. // We have to do this manually, which is okay since IE already uses // customUndo and we turned it on for WebKit. WebKit pasted funny, // so couldn't use the execCommand approach while(node.firstChild){ dojo.place(node.firstChild, node, "before"); } node.parentNode.removeChild(node); }else{ // Everyone else works fine this way, a paste-over and is native // undo friendly. dojo.withGlobal(editor.window, "selectElementChildren", dijit._editor.selection, [node]); var html = dojo.withGlobal(editor.window, "getSelectedHtml", dijit._editor.selection, [null]); dojo.withGlobal(editor.window, "selectElement", dijit._editor.selection, [node]); editor.execCommand("inserthtml", html||""); } } }); // TODO: for 2.0, split into FontChoice plugin into three separate classes, // one for each command (and change registry below) dojo.declare("dijit._editor.plugins.FontChoice", dijit._editor._Plugin,{ // summary: // This plugin provides three drop downs for setting style in the editor // (font, font size, and format block), as controlled by command. // // description: // The commands provided by this plugin are: // // * fontName // | Provides a drop down to select from a list of font names // * fontSize // | Provides a drop down to select from a list of font sizes // * formatBlock // | Provides a drop down to select from a list of block styles // | // // which can easily be added to an editor by including one or more of the above commands // in the `plugins` attribute as follows: // // | plugins="['fontName','fontSize',...]" // // It is possible to override the default dropdown list by providing an Array for the `custom` property when // instantiating this plugin, e.g. // // | plugins="[{name:'dijit._editor.plugins.FontChoice', command:'fontName', custom:['Verdana','Myriad','Garamond']},...]" // // Alternatively, for `fontName` only, `generic:true` may be specified to provide a dropdown with // [CSS generic font families](http://www.w3.org/TR/REC-CSS2/fonts.html#generic-font-families) // // Note that the editor is often unable to properly handle font styling information defined outside // the context of the current editor instance, such as pre-populated HTML. // useDefaultCommand: [protected] booleam // Override _Plugin.useDefaultCommand... // processing is handled by this plugin, not by dijit.Editor. useDefaultCommand: false, _initButton: function(){ // summary: // Overrides _Plugin._initButton(), to initialize the FilteringSelect+label in toolbar, // rather than a simple button. // tags: // protected // Create the widget to go into the toolbar (the so-called "button") var clazz = { fontName: dijit._editor.plugins._FontNameDropDown, fontSize: dijit._editor.plugins._FontSizeDropDown, formatBlock: dijit._editor.plugins._FormatBlockDropDown }[this.command], params = this.params; // For back-compat reasons support setting custom values via "custom" parameter // rather than "values" parameter if(this.params.custom){ params.values = this.params.custom; } var editor = this.editor; this.button = new clazz(dojo.delegate({dir: editor.dir, lang: editor.lang}, params)); // Reflect changes to the drop down in the editor this.connect(this.button.select, "onChange", function(choice){ // User invoked change, since all internal updates set priorityChange to false and will // not trigger an onChange event. this.editor.focus(); if(this.command == "fontName" && choice.indexOf(" ") != -1){ choice = "'" + choice + "'"; } // Invoke, the editor already normalizes commands called through its // execCommand. if(this.button._execCommand){ this.button._execCommand(this.editor, this.command, choice); }else{ this.editor.execCommand(this.command, choice); } }); }, updateState: function(){ // summary: // Overrides _Plugin.updateState(). This controls updating the menu // options to the right values on state changes in the document (that trigger a // test of the actions.) // It set value of drop down in toolbar to reflect font/font size/format block // of text at current caret position. // tags: // protected var _e = this.editor; var _c = this.command; if(!_e || !_e.isLoaded || !_c.length){ return; } if(this.button){ var disabled = this.get("disabled"); this.button.set("disabled", disabled); if(disabled){ return; } var value; try{ value = _e.queryCommandValue(_c) || ""; }catch(e){ //Firefox may throw error above if the editor is just loaded, ignore it value = ""; } // strip off single quotes, if any var quoted = dojo.isString(value) && value.match(/'([^']*)'/); if(quoted){ value = quoted[1]; } if(_c === "formatBlock"){ if(!value || value == "p"){ // Some browsers (WebKit) doesn't actually get the tag info right. // and IE returns paragraph when in a DIV!, so incorrect a lot, // so we have double-check it. value = null; var elem; // Try to find the current element where the caret is. var sel = dijit.range.getSelection(this.editor.window); if(sel && sel.rangeCount > 0){ var range = sel.getRangeAt(0); if(range){ elem = range.endContainer; } } // Okay, now see if we can find one of the formatting types we're in. while(elem && elem !== _e.editNode && elem !== _e.document){ var tg = elem.tagName?elem.tagName.toLowerCase():""; if(tg && dojo.indexOf(this.button.values, tg) > -1){ value = tg; break; } elem = elem.parentNode; } if(!value){ // Still no value, so lets select 'none'. value = "noFormat"; } }else{ // Check that the block format is one allowed, if not, // null it so that it gets set to empty. if(dojo.indexOf(this.button.values, value) < 0){ value = "noFormat"; } } } if(value !== this.button.get("value")){ // Set the value, but denote it is not a priority change, so no // onchange fires. this.button.set('value', value, false); } } } }); // Register this plugin. dojo.subscribe(dijit._scopeName + ".Editor.getPlugin",null,function(o){ if(o.plugin){ return; } switch(o.args.name){ case "fontName": case "fontSize": case "formatBlock": o.plugin = new dijit._editor.plugins.FontChoice({ command: o.args.name, plainText: o.args.plainText?o.args.plainText:false }); } }); }