www

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | Submodules | README | LICENSE

styled-textbox.xml (21911B)


      1 <?xml version="1.0"?>
      2 <!--
      3     ***** BEGIN LICENSE BLOCK *****
      4     
      5     Copyright © 2009 Center for History and New Media
      6                      George Mason University, Fairfax, Virginia, USA
      7                      http://zotero.org
      8     
      9     This file is part of Zotero.
     10     
     11     Zotero is free software: you can redistribute it and/or modify
     12     it under the terms of the GNU Affero General Public License as published by
     13     the Free Software Foundation, either version 3 of the License, or
     14     (at your option) any later version.
     15     
     16     Zotero is distributed in the hope that it will be useful,
     17     but WITHOUT ANY WARRANTY; without even the implied warranty of
     18     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     19     GNU Affero General Public License for more details.
     20     
     21     You should have received a copy of the GNU Affero General Public License
     22     along with Zotero.  If not, see <http://www.gnu.org/licenses/>.
     23     
     24     ***** END LICENSE BLOCK *****
     25 -->
     26 
     27 <!DOCTYPE bindings SYSTEM "chrome://zotero/locale/zotero.dtd">
     28 <bindings xmlns="http://www.mozilla.org/xbl"
     29           xmlns:html="http://www.w3.org/1999/xhtml"
     30 		  xmlns:xbl="http://www.mozilla.org/xbl"
     31 		  xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
     32 	<binding id="styled-textbox">
     33 		<implementation>
     34 			<field name="_editable"/>
     35 			<field name="_mode"/>
     36 			<field name="_format"/>
     37 			<field name="_changed"/>
     38 			<field name="_loadHandler"/>
     39 			<field name="_commandString"/>
     40 			<field name="_eventHandler"/>
     41 			<field name="_editor"/>
     42 			<field name="_value"/>
     43 			<field name="_timer"/>
     44 			<field name="_focus"/>
     45 			<field name="_constructed"/>
     46 			<field name="_loadOnConstruct"/>
     47 			
     48 			<constructor><![CDATA[
     49 				this.mode = this.getAttribute('mode');
     50 				
     51 				this._iframe = document.getAnonymousElementByAttribute(this, "anonid", "rt-view");
     52 				
     53 				// Atomic units, HTML -> RTF (cleanup)
     54 				//[/<\/p>(?!\s*$)/g, "\\par{}"],
     55 				//[/ /g, "&nbsp;"],
     56 				//[/\u00A0/g, " "],
     57 				this._htmlRTFmap = [
     58 					[/<br \/>/g, "\x0B"],
     59 					[/<span class=\"tab\">&nbsp;<\/span>/g, "\\tab{}"],
     60 					[/&lsquo;/g, "‘"],
     61 					[/&rsquo;/g, "’"],
     62 					[/&ldquo;/g, "“"],
     63 					[/&rdquo;/g, "”"],
     64 					[/&nbsp;/g, "\u00A0"],
     65 					[/"(\w)/g, "“$1"],
     66 					[/([\w,.?!])"/g, "$1”"],
     67 					[/<p>/g, ""],
     68 					[/<\/?div[^>]*>/g, ""]
     69 				];
     70 				
     71 				// Atomic units, RTF -> HTML (cleanup)
     72 				this._rtfHTMLmap = [
     73 					[/\\uc0\{?\\u([0-9]+)\}?(?:{}| )?/g, function(wholeStr, aCode) { return String.fromCharCode(aCode) }],
     74 					[/\\tab(?:\{\}| )/g, '<span class="tab">&nbsp;</span>'],
     75 					[/(?:\\par{}|\\\r?\n)/g, "</p><p>"]
     76 				];
     77 				
     78 				this.prepare = function() {
     79 					// DEBUG: Does this actually happen?
     80 					if (this.prepared) return;
     81 					
     82 					// Tag data
     83 					var _rexData = [
     84 						[
     85 							[
     86 								["<span +style=\"font-variant: *small-caps;\">"],
     87 								["{\\scaps ", "{\\scaps{}"]
     88 							],
     89 							[
     90 								["<\/span>"],
     91 								["}"]
     92 							]
     93 						],
     94 						[
     95 							[
     96 								["<span +style=\"text-decoration: *underline;\">"],
     97 								["{\\ul{}", "{\\ul "]
     98 							],
     99 							[
    100 								["<\/span>"],
    101 								["}"]
    102 							]
    103 						],
    104 						[
    105 							[
    106 								["<sup>"],
    107 								["\\super ", "\\super{}"]
    108 							],
    109 							[
    110 								["</sup>"],
    111 								["\\nosupersub{}", "\\nosupersub "]
    112 							]
    113 						],
    114 						[
    115 							[
    116 								["<sub>"],
    117 								["\\sub ", "\\sub{}"]
    118 							],
    119 							[
    120 								["</sub>"],
    121 								["\\nosupersub{}", "\\nosupersub "]
    122 							]
    123 						],
    124 						[
    125 							[
    126 								["<em>"],
    127 								["{\\i{}", "{\\i "]
    128 							],
    129 							[
    130 								["</em>"],
    131 								["}"]
    132 							]
    133 						],
    134 						[
    135 							[
    136 								["<i>"],
    137 								["{\\i{}", "{\\i "]
    138 							],
    139 							[
    140 								["</i>"],
    141 								["}"]
    142 							]
    143 						],
    144 						[
    145 							[
    146 								["<b>"],
    147 								["{\\b{}", "{\\b "]
    148 							],
    149 							[
    150 								["</b>"],
    151 								["}"]
    152 							]
    153 						],
    154 						[
    155 							[
    156 								["<strong>"],
    157 								["{\\b{}", "{\\b "]
    158 							],
    159 							[
    160 								["</strong>"],
    161 								["}"]
    162 							]
    163 						],
    164 						[
    165 							[
    166 								["<span +style=\"font-variant: *normal;\">"],
    167 								["{\\scaps0{}", "{\\scaps0 "]
    168 							],
    169 							[
    170 								["</span>"],
    171 								["}"]
    172 							]
    173 						],
    174 						[
    175 							[
    176 								["<span +style=\"font-style: *normal;\">"],
    177 								["{\\i0{}", "{\\i0 "]
    178 							],
    179 							[
    180 								["</span>"],
    181 								["}"]
    182 							]
    183 						],
    184 						[
    185 							[
    186 								["<span +style=\"font-weight: *normal;\">"],
    187 								["{\\b0{}", "{\\b0 "]
    188 							],
    189 							[
    190 								["</span>"],
    191 								["}"]
    192 							]
    193 						]
    194 					];
    195 					
    196 					function longestFirst(a, b) {
    197 						if (a.length < b.length) {
    198 							return 1;
    199 						} else if (a.length > b.length) {
    200 							return -1;
    201 						} else {
    202 							return 0;
    203 						}
    204 					}
    205 					
    206 					function normalizeRegExpString(str) {
    207 						if (!str) return str;
    208 						return str.replace(/\s+/g, " ")
    209 									.replace(/(?:[\+]|\s[\*])/g, "")
    210 									.replace(/[\']/g, '\"')
    211 									.replace(/:\s/g, ":");
    212 					}
    213 
    214 					this.normalizeRegExpString = normalizeRegExpString;
    215 					
    216 					function composeRex(rexes, noGlobal) {
    217 						var lst = [];
    218 						for (var rex in rexes) {
    219 							lst.push(rex);
    220 						}
    221 						lst.sort(longestFirst);
    222 						var rexStr = "(?:" + lst.join("|") + ")";
    223 						return new RegExp(rexStr, "g");
    224 					}
    225 					
    226 					// Create splitting regexps
    227 					function splitRexMaker(segment) {
    228 						var rexes = {};
    229 						for (var i=0,ilen=_rexData.length; i < ilen; i++) {
    230 							for (var j=0,jlen=_rexData[i].length; j < jlen; j++) {
    231 								for (var k=0,klen=_rexData[i][j][segment].length; k < klen; k++) {
    232 									rexes[_rexData[i][j][segment][k].replace("\\", "\\\\")] = true;
    233 								}
    234 							}
    235 						}
    236 						var ret = composeRex(rexes, true);
    237 						return ret;
    238 					}
    239 					this.rtfHTMLsplitRex = splitRexMaker(1);
    240 					this.htmlRTFsplitRex = splitRexMaker(0);
    241 					
    242 					// Create open-tag sniffing regexp
    243 					function openSniffRexMaker(segment) {
    244 						var rexes = {};
    245 						for (var i=0,ilen=_rexData.length; i < ilen; i++) {
    246 							for (var j=0,jlen=_rexData[i][0][segment].length; j < jlen; j++) {
    247 								rexes[_rexData[i][0][segment][j].replace("\\", "\\\\")] = true;
    248 							}
    249 						}
    250 						return composeRex(rexes);
    251 					}
    252 					this.rtfHTMLopenSniffRex = openSniffRexMaker(1);
    253 					this.htmlRTFopenSniffRex = openSniffRexMaker(0);
    254 					
    255 					// Create open-tag remapper
    256 					function openTagRemapMaker(segment) {
    257 						var ret = {};
    258 						for (var i=0,ilen=_rexData.length; i < ilen; i++) {
    259 							var primaryVal = normalizeRegExpString(_rexData[i][0][segment][0]);
    260 							for (var j=0,jlen=_rexData[i][0][segment].length; j < jlen; j++) {
    261 								var key = normalizeRegExpString(_rexData[i][0][segment][j]);
    262 								ret[key] = primaryVal;
    263 							}
    264 						}
    265 						return ret;
    266 					}
    267 
    268 					this.rtfHTMLopenTagRemap = openTagRemapMaker(1);
    269 					this.htmlRTFopenTagRemap = openTagRemapMaker(0);
    270 					
    271 					// Create open-tag-keyed close-tag sniffing regexps
    272 					function closeTagRexMaker(segment) {
    273 						var ret = {};
    274 						var rexes = {};
    275 						for (var i=0,ilen=_rexData.length; i < ilen; i++) {
    276 							var primaryVal = _rexData[i][0][segment][0];
    277 							for (var j=0,jlen=_rexData[i][1][segment].length; j < jlen; j++) {
    278 								rexes[_rexData[i][1][segment][j]] = true;
    279 							}
    280 							ret[primaryVal] = composeRex(rexes);
    281 						}
    282 						return ret;
    283 					}
    284 					this.rtfHTMLcloseTagRex = closeTagRexMaker(1);
    285 					this.htmlRTFcloseTagRex = closeTagRexMaker(0);
    286 					
    287 					// Create open-tag-keyed open/close tag registry
    288 					function tagRegistryMaker(segment) {
    289 						var antisegment = 1;
    290 						if (segment == 1) {
    291 							antisegment = 0;
    292 						}
    293 						var ret = {};
    294 						for (var i=0,ilen=_rexData.length; i < ilen; i++) {
    295 							var primaryVal = normalizeRegExpString(_rexData[i][0][segment][0]);
    296 							ret[primaryVal] = {
    297 								open: normalizeRegExpString(_rexData[i][0][antisegment][0]),
    298 								close: _rexData[i][1][antisegment][0]
    299 							}
    300 						}
    301 						return ret;
    302 					}
    303 					
    304 					this.rtfHTMLtagRegistry = tagRegistryMaker(1);
    305 					this.htmlRTFtagRegistry = tagRegistryMaker(0);
    306 
    307                     this.prepared = true;
    308 				}
    309 				this.prepare();
    310 				
    311 				this.getSplit = function(mode, txt) {
    312 					if (!txt) return [];
    313 					var splt = txt.split(this[mode + "splitRex"]);
    314 					var mtch = txt.match(this[mode + "splitRex"]);
    315 					var lst = [splt[0]];
    316 					for (var i=1,ilen=splt.length; i < ilen; i++) {
    317 						lst.push(mtch[i-1]);
    318 						lst.push(splt[i]);
    319 					}
    320 					return lst;
    321 				}
    322 				
    323 				this.getOpenTag = function(mode, str) {
    324 					var m = str.match(this[mode + "openSniffRex"]);
    325 					if (m) {
    326 						m = this[mode + "openTagRemap"][this.normalizeRegExpString(m[0])];
    327 					}
    328 					return m;
    329 				}
    330 				
    331 				this.convert = function(mode, txt) {
    332 					var lst = this.getSplit(mode, txt);
    333 					var sdepth = 0;
    334 					var depth = 0;
    335 					for (var i=1,ilen=lst.length; i < ilen; i += 2) {
    336 						var openTag = this.getOpenTag(mode, lst[i]);
    337 						if (openTag) {
    338 							sdepth++;
    339 							depth = sdepth;
    340 							for (var j=(i+2),jlen=lst.length; j < jlen; j += 2) {
    341 								var closeTag = !this.getOpenTag(mode, lst[j]);
    342 								if (closeTag) {
    343 									if (depth === sdepth && lst[j].match(this[mode + "closeTagRex"][openTag])) {
    344 										lst[i] = this[mode + "tagRegistry"][openTag].open;
    345 										lst[j] = this[mode + "tagRegistry"][openTag].close;
    346 										break;
    347 									}
    348 									depth--;
    349 								} else {
    350 									depth++;
    351 								}
    352 							}
    353 						} else {
    354 							sdepth--;
    355 						}
    356 					}
    357 					return lst.join("");
    358 				}
    359 				
    360 				this.htmlToRTF = function(txt) {
    361 					txt = this.convert("htmlRTF", txt);
    362 					for (var i=0,ilen=this._htmlRTFmap.length; i < ilen; i++) {
    363 						var entry = this._htmlRTFmap[i];
    364 						txt = txt.replace(entry[0], entry[1]);
    365 					}
    366 					txt = Zotero.Utilities.unescapeHTML(txt);
    367 					txt = txt.replace(/[\x7F-\uFFFF]/g, function(aChar) { return "\\uc0\\u"+aChar.charCodeAt(0).toString()+"{}"});
    368 					return txt.trim();
    369 				}
    370 				
    371 				this.rtfToHTML = function(txt) {
    372 					for (var i=0,ilen=this._rtfHTMLmap.length; i < ilen; i++) {
    373 						var entry = this._rtfHTMLmap[i];
    374 						txt = txt.replace(entry[0], entry[1]);
    375 					}
    376 					txt = this.convert("rtfHTML", txt);
    377 					return txt.trim();
    378 				}
    379 
    380 				this._constructed = true;
    381 				
    382 				// Don't load if a value hasn't yet been set
    383 				if (this._loadOnConstruct) {
    384 					this._load();
    385 				}
    386 			]]></constructor>
    387 			
    388 			<property name="mode">
    389 				<getter><![CDATA[
    390 					if (!this._mode) {
    391 						throw ("mode is not defined in styled-textbox.xml");
    392 					}
    393 					return this._mode;
    394 				]]></getter>
    395 				<setter><![CDATA[
    396 					Zotero.debug("Setting mode to " + val);
    397 					switch (val) {
    398 						case 'note':
    399 							this._eventHandler = function (event) {
    400 								// Necessary in Fx32+
    401 								if (event.wrappedJSObject) {
    402 									event = event.wrappedJSObject;
    403 								}
    404 								
    405 								var commandEvent = false;
    406 								
    407 								if (Zotero.Prefs.get('debugNoteEvents')) {
    408 									Zotero.debug(event.type);
    409 									Zotero.debug(event.which);
    410 								}
    411 								switch (event.type) {
    412 									case 'keydown':
    413 										// Handle forward-delete, which doesn't register as a keypress
    414 										// when a selection is cleared
    415 										if (event.which == event.DOM_VK_DELETE) {
    416 											this._changed = true;
    417 											commandEvent = true;
    418 										}
    419 										break;
    420 									
    421 									case 'keypress':
    422 										// Ignore keypresses that don't change
    423 										// any text
    424 										//Zotero.debug(event.which);
    425 										if (!event.which &&
    426 												event.keyCode != event.DOM_VK_DELETE &&
    427 												event.keyCode != event.DOM_VK_BACK_SPACE) {
    428 											//Zotero.debug("Not a char");
    429 											return;
    430 										}
    431 										this._changed = true;
    432 										commandEvent = true;
    433 										break;
    434 									
    435 									// 'change' includes text added via drag-and-drop
    436 									case 'change':
    437 									case 'undo':
    438 									case 'redo':
    439 										this._changed = true;
    440 										commandEvent = true;
    441 										break;
    442 									
    443 									case 'ZoteroLinkClick':
    444 										var zp = typeof ZoteroPane != 'undefined'
    445 											? ZoteroPane
    446 											: window.opener.ZoteroPane;
    447 										zp.loadURI(event.value);
    448 										break;
    449 									
    450 									default:
    451 										return;
    452 								}
    453 								
    454 								// Trigger command on change
    455 								if (commandEvent && this.timeout) {
    456 									if (this._timer) {
    457 										clearTimeout(this._timer);
    458 									}
    459 									
    460 									this._timer = setTimeout(function () {
    461 										var attr = this.getAttribute('oncommand');
    462 										attr = attr.replace('this', 'thisObj');
    463 										var func = new Function('thisObj', 'event', attr);
    464 										func(this, event);
    465 									}.bind(this), this.timeout);
    466 								}
    467 								
    468 								return true;
    469 							}.bind(this);
    470 							break;
    471 						
    472 						case 'integration':
    473 							break;
    474 							
    475 						default:
    476 							throw ("Invalid mode '" + val + "' in styled-textbox.xml");
    477 					}
    478 					return this._mode = val;
    479 				]]></setter>
    480 			</property>
    481 			
    482 			<!-- Sets or returns formatting (currently, HTML or Integration) of rich text box -->
    483 			<property name="initialized">
    484 				<getter><![CDATA[
    485 					return !!this._editor;
    486 				]]></getter>
    487 			</property>	
    488 			
    489 			<!-- Sets or returns formatting (currently, HTML or Integration) of rich text box -->
    490 			<property name="format">
    491 				<getter><![CDATA[
    492 					return this._format;
    493 				]]></getter>
    494 				<setter><![CDATA[
    495 					return this._format = val;
    496 				]]></setter>
    497 			</property>
    498 			
    499 			<!-- Sets or returns contents of rich text box -->
    500 			<property name="value">
    501 				<getter><![CDATA[
    502 					if (!this._editor) {
    503 						return null;
    504 					}
    505 					
    506 					var output = this._editor.getContent().trim();
    507 					
    508 					if(this._format == "RTF") {
    509 						// strip divs
    510 						if(output.substr(0, 5) == "<div>" && output.substr(-6) == "</div>") {
    511 							output = output.slice(0, output.length-6).slice(5).trim();
    512 						}
    513 						output = this.htmlToRTF(output)
    514 					}
    515 					
    516 					return output;
    517 				]]></getter>
    518 				<setter><![CDATA[
    519 					if (self._timer) {
    520 						clearTimeout(self._timer);
    521 					}
    522 					
    523 					if(!this._editor) {
    524 						Zotero.debug('No editor yet');
    525 						
    526 						this._value = val;
    527 						if (!this._constructed) {
    528 							Zotero.debug('Styled textbox not yet constructed', 2);
    529 							this._loadOnConstruct = true;
    530 						}
    531 						else if (!this._loaded) {
    532 							this._load();
    533 						}
    534 						return ;
    535 					}
    536 					
    537 					if (this.value == val) {
    538 						Zotero.debug("Textbox value hasn't changed");
    539 						this._changed = false;
    540 						return;
    541 					}
    542 					
    543 					var html = val;
    544 					if(this._format == "RTF") {
    545 						var bodyStyle = "";
    546 						if(html.substr(0, 3) == "\\li") {
    547 							// try to show paragraph formatting
    548 							var returnIndex = html.indexOf("\r\n");
    549 							
    550 							var tags = html.substr(1, returnIndex).split("\\");
    551 							html = html.substr(returnIndex+2);
    552 							
    553 							for(var i=0; i<tags.length; i++) {
    554 								var tagName = tags[i].substr(0, 2);
    555 								var tagValue = tags[i].substring(2, tags[i].length-1);
    556 								if(tagName == "li") {
    557 									var li = parseInt(tagValue, 10);
    558 								} else if(tagName == "fi") {
    559 									var fi = parseInt(tagValue, 10);
    560 								}
    561 							}
    562 							
    563 							// don't negatively indent
    564 							if(fi < 0 && li == 0) li = -fi;
    565 							
    566 							bodyStyle = "margin-left:"+(li/20+6)+"pt;text-indent:"+(fi/20)+"pt;";
    567 						}
    568 						
    569 						html = this.rtfToHTML(html);
    570 						
    571 						html = '<div style="'+bodyStyle+'"><p>'+html+"</p></div>";
    572 					}
    573 					
    574 					Zotero.debug("Setting content to " + html);
    575 					
    576 					this._editor.setContent(html);
    577 					this._changed = false;
    578 					return val;
    579 				]]></setter>
    580 			</property>
    581 			
    582 			<property name="timeout"
    583 					onset="this.setAttribute('timeout', val); return val;"
    584 					onget="return parseInt(this.getAttribute('timeout')) || 0;"/>
    585 			
    586 			<property name="changed" onget="return this._changed;" onset="this._changed = !!val;"/>
    587 			
    588 			<method name="focus">
    589 				<body>
    590 				<![CDATA[
    591 					if (this._editor) {
    592 						this._iframe.focus();
    593 						this._editor.focus();
    594 						this._focus = false;
    595 					}
    596 					else {
    597 						this._focus = true;
    598 					}
    599 				]]>
    600 				</body>
    601 			</method>
    602 			
    603 			<method name="clearUndo">
    604 				<body>
    605 				<![CDATA[
    606 					if (this._editor) {
    607 						this._editor.undoManager.clear();
    608 						this._editor.undoManager.add();
    609 					}
    610 				]]>
    611 				</body>
    612 			</method>
    613 			
    614 			<method name="onInit">
    615 				<parameter name="callback"/>
    616 				<body><![CDATA[
    617 					if (this.initialized) {
    618 						callback(this._editor);
    619 					}
    620 					else {
    621 						if (!this._onInitCallbacks) {
    622 							this._onInitCallbacks = [];
    623 						}
    624 						this._onInitCallbacks.push(callback);
    625 					}
    626 				]]></body>
    627 			</method>
    628 			
    629 			<field name="_loaded"/>
    630 			<method name="_load">
    631 				<body>
    632 				<![CDATA[
    633 					this._loaded = true;
    634 					
    635 					// Unless we find a better way, use a separate HTML file
    636 					// for read-only mode
    637 					var htmlFile = this.mode + (this.getAttribute('readonly') != 'true' ? "" : "view");
    638 					
    639 					var ios = Components.classes["@mozilla.org/network/io-service;1"].
    640 						getService(Components.interfaces.nsIIOService);
    641 					var uri = ios.newURI("resource://zotero/tinymce/" + htmlFile + ".html", null, null);
    642 					
    643 					// Pass directionality (LTR/RTL) and locale in URL
    644 					uri.spec += "?locale=" + encodeURIComponent(Zotero.locale)
    645 						+ "&dir=" + Zotero.dir;
    646 					
    647 					
    648 					Zotero.debug("Loading " + uri.spec);
    649 					
    650 					// Register handler for deferred setting of content
    651 					var self = this;
    652 					var matchTo = null;
    653 					var listener = function(e) {
    654 						var win = self._iframe.contentWindow;
    655 						var SJOW = win.wrappedJSObject;
    656 						
    657 						// only fire if the target matches, or _zoteroMatchTo, which we set last
    658 						// time the target matched, matches
    659 						if(e.target !== self._iframe.contentDocument
    660 							&& (!SJOW._zoteroMatchTo || SJOW._zoteroMatchTo !== matchTo)) return;
    661 						
    662 						if (!SJOW.tinyMCE) {
    663 							Zotero.getInstalledExtensions().then(function(exts) {
    664 								for (let ext of exts) {
    665 									if (ext.indexOf('NoScript') != -1 && ext.indexOf('disabled') == -1) {
    666 										var doc = win.document;
    667 										var div = doc.getElementById('tinymce');
    668 										var warning = doc.createElement('div');
    669 										warning.id = 'noScriptWarning';
    670 										var str = "The NoScript extension is preventing Zotero "
    671 											+ "from displaying notes. To use NoScript and Zotero together, "
    672 											+ "whitelist the 'file:' scheme in the NoScript preferences "
    673 											+ "and restart " + Zotero.appName + ".";
    674 										warning.appendChild(document.createTextNode(str));
    675 										div.appendChild(warning);
    676 										break;
    677 									}
    678 								}
    679 							});
    680 							return;
    681 						}
    682 						
    683 						if (!SJOW.zoteroInit) {
    684 							SJOW.zoteroInit = function(editor) {
    685 								// Necessary in Fx32+
    686 								if (editor.wrappedJSObject) {
    687 									self._editor = editor.wrappedJSObject;
    688 								}
    689 								else {
    690 									self._editor = editor;
    691 								}
    692 								if (self._value) {
    693 									self.value = self._value;
    694 									
    695 									// Prevent undoing to empty note after initialization
    696 									self._editor.undoManager.clear();
    697 									self._editor.undoManager.add();
    698 								}
    699 								if (self._focus) {
    700 									setTimeout(function () {
    701 										self._iframe.focus();
    702 										self._editor.focus();
    703 									});
    704 									self._focus = false;
    705 								}
    706 								
    707 								// Add CSS rules to notes
    708 								if (self.mode == 'note') {
    709 									let fontSize = Zotero.Prefs.get('note.fontSize');
    710 									// Fix empty old font prefs before a value was enforced
    711 									if (fontSize < 6) {
    712 										fontSize = 11;
    713 									}
    714 									var css = "body#zotero-tinymce-note, "
    715 										+ "body#zotero-tinymce-note p, "
    716 										+ "body#zotero-tinymce-note th, "
    717 										+ "body#zotero-tinymce-note td, "
    718 										+ "body#zotero-tinymce-note pre { "
    719 											+ "font-size: " + fontSize + "px; "
    720 										+ "} "
    721 										+ "body#zotero-tinymce-note, "
    722 										+ "body#zotero-tinymce-note p { "
    723 											+ "font-family: "
    724 											+ Zotero.Prefs.get('note.fontFamily') + "; "
    725 										+ "}"
    726 										+ Zotero.Prefs.get('note.css');
    727 									
    728 									var doc = editor.contentDocument;
    729 									var head = doc.getElementsByTagName("head")[0];
    730 									var style = doc.createElement("style");
    731 									style.innerHTML = css;
    732 									head.appendChild(style);
    733 								}
    734 								
    735 								let cb;
    736 								if (this._onInitCallbacks) {
    737 									while (cb = this._onInitCallbacks.shift()) {
    738 										cb(this._editor);
    739 									}
    740 								}
    741 							}.bind(this);
    742 						}
    743 						
    744 						var editor = SJOW.tinyMCE.get("tinymce");
    745 						if (!editor) {
    746 							Zotero.debug("editor not ready");
    747 							
    748 							// this is a hack; I'm not sure why we can't identify the event target
    749 							// next time without it, but apparently we can't
    750 							matchTo = Zotero.randomString();
    751 							SJOW._zoteroMatchTo = matchTo;
    752 							
    753 							// Not ready yet
    754 							return;
    755 						}
    756 						
    757 						self._iframe.removeEventListener("DOMContentLoaded", listener, false);
    758 						
    759 						if (self._eventHandler) {
    760 							win.wrappedJSObject.zoteroHandleEvent = self._eventHandler;
    761 						}
    762 						
    763 						// Run Cut/Copy/Paste with chrome privileges
    764 						win.wrappedJSObject.zoteroExecCommand = function (doc, command, ui, value) {
    765 							return doc.execCommand(command, ui, value);
    766 						}
    767 					}.bind(this);
    768 					
    769 					this._iframe.addEventListener("DOMContentLoaded", listener, false);
    770 					
    771 					this._iframe.webNavigation.loadURI(uri.spec,
    772 						Components.interfaces.nsIWebNavigation.LOAD_FLAGS_BYPASS_HISTORY, null, null, null);
    773 				]]>
    774 				</body>
    775 			</method>
    776 			
    777 		</implementation>
    778 		
    779 		<content>
    780 			<xul:iframe flex="1" anonid="rt-view" class="rt-view" type="content"
    781 				xbl:inherits="onfocus,onblur,flex,width,height,hidden"
    782 				style="overflow: hidden"/>
    783 		</content>
    784 	</binding>
    785 </bindings>