You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
631 lines
20 KiB
631 lines
20 KiB
define([ |
|
"require", |
|
"dojo/_base/array", // array.indexOf array.map |
|
"dojo/_base/declare", // declare |
|
"dojo/dom-construct", // domConstruct.place |
|
"dojo/i18n", // i18n.getLocalization |
|
"dojo/_base/lang", // lang.delegate lang.hitch lang.isString |
|
"dojo/string", |
|
"dojo/store/Memory", // MemoryStore |
|
"../../registry", // registry.getUniqueId |
|
"../../_Widget", |
|
"../../_TemplatedMixin", |
|
"../../_WidgetsInTemplateMixin", |
|
"../../form/FilteringSelect", |
|
"../_Plugin", |
|
"../range", |
|
"dojo/i18n!../nls/FontChoice" |
|
], function(require, array, declare, domConstruct, i18n, lang, stringUtil, MemoryStore, |
|
registry, _Widget, _TemplatedMixin, _WidgetsInTemplateMixin, FilteringSelect, _Plugin, rangeapi){ |
|
|
|
// module: |
|
// dijit/_editor/plugins/FontChoice |
|
|
|
var _FontDropDown = declare("dijit._editor.plugins._FontDropDown", |
|
[_Widget, _TemplatedMixin, _WidgetsInTemplateMixin], { |
|
// 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: "", |
|
|
|
// 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: "<span style='white-space: nowrap' class='dijit dijitReset dijitInline'>" + |
|
"<label class='dijitLeft dijitInline' for='${selectId}'>${label}</label>" + |
|
"<input data-dojo-type='../../form/FilteringSelect' required='false' " + |
|
"data-dojo-props='labelType:\"html\", labelAttr:\"label\", searchAttr:\"name\"' " + |
|
"class='${comboClass}' " + |
|
"tabIndex='-1' id='${selectId}' data-dojo-attach-point='select' value=''/>" + |
|
"</span>", |
|
|
|
// contextRequire: [public] Function |
|
// The context require that is used to resolve modules in template. |
|
contextRequire: require, |
|
|
|
postMixInProperties: function(){ |
|
// summary: |
|
// Over-ride to set specific properties. |
|
this.inherited(arguments); |
|
|
|
this.strings = i18n.getLocalization("dijit._editor", "FontChoice"); |
|
|
|
// Set some substitution variables used in the template |
|
this.label = this.strings[this.command]; |
|
|
|
// _WidgetBase sets the id after postMixInProperties(), but we need it now. |
|
// Alternative is to have a buildRendering() method and move this.selectId setting there, |
|
// or alternately get rid of selectId variable and just access ${id} in template? |
|
this.id = registry.getUniqueId(this.declaredClass.replace(/\./g, "_")); |
|
|
|
this.selectId = this.id + "_select"; // used in template |
|
|
|
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: "<font size=1>xx-small</font-size>" } |
|
this.select.set("store", new MemoryStore({ |
|
idProperty: "value", |
|
data: array.map(this.values, function(value){ |
|
var name = this.strings[value] || value; |
|
return { |
|
label: this.getLabel(value, name), |
|
name: name, |
|
value: value |
|
}; |
|
}, this) |
|
})); |
|
|
|
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; |
|
this.select.set('value', array.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 retrieving 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._set("disabled", value); |
|
this.select.set("disabled", value); |
|
} |
|
}); |
|
|
|
|
|
var _FontNameDropDown = declare("dijit._editor.plugins._FontNameDropDown", _FontDropDown, { |
|
// summary: |
|
// Dropdown to select a font; goes in editor toolbar. |
|
|
|
// generic: [const] Boolean |
|
// Use generic (web standard) font names |
|
generic: false, |
|
|
|
// command: [public] String |
|
// The editor 'command' implemented by this plugin. |
|
command: "fontName", |
|
|
|
comboClass: "dijitFontNameCombo", |
|
|
|
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 "<div style='font-family: " + value + "'>" + name + "</div>"; |
|
} |
|
}, |
|
|
|
_normalizeFontName: function (value) { |
|
// summary: |
|
// Function used to choose one font name when the value is a list of font names |
|
// like "Verdana, Arial, Helvetica, sans-serif" |
|
var allowedValues = this.values; |
|
if (!value || !allowedValues) { |
|
return value; |
|
} |
|
var fontNames = value.split(','); |
|
if (fontNames.length > 1) { |
|
for (var i = 0, l = fontNames.length; i < l; i++) { |
|
var fontName = stringUtil.trim(fontNames[i]); |
|
var pos = array.indexOf(allowedValues, fontName); |
|
if (pos > -1) { |
|
return fontName; |
|
} |
|
} |
|
} |
|
return value; |
|
}, |
|
|
|
_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; |
|
value = this._normalizeFontName(value); |
|
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", |
|
"Estrangelo Edessa": "cursive", // Windows 7 |
|
"Gabriola": "fantasy" // Windows 7 |
|
}; |
|
value = map[value] || value; |
|
} |
|
this.inherited(arguments, [value, priorityChange]); |
|
} |
|
}); |
|
|
|
var _FontSizeDropDown = declare("dijit._editor.plugins._FontSizeDropDown", _FontDropDown, { |
|
// summary: |
|
// Dropdown to select a font size; goes in editor toolbar. |
|
|
|
// command: [public] String |
|
// The editor 'command' implemented by this plugin. |
|
command: "fontSize", |
|
|
|
comboClass: "dijitFontSizeCombo", |
|
|
|
// 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 "<font size=" + value + "'>" + name + "</font>"; |
|
} |
|
}, |
|
|
|
_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; |
|
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]); |
|
} |
|
}); |
|
|
|
|
|
var _FormatBlockDropDown = declare("dijit._editor.plugins._FormatBlockDropDown", _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", |
|
|
|
comboClass: "dijitFormatBlockCombo", |
|
|
|
// 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 + "</" + value + ">"; |
|
} |
|
}, |
|
|
|
_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 = rangeapi.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 = lang.hitch(this, function(node, ary){ |
|
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(editor.selection.inSelection(c)){ |
|
var tag = c.tagName ? c.tagName.toLowerCase() : ""; |
|
if(array.indexOf(this.values, tag) !== -1){ |
|
ary.push(c); |
|
} |
|
processChildren(c, ary); |
|
} |
|
} |
|
} |
|
} |
|
}); |
|
|
|
var unformatNodes = lang.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(array.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(editor.selection.inSelection(node)){ |
|
if(node.nodeType == 1){ |
|
tag = node.tagName ? node.tagName.toLowerCase() : ""; |
|
if(array.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){ |
|
domConstruct.place(node.firstChild, node, "before"); |
|
} |
|
node.parentNode.removeChild(node); |
|
}else{ |
|
// Everyone else works fine this way, a paste-over and is native |
|
// undo friendly. |
|
editor.selection.selectElementChildren(node); |
|
var html = editor.selection.getSelectedHtml(); |
|
editor.selection.selectElement(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) |
|
var FontChoice = declare("dijit._editor.plugins.FontChoice", _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', values:['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] Boolean |
|
// 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: _FontNameDropDown, |
|
fontSize: _FontSizeDropDown, |
|
formatBlock: _FormatBlockDropDown |
|
}[this.command], |
|
params = this.params; |
|
|
|
// For back-compat reasons support setting custom values via "custom" parameter |
|
// rather than "values" parameter. Remove in 2.0. |
|
if(this.params.custom){ |
|
params.values = this.params.custom; |
|
} |
|
|
|
var editor = this.editor; |
|
this.button = new clazz(lang.delegate({dir: editor.dir, lang: editor.lang}, params)); |
|
|
|
// Reflect changes to the drop down in the editor |
|
this.own(this.button.select.on("change", lang.hitch(this, function(choice){ |
|
// User invoked change, since all internal updates set priorityChange to false and will |
|
// not trigger an onChange event. |
|
|
|
if(this.editor.focused){ |
|
// put focus back in the iframe, unless focus has somehow been shifted out of the editor completely |
|
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 = lang.isString(value) && (value.match(/'([^']*)'/) || value.match(/"([^"]*)"/)); |
|
if(quoted){ |
|
value = quoted[1]; |
|
} |
|
if (_c === "fontSize" && !value) { |
|
value = 3; // default to "small" since Editor starts out with 16px font which is considered "small". |
|
} |
|
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 = rangeapi.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 && array.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(array.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 these plugins |
|
array.forEach(["fontName", "fontSize", "formatBlock"], function(name){ |
|
_Plugin.registry[name] = function(args){ |
|
return new FontChoice({ |
|
command: name, |
|
plainText: args.plainText |
|
}); |
|
}; |
|
}); |
|
|
|
// Make all classes available through AMD, and return main class |
|
FontChoice._FontDropDown = _FontDropDown; |
|
FontChoice._FontNameDropDown = _FontNameDropDown; |
|
FontChoice._FontSizeDropDown = _FontSizeDropDown; |
|
FontChoice._FormatBlockDropDown = _FormatBlockDropDown; |
|
return FontChoice; |
|
|
|
});
|
|
|