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