]>
Commit | Line | Data |
---|---|---|
f0cfe83e AD |
1 | define("dijit/_editor/plugins/FontChoice", [ |
2 | "dojo/_base/array", // array.indexOf array.map | |
3 | "dojo/_base/declare", // declare | |
4 | "dojo/dom-construct", // domConstruct.place | |
5 | "dojo/i18n", // i18n.getLocalization | |
6 | "dojo/_base/lang", // lang.delegate lang.hitch lang.isString | |
7 | "dojo/store/Memory", // MemoryStore | |
8 | "../../registry", // registry.getUniqueId | |
9 | "../../_Widget", | |
10 | "../../_TemplatedMixin", | |
11 | "../../_WidgetsInTemplateMixin", | |
12 | "../../form/FilteringSelect", | |
13 | "../_Plugin", | |
14 | "../range", | |
15 | "dojo/i18n!../nls/FontChoice" | |
16 | ], function(array, declare, domConstruct, i18n, lang, MemoryStore, | |
17 | registry, _Widget, _TemplatedMixin, _WidgetsInTemplateMixin, FilteringSelect, _Plugin, rangeapi){ | |
18 | ||
19 | ||
20 | // module: | |
21 | // dijit/_editor/plugins/FontChoice | |
22 | ||
23 | ||
24 | var _FontDropDown = declare("dijit._editor.plugins._FontDropDown", | |
25 | [_Widget, _TemplatedMixin, _WidgetsInTemplateMixin], { | |
26 | // summary: | |
27 | // Base class for widgets that contains a label (like "Font:") | |
28 | // and a FilteringSelect drop down to pick a value. | |
29 | // Used as Toolbar entry. | |
30 | ||
31 | // label: [public] String | |
32 | // The label to apply to this particular FontDropDown. | |
33 | label: "", | |
34 | ||
35 | // plainText: [public] boolean | |
36 | // Flag to indicate that the returned label should be plain text | |
37 | // instead of an example. | |
38 | plainText: false, | |
39 | ||
40 | // templateString: [public] String | |
41 | // The template used to construct the labeled dropdown. | |
42 | templateString: | |
43 | "<span style='white-space: nowrap' class='dijit dijitReset dijitInline'>" + | |
44 | "<label class='dijitLeft dijitInline' for='${selectId}'>${label}</label>" + | |
45 | "<input data-dojo-type='dijit.form.FilteringSelect' required='false' " + | |
46 | "data-dojo-props='labelType:\"html\", labelAttr:\"label\", searchAttr:\"name\"' " + | |
47 | "tabIndex='-1' id='${selectId}' data-dojo-attach-point='select' value=''/>" + | |
48 | "</span>", | |
49 | ||
50 | postMixInProperties: function(){ | |
51 | // summary: | |
52 | // Over-ride to set specific properties. | |
53 | this.inherited(arguments); | |
54 | ||
55 | this.strings = i18n.getLocalization("dijit._editor", "FontChoice"); | |
56 | ||
57 | // Set some substitution variables used in the template | |
58 | this.label = this.strings[this.command]; | |
59 | ||
60 | // _WidgetBase sets the id after postMixInProperties(), but we need it now. | |
61 | // Alternative is to have a buildRendering() method and move this.selectId setting there, | |
62 | // or alternately get rid of selectId variable and just access ${id} in template? | |
63 | this.id = registry.getUniqueId(this.declaredClass.replace(/\./g,"_")); | |
64 | ||
65 | this.selectId = this.id + "_select"; // used in template | |
66 | ||
67 | this.inherited(arguments); | |
68 | }, | |
69 | ||
70 | postCreate: function(){ | |
71 | // summary: | |
72 | // Over-ride for the default postCreate action | |
73 | // This establishes the filtering selects and the like. | |
74 | ||
75 | // Initialize the list of items in the drop down by creating data store with items like: | |
76 | // {value: 1, name: "xx-small", label: "<font size=1>xx-small</font-size>" } | |
77 | this.select.set("store", new MemoryStore({ | |
78 | idProperty: "value", | |
79 | data: array.map(this.values, function(value){ | |
80 | var name = this.strings[value] || value; | |
81 | return { | |
82 | label: this.getLabel(value, name), | |
83 | name: name, | |
84 | value: value | |
85 | }; | |
86 | }, this) | |
87 | })); | |
88 | ||
89 | this.select.set("value", "", false); | |
90 | this.disabled = this.select.get("disabled"); | |
91 | }, | |
92 | ||
93 | _setValueAttr: function(value, priorityChange){ | |
94 | // summary: | |
95 | // Over-ride for the default action of setting the | |
96 | // widget value, maps the input to known values | |
97 | // value: Object|String | |
98 | // The value to set in the select. | |
99 | // priorityChange: | |
100 | // Optional parameter used to tell the select whether or not to fire | |
101 | // onChange event. | |
102 | ||
103 | // if the value is not a permitted value, just set empty string to prevent showing the warning icon | |
104 | priorityChange = priorityChange !== false; | |
105 | this.select.set('value', array.indexOf(this.values,value) < 0 ? "" : value, priorityChange); | |
106 | if(!priorityChange){ | |
107 | // Clear the last state in case of updateState calls. Ref: #10466 | |
108 | this.select._lastValueReported=null; | |
109 | } | |
110 | }, | |
111 | ||
112 | _getValueAttr: function(){ | |
113 | // summary: | |
114 | // Allow retrieving the value from the composite select on | |
115 | // call to button.get("value"); | |
116 | return this.select.get('value'); | |
117 | }, | |
118 | ||
119 | focus: function(){ | |
120 | // summary: | |
121 | // Over-ride for focus control of this widget. Delegates focus down to the | |
122 | // filtering select. | |
123 | this.select.focus(); | |
124 | }, | |
125 | ||
126 | _setDisabledAttr: function(value){ | |
127 | // summary: | |
128 | // Over-ride for the button's 'disabled' attribute so that it can be | |
129 | // disabled programmatically. | |
130 | ||
131 | // Save off ths disabled state so the get retrieves it correctly | |
132 | //without needing to have a function proxy it. | |
133 | this.disabled = value; | |
134 | this.select.set("disabled", value); | |
135 | } | |
136 | }); | |
137 | ||
138 | ||
139 | var _FontNameDropDown = declare("dijit._editor.plugins._FontNameDropDown", _FontDropDown, { | |
140 | // summary: | |
141 | // Dropdown to select a font; goes in editor toolbar. | |
142 | ||
143 | // generic: [const] Boolean | |
144 | // Use generic (web standard) font names | |
145 | generic: false, | |
146 | ||
147 | // command: [public] String | |
148 | // The editor 'command' implemented by this plugin. | |
149 | command: "fontName", | |
150 | ||
151 | postMixInProperties: function(){ | |
152 | // summary: | |
153 | // Over-ride for the default posr mixin control | |
154 | if(!this.values){ | |
155 | this.values = this.generic ? | |
156 | ["serif", "sans-serif", "monospace", "cursive", "fantasy"] : // CSS font-family generics | |
157 | ["Arial", "Times New Roman", "Comic Sans MS", "Courier New"]; | |
158 | } | |
159 | this.inherited(arguments); | |
160 | }, | |
161 | ||
162 | getLabel: function(value, name){ | |
163 | // summary: | |
164 | // Function used to generate the labels of the format dropdown | |
165 | // will return a formatted, or plain label based on the value | |
166 | // of the plainText option. | |
167 | // value: String | |
168 | // The 'insert value' associated with a name | |
169 | // name: String | |
170 | // The text name of the value | |
171 | if(this.plainText){ | |
172 | return name; | |
173 | }else{ | |
174 | return "<div style='font-family: "+value+"'>" + name + "</div>"; | |
175 | } | |
176 | }, | |
177 | ||
178 | _setValueAttr: function(value, priorityChange){ | |
179 | // summary: | |
180 | // Over-ride for the default action of setting the | |
181 | // widget value, maps the input to known values | |
182 | ||
183 | priorityChange = priorityChange !== false; | |
184 | if(this.generic){ | |
185 | var map = { | |
186 | "Arial": "sans-serif", | |
187 | "Helvetica": "sans-serif", | |
188 | "Myriad": "sans-serif", | |
189 | "Times": "serif", | |
190 | "Times New Roman": "serif", | |
191 | "Comic Sans MS": "cursive", | |
192 | "Apple Chancery": "cursive", | |
193 | "Courier": "monospace", | |
194 | "Courier New": "monospace", | |
195 | "Papyrus": "fantasy", | |
196 | "Estrangelo Edessa": "cursive", // Windows 7 | |
197 | "Gabriola": "fantasy" // Windows 7 | |
198 | }; | |
199 | value = map[value] || value; | |
200 | } | |
201 | this.inherited(arguments, [value, priorityChange]); | |
202 | } | |
203 | }); | |
204 | ||
205 | var _FontSizeDropDown = declare("dijit._editor.plugins._FontSizeDropDown", _FontDropDown, { | |
206 | // summary: | |
207 | // Dropdown to select a font size; goes in editor toolbar. | |
208 | ||
209 | // command: [public] String | |
210 | // The editor 'command' implemented by this plugin. | |
211 | command: "fontSize", | |
212 | ||
213 | // values: [public] Number[] | |
214 | // The HTML font size values supported by this plugin | |
215 | values: [1,2,3,4,5,6,7], // sizes according to the old HTML FONT SIZE | |
216 | ||
217 | getLabel: function(value, name){ | |
218 | // summary: | |
219 | // Function used to generate the labels of the format dropdown | |
220 | // will return a formatted, or plain label based on the value | |
221 | // of the plainText option. | |
222 | // We're stuck using the deprecated FONT tag to correspond | |
223 | // with the size measurements used by the editor | |
224 | // value: String | |
225 | // The 'insert value' associated with a name | |
226 | // name: String | |
227 | // The text name of the value | |
228 | if(this.plainText){ | |
229 | return name; | |
230 | }else{ | |
231 | return "<font size=" + value + "'>" + name + "</font>"; | |
232 | } | |
233 | }, | |
234 | ||
235 | _setValueAttr: function(value, priorityChange){ | |
236 | // summary: | |
237 | // Over-ride for the default action of setting the | |
238 | // widget value, maps the input to known values | |
239 | priorityChange = priorityChange !== false; | |
240 | if(value.indexOf && value.indexOf("px") != -1){ | |
241 | var pixels = parseInt(value, 10); | |
242 | value = {10:1, 13:2, 16:3, 18:4, 24:5, 32:6, 48:7}[pixels] || value; | |
243 | } | |
244 | ||
245 | this.inherited(arguments, [value, priorityChange]); | |
246 | } | |
247 | }); | |
248 | ||
249 | ||
250 | var _FormatBlockDropDown = declare("dijit._editor.plugins._FormatBlockDropDown", _FontDropDown, { | |
251 | // summary: | |
252 | // Dropdown to select a format (like paragraph or heading); goes in editor toolbar. | |
253 | ||
254 | // command: [public] String | |
255 | // The editor 'command' implemented by this plugin. | |
256 | command: "formatBlock", | |
257 | ||
258 | // values: [public] Array | |
259 | // The HTML format tags supported by this plugin | |
260 | values: ["noFormat", "p", "h1", "h2", "h3", "pre"], | |
261 | ||
262 | postCreate: function(){ | |
263 | // Init and set the default value to no formatting. Update state will adjust it | |
264 | // as needed. | |
265 | this.inherited(arguments); | |
266 | this.set("value", "noFormat", false); | |
267 | }, | |
268 | ||
269 | getLabel: function(value, name){ | |
270 | // summary: | |
271 | // Function used to generate the labels of the format dropdown | |
272 | // will return a formatted, or plain label based on the value | |
273 | // of the plainText option. | |
274 | // value: String | |
275 | // The 'insert value' associated with a name | |
276 | // name: String | |
277 | // The text name of the value | |
278 | if(this.plainText || value == "noFormat"){ | |
279 | return name; | |
280 | }else{ | |
281 | return "<" + value + ">" + name + "</" + value + ">"; | |
282 | } | |
283 | }, | |
284 | ||
285 | _execCommand: function(editor, command, choice){ | |
286 | // summary: | |
287 | // Over-ride for default exec-command label. | |
288 | // Allows us to treat 'none' as special. | |
289 | if(choice === "noFormat"){ | |
290 | var start; | |
291 | var end; | |
292 | var sel = rangeapi.getSelection(editor.window); | |
293 | if(sel && sel.rangeCount > 0){ | |
294 | var range = sel.getRangeAt(0); | |
295 | var node, tag; | |
296 | if(range){ | |
297 | start = range.startContainer; | |
298 | end = range.endContainer; | |
299 | ||
300 | // find containing nodes of start/end. | |
301 | while(start && start !== editor.editNode && | |
302 | start !== editor.document.body && | |
303 | start.nodeType !== 1){ | |
304 | start = start.parentNode; | |
305 | } | |
306 | ||
307 | while(end && end !== editor.editNode && | |
308 | end !== editor.document.body && | |
309 | end.nodeType !== 1){ | |
310 | end = end.parentNode; | |
311 | } | |
312 | ||
313 | var processChildren = lang.hitch(this, function(node, ary){ | |
314 | if(node.childNodes && node.childNodes.length){ | |
315 | var i; | |
316 | for(i = 0; i < node.childNodes.length; i++){ | |
317 | var c = node.childNodes[i]; | |
318 | if(c.nodeType == 1){ | |
319 | if(editor._sCall("inSelection", [c])){ | |
320 | var tag = c.tagName? c.tagName.toLowerCase(): ""; | |
321 | if(array.indexOf(this.values, tag) !== -1){ | |
322 | ary.push(c); | |
323 | } | |
324 | processChildren(c, ary); | |
325 | } | |
326 | } | |
327 | } | |
328 | } | |
329 | }); | |
330 | ||
331 | var unformatNodes = lang.hitch(this, function(nodes){ | |
332 | // summary: | |
333 | // Internal function to clear format nodes. | |
334 | // nodes: | |
335 | // The array of nodes to strip formatting from. | |
336 | if(nodes && nodes.length){ | |
337 | editor.beginEditing(); | |
338 | while(nodes.length){ | |
339 | this._removeFormat(editor, nodes.pop()); | |
340 | } | |
341 | editor.endEditing(); | |
342 | } | |
343 | }); | |
344 | ||
345 | var clearNodes = []; | |
346 | if(start == end){ | |
347 | //Contained within the same block, may be collapsed, but who cares, see if we | |
348 | // have a block element to remove. | |
349 | var block; | |
350 | node = start; | |
351 | while(node && node !== editor.editNode && node !== editor.document.body){ | |
352 | if(node.nodeType == 1){ | |
353 | tag = node.tagName? node.tagName.toLowerCase(): ""; | |
354 | if(array.indexOf(this.values, tag) !== -1){ | |
355 | block = node; | |
356 | break; | |
357 | } | |
358 | } | |
359 | node = node.parentNode; | |
360 | } | |
361 | ||
362 | //Also look for all child nodes in the selection that may need to be | |
363 | //cleared of formatting | |
364 | processChildren(start, clearNodes); | |
365 | if(block){ clearNodes = [block].concat(clearNodes); } | |
366 | unformatNodes(clearNodes); | |
367 | }else{ | |
368 | // Probably a multi select, so we have to process it. Whee. | |
369 | node = start; | |
370 | while(editor._sCall("inSelection", [node])){ | |
371 | if(node.nodeType == 1){ | |
372 | tag = node.tagName? node.tagName.toLowerCase(): ""; | |
373 | if(array.indexOf(this.values, tag) !== -1){ | |
374 | clearNodes.push(node); | |
375 | } | |
376 | processChildren(node,clearNodes); | |
377 | } | |
378 | node = node.nextSibling; | |
379 | } | |
380 | unformatNodes(clearNodes); | |
381 | } | |
382 | editor.onDisplayChanged(); | |
383 | } | |
384 | } | |
385 | }else{ | |
386 | editor.execCommand(command, choice); | |
387 | } | |
388 | }, | |
389 | ||
390 | _removeFormat: function(editor, node){ | |
391 | // summary: | |
392 | // function to remove the block format node. | |
393 | // node: | |
394 | // The block format node to remove (and leave the contents behind) | |
395 | if(editor.customUndo){ | |
396 | // So of course IE doesn't work right with paste-overs. | |
397 | // We have to do this manually, which is okay since IE already uses | |
398 | // customUndo and we turned it on for WebKit. WebKit pasted funny, | |
399 | // so couldn't use the execCommand approach | |
400 | while(node.firstChild){ | |
401 | domConstruct.place(node.firstChild, node, "before"); | |
402 | } | |
403 | node.parentNode.removeChild(node); | |
404 | }else{ | |
405 | // Everyone else works fine this way, a paste-over and is native | |
406 | // undo friendly. | |
407 | editor._sCall("selectElementChildren", [node]) | |
408 | var html = editor._sCall("getSelectedHtml", []) | |
409 | editor._sCall("selectElement", [node]) | |
410 | editor.execCommand("inserthtml", html||""); | |
411 | } | |
412 | } | |
413 | }); | |
414 | ||
415 | // TODO: for 2.0, split into FontChoice plugin into three separate classes, | |
416 | // one for each command (and change registry below) | |
417 | var FontChoice = declare("dijit._editor.plugins.FontChoice", _Plugin,{ | |
418 | // summary: | |
419 | // This plugin provides three drop downs for setting style in the editor | |
420 | // (font, font size, and format block), as controlled by command. | |
421 | // | |
422 | // description: | |
423 | // The commands provided by this plugin are: | |
424 | // | |
425 | // - fontName: Provides a drop down to select from a list of font names | |
426 | // - fontSize: Provides a drop down to select from a list of font sizes | |
427 | // - formatBlock: Provides a drop down to select from a list of block styles | |
428 | // which can easily be added to an editor by including one or more of the above commands | |
429 | // in the `plugins` attribute as follows: | |
430 | // | |
431 | // | plugins="['fontName','fontSize',...]" | |
432 | // | |
433 | // It is possible to override the default dropdown list by providing an Array for the `custom` property when | |
434 | // instantiating this plugin, e.g. | |
435 | // | |
436 | // | plugins="[{name:'dijit._editor.plugins.FontChoice', command:'fontName', values:['Verdana','Myriad','Garamond']},...]" | |
437 | // | |
438 | // Alternatively, for `fontName` only, `generic:true` may be specified to provide a dropdown with | |
439 | // [CSS generic font families](http://www.w3.org/TR/REC-CSS2/fonts.html#generic-font-families). | |
440 | // | |
441 | // Note that the editor is often unable to properly handle font styling information defined outside | |
442 | // the context of the current editor instance, such as pre-populated HTML. | |
443 | ||
444 | // useDefaultCommand: [protected] Boolean | |
445 | // Override _Plugin.useDefaultCommand... | |
446 | // processing is handled by this plugin, not by dijit/Editor. | |
447 | useDefaultCommand: false, | |
448 | ||
449 | _initButton: function(){ | |
450 | // summary: | |
451 | // Overrides _Plugin._initButton(), to initialize the FilteringSelect+label in toolbar, | |
452 | // rather than a simple button. | |
453 | // tags: | |
454 | // protected | |
455 | ||
456 | // Create the widget to go into the toolbar (the so-called "button") | |
457 | var clazz = { | |
458 | fontName: _FontNameDropDown, | |
459 | fontSize: _FontSizeDropDown, | |
460 | formatBlock: _FormatBlockDropDown | |
461 | }[this.command], | |
462 | params = this.params; | |
463 | ||
464 | // For back-compat reasons support setting custom values via "custom" parameter | |
465 | // rather than "values" parameter. Remove in 2.0. | |
466 | if(this.params.custom){ | |
467 | params.values = this.params.custom; | |
468 | } | |
469 | ||
470 | var editor = this.editor; | |
471 | this.button = new clazz(lang.delegate({dir: editor.dir, lang: editor.lang}, params)); | |
472 | ||
473 | // Reflect changes to the drop down in the editor | |
474 | this.connect(this.button.select, "onChange", function(choice){ | |
475 | // User invoked change, since all internal updates set priorityChange to false and will | |
476 | // not trigger an onChange event. | |
477 | this.editor.focus(); | |
478 | ||
479 | if(this.command == "fontName" && choice.indexOf(" ") != -1){ choice = "'" + choice + "'"; } | |
480 | ||
481 | // Invoke, the editor already normalizes commands called through its | |
482 | // execCommand. | |
483 | if(this.button._execCommand){ | |
484 | this.button._execCommand(this.editor, this.command, choice); | |
485 | }else{ | |
486 | this.editor.execCommand(this.command, choice); | |
487 | } | |
488 | }); | |
489 | }, | |
490 | ||
491 | updateState: function(){ | |
492 | // summary: | |
493 | // Overrides _Plugin.updateState(). This controls updating the menu | |
494 | // options to the right values on state changes in the document (that trigger a | |
495 | // test of the actions.) | |
496 | // It set value of drop down in toolbar to reflect font/font size/format block | |
497 | // of text at current caret position. | |
498 | // tags: | |
499 | // protected | |
500 | var _e = this.editor; | |
501 | var _c = this.command; | |
502 | if(!_e || !_e.isLoaded || !_c.length){ return; } | |
503 | ||
504 | if(this.button){ | |
505 | var disabled = this.get("disabled"); | |
506 | this.button.set("disabled", disabled); | |
507 | if(disabled){ return; } | |
508 | var value; | |
509 | try{ | |
510 | value = _e.queryCommandValue(_c) || ""; | |
511 | }catch(e){ | |
512 | //Firefox may throw error above if the editor is just loaded, ignore it | |
513 | value = ""; | |
514 | } | |
515 | ||
516 | // strip off single quotes, if any | |
517 | var quoted = lang.isString(value) && value.match(/'([^']*)'/); | |
518 | if(quoted){ value = quoted[1]; } | |
519 | ||
520 | if(_c === "formatBlock"){ | |
521 | if(!value || value == "p"){ | |
522 | // Some browsers (WebKit) doesn't actually get the tag info right. | |
523 | // and IE returns paragraph when in a DIV!, so incorrect a lot, | |
524 | // so we have double-check it. | |
525 | value = null; | |
526 | var elem; | |
527 | // Try to find the current element where the caret is. | |
528 | var sel = rangeapi.getSelection(this.editor.window); | |
529 | if(sel && sel.rangeCount > 0){ | |
530 | var range = sel.getRangeAt(0); | |
531 | if(range){ | |
532 | elem = range.endContainer; | |
533 | } | |
534 | } | |
535 | ||
536 | // Okay, now see if we can find one of the formatting types we're in. | |
537 | while(elem && elem !== _e.editNode && elem !== _e.document){ | |
538 | var tg = elem.tagName?elem.tagName.toLowerCase():""; | |
539 | if(tg && array.indexOf(this.button.values, tg) > -1){ | |
540 | value = tg; | |
541 | break; | |
542 | } | |
543 | elem = elem.parentNode; | |
544 | } | |
545 | if(!value){ | |
546 | // Still no value, so lets select 'none'. | |
547 | value = "noFormat"; | |
548 | } | |
549 | }else{ | |
550 | // Check that the block format is one allowed, if not, | |
551 | // null it so that it gets set to empty. | |
552 | if(array.indexOf(this.button.values, value) < 0){ | |
553 | value = "noFormat"; | |
554 | } | |
555 | } | |
556 | } | |
557 | if(value !== this.button.get("value")){ | |
558 | // Set the value, but denote it is not a priority change, so no | |
559 | // onchange fires. | |
560 | this.button.set('value', value, false); | |
561 | } | |
562 | } | |
563 | } | |
564 | }); | |
565 | ||
566 | // Register these plugins | |
567 | array.forEach(["fontName", "fontSize", "formatBlock"], function(name){ | |
568 | _Plugin.registry[name] = function(args){ | |
569 | return new FontChoice({ | |
570 | command: name, | |
571 | plainText: args.plainText | |
572 | }); | |
573 | }; | |
574 | }); | |
575 | ||
576 | // Make all classes available through AMD, and return main class | |
577 | FontChoice._FontDropDown = _FontDropDown; | |
578 | FontChoice._FontNameDropDown = _FontNameDropDown; | |
579 | FontChoice._FontSizeDropDown = _FontSizeDropDown; | |
580 | FontChoice._FormatBlockDropDown = _FormatBlockDropDown; | |
581 | return FontChoice; | |
582 | ||
583 | }); |