!import
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
1 /* ***** BEGIN LICENSE BLOCK *****
2 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
3 *
4 * The contents of this file are subject to the Mozilla Public License Version
5 * 1.1 (the "License"); you may not use this file except in compliance with
6 * the License. You may obtain a copy of the License at
7 * http://www.mozilla.org/MPL/
8 *
9 * Software distributed under the License is distributed on an "AS IS" basis,
10 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
11 * for the specific language governing rights and limitations under the
12 * License.
13 *
14 * The Original Code is Mozilla Communicator client code, released
15 * March 31, 1998.
16 *
17 * The Initial Developer of the Original Code is
18 * Netscape Communications Corporation.
19 * Portions created by the Initial Developer are Copyright (C) 1998-1999
20 * the Initial Developer. All Rights Reserved.
21 *
22 * Contributor(s):
23 * Pete Collins
24 * Brian King
25 * Charles Manske (cmanske@netscape.com)
26 * Neil Rashbrook (neil@parkwaycc.co.uk)
27 *
28 * Alternatively, the contents of this file may be used under the terms of
29 * either of the GNU General Public License Version 2 or later (the "GPL"),
30 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
31 * in which case the provisions of the GPL or the LGPL are applicable instead
32 * of those above. If you wish to allow use of your version of this file only
33 * under the terms of either the GPL or the LGPL, and not to allow others to
34 * use your version of this file under the terms of the MPL, indicate your
35 * decision by deleting the provisions above and replace them with the notice
36 * and other provisions required by the GPL or the LGPL. If you do not delete
37 * the provisions above, a recipient may use your version of this file under
38 * the terms of any one of the MPL, the GPL or the LGPL.
39 *
40 * ***** END LICENSE BLOCK ***** */
41
42 // Each editor window must include this file
43
44 // Object to attach commonly-used widgets (all dialogs should use this)
45 var gDialog = {};
46
47 var gValidationError = false;
48
49 // Use for 'defaultIndex' param in InitPixelOrPercentMenulist
50 const gPixel = 0;
51 const gPercent = 1;
52
53 const gMaxPixels = 100000; // Used for image size, borders, spacing, and padding
54 // Gecko code uses 1000 for maximum rowspan, colspan
55 // Also, editing performance is really bad above this
56 const gMaxRows = 1000;
57 const gMaxColumns = 1000;
58 const gMaxTableSize = 1000000; // Width or height of table or cells
59
60 // For dialogs that expand in size. Default is smaller size see "onMoreFewer()" below
61 var SeeMore = false;
62
63 // A XUL element with id="location" for managing
64 // dialog location relative to parent window
65 var gLocation;
66
67 // The element being edited - so AdvancedEdit can have access to it
68 var globalElement;
69
70 /* Validate contents of an input field
71 *
72 * inputWidget The 'textbox' XUL element for text input of the attribute's value
73 * listWidget The 'menulist' XUL element for choosing "pixel" or "percent"
74 * May be null when no pixel/percent is used.
75 * minVal minimum allowed for input widget's value
76 * maxVal maximum allowed for input widget's value
77 * (when "listWidget" is used, maxVal is used for "pixel" maximum,
78 * 100% is assumed if "percent" is the user's choice)
79 * element The DOM element that we set the attribute on. May be null.
80 * attName Name of the attribute to set. May be null or ignored if "element" is null
81 * mustHaveValue If true, error dialog is displayed if "value" is empty string
82 *
83 * This calls "ValidateNumberRange()", which puts up an error dialog to inform the user.
84 * If error, we also:
85 * Shift focus and select contents of the inputWidget,
86 * Switch to appropriate panel of tabbed dialog if user implements "SwitchToValidate()",
87 * and/or will expand the dialog to full size if "More / Fewer" feature is implemented
88 *
89 * Returns the "value" as a string, or "" if error or input contents are empty
90 * The global "gValidationError" variable is set true if error was found
91 */
ValidateNumber
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
92 function ValidateNumber(inputWidget, listWidget, minVal, maxVal, element, attName, mustHaveValue, mustShowMoreSection)
93 {
94 if (!inputWidget)
95 {
96 gValidationError = true;
97 return "";
98 }
99
100 // Global error return value
101 gValidationError = false;
102 var maxLimit = maxVal;
103 var isPercent = false;
104
105 var numString = TrimString(inputWidget.value);
106 if (numString || mustHaveValue)
107 {
108 if (listWidget)
109 isPercent = (listWidget.selectedIndex == 1);
110 if (isPercent)
111 maxLimit = 100;
112
113 // This method puts up the error message
114 numString = ValidateNumberRange(numString, minVal, maxLimit, mustHaveValue);
115 if(!numString)
116 {
117 // Switch to appropriate panel for error reporting
118 SwitchToValidatePanel();
119
120 // or expand dialog for users of "More / Fewer" button
121 if ("dialog" in window && dialog &&
122 "MoreSection" in gDialog && gDialog.MoreSection)
123 {
124 if ( !SeeMore )
125 onMoreFewer();
126 }
127
128 // Error - shift to offending input widget
129 SetTextboxFocus(inputWidget);
130 gValidationError = true;
131 }
132 else
133 {
134 if (isPercent)
135 numString += "%";
136 if (element)
137 GetCurrentEditor().setAttributeOrEquivalent(element, attName, numString, true);
138 }
139 } else if (element) {
140 GetCurrentEditor().removeAttributeOrEquivalent(element, attName, true)
141 }
142 return numString;
143 }
144
145 /* Validate contents of an input field
146 *
147 * value number to validate
148 * minVal minimum allowed for input widget's value
149 * maxVal maximum allowed for input widget's value
150 * (when "listWidget" is used, maxVal is used for "pixel" maximum,
151 * 100% is assumed if "percent" is the user's choice)
152 * mustHaveValue If true, error dialog is displayed if "value" is empty string
153 *
154 * If inputWidget's value is outside of range, or is empty when "mustHaveValue" = true,
155 * an error dialog is popuped up to inform the user. The focus is shifted
156 * to the inputWidget.
157 *
158 * Returns the "value" as a string, or "" if error or input contents are empty
159 * The global "gValidationError" variable is set true if error was found
160 */
ValidateNumberRange
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
161 function ValidateNumberRange(value, minValue, maxValue, mustHaveValue)
162 {
163 // Initialize global error flag
164 gValidationError = false;
165 value = TrimString(String(value));
166
167 // We don't show error for empty string unless caller wants to
168 if (!value && !mustHaveValue)
169 return "";
170
171 var numberStr = "";
172
173 if (value.length > 0)
174 {
175 // Extract just numeric characters
176 var number = Number(value.replace(/\D+/g, ""));
177 if (number >= minValue && number <= maxValue )
178 {
179 // Return string version of the number
180 return String(number);
181 }
182 numberStr = String(number);
183 }
184
185 var message = "";
186
187 if (numberStr.length > 0)
188 {
189 // We have a number from user outside of allowed range
190 message = GetString( "ValidateRangeMsg");
191 message = message.replace(/%n%/, numberStr);
192 message += "\n ";
193 }
194 message += GetString( "ValidateNumberMsg");
195
196 // Replace variable placeholders in message with number values
197 message = message.replace(/%min%/, minValue).replace(/%max%/, maxValue);
198 ShowInputErrorMessage(message);
199
200 // Return an empty string to indicate error
201 gValidationError = true;
202 return "";
203 }
204
SetTextboxFocusById
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
205 function SetTextboxFocusById(id)
206 {
207 SetTextboxFocus(document.getElementById(id));
208 }
209
SetTextboxFocus
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
210 function SetTextboxFocus(textbox)
211 {
212 if (textbox)
213 {
214 //XXX Using the setTimeout is hacky workaround for bug 103197
215 // Must create a new function to keep "textbox" in scope
anon:216:16
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
216 setTimeout( function(textbox) { textbox.focus(); textbox.select(); }, 0, textbox );
217 }
218 }
219
ShowInputErrorMessage
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
220 function ShowInputErrorMessage(message)
221 {
222 AlertWithTitle(GetString("InputError"), message);
223 window.focus();
224 }
225
226 // Get the text appropriate to parent container
227 // to determine what a "%" value is referring to.
228 // elementForAtt is element we are actually setting attributes on
229 // (a temporary copy of element in the doc to allow canceling),
230 // but elementInDoc is needed to find parent context in document
GetAppropriatePercentString
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
231 function GetAppropriatePercentString(elementForAtt, elementInDoc)
232 {
233 var editor = GetCurrentEditor();
234 try {
235 var name = elementForAtt.nodeName.toLowerCase();
236 if ( name == "td" || name == "th")
237 return GetString("PercentOfTable");
238
239 // Check if element is within a table cell
240 if (editor.getElementOrParentByTagName("td", elementInDoc))
241 return GetString("PercentOfCell");
242 else
243 return GetString("PercentOfWindow");
244 } catch (e) { return "";}
245 }
246
ClearListbox
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
247 function ClearListbox(listbox)
248 {
249 if (listbox)
250 {
251 listbox.clearSelection();
252 while (listbox.firstChild)
253 listbox.removeChild(listbox.firstChild);
254 }
255 }
256
forceInteger
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
257 function forceInteger(elementID)
258 {
259 var editField = document.getElementById( elementID );
260 if ( !editField )
261 return;
262
263 var stringIn = editField.value;
264 if (stringIn && stringIn.length > 0)
265 {
266 // Strip out all nonnumeric characters
267 stringIn = stringIn.replace(/\D+/g,"");
268 if (!stringIn) stringIn = "";
269
270 // Write back only if changed
271 if (stringIn != editField.value)
272 editField.value = stringIn;
273 }
274 }
275
LimitStringLength
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
276 function LimitStringLength(elementID, length)
277 {
278 var editField = document.getElementById( elementID );
279 if ( !editField )
280 return;
281
282 var stringIn = editField.value;
283 if (stringIn && stringIn.length > length)
284 editField.value = stringIn.slice(0,length);
285 }
286
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
287 function InitPixelOrPercentMenulist(elementForAtt, elementInDoc, attribute, menulistID, defaultIndex)
288 {
289 if (!defaultIndex) defaultIndex = gPixel;
290
291 // var size = elementForAtt.getAttribute(attribute);
292 var size = GetHTMLOrCSSStyleValue(elementForAtt, attribute, attribute)
293 var menulist = document.getElementById(menulistID);
294 var pixelItem;
295 var percentItem;
296
297 if (!menulist)
298 {
299 dump("NO MENULIST found for ID="+menulistID+"\n");
300 return size;
301 }
302
303 menulist.removeAllItems();
304 pixelItem = menulist.appendItem(GetString("Pixels"));
305
306 if (!pixelItem) return 0;
307
308 percentItem = menulist.appendItem(GetAppropriatePercentString(elementForAtt, elementInDoc));
309 if (size && size.length > 0)
310 {
311 // Search for a "%" or "px"
312 if (/%/.test(size))
313 {
314 // Strip out the %
315 size = RegExp.leftContext;
316 if (percentItem)
317 menulist.selectedItem = percentItem;
318 }
319 else
320 {
321 if (/px/.test(size))
322 // Strip out the px
323 size = RegExp.leftContext;
324 menulist.selectedItem = pixelItem;
325 }
326 }
327 else
328 menulist.selectedIndex = defaultIndex;
329
330 return size;
331 }
332
onAdvancedEdit
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
333 function onAdvancedEdit()
334 {
335 // First validate data from widgets in the "simpler" property dialog
336 if (ValidateData())
337 {
338 // Set true if OK is clicked in the Advanced Edit dialog
339 window.AdvancedEditOK = false;
340 // Open the AdvancedEdit dialog, passing in the element to be edited
341 // (the copy named "globalElement")
342 window.openDialog("chrome://editor/content/EdAdvancedEdit.xul", "_blank", "chrome,close,titlebar,modal,resizable=yes", "", globalElement);
343 window.focus();
344 if (window.AdvancedEditOK)
345 {
346 // Copy edited attributes to the dialog widgets:
347 InitDialog();
348 }
349 }
350 }
351
getColor
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
352 function getColor(ColorPickerID)
353 {
354 var colorPicker = document.getElementById(ColorPickerID);
355 var color;
356 if (colorPicker)
357 {
358 // Extract color from colorPicker and assign to colorWell.
359 color = colorPicker.getAttribute("color");
360 if (color && color == "")
361 return null;
362 // Clear color so next if it's called again before
363 // color picker is actually used, we dedect the "don't set color" state
364 colorPicker.setAttribute("color","");
365 }
366
367 return color;
368 }
369
setColorWell
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
370 function setColorWell(ColorWellID, color)
371 {
372 var colorWell = document.getElementById(ColorWellID);
373 if (colorWell)
374 {
375 if (!color || color == "")
376 {
377 // Don't set color (use default)
378 // Trigger change to not show color swatch
379 colorWell.setAttribute("default","true");
380 // Style in CSS sets "background-color",
381 // but color won't clear unless we do this:
382 colorWell.removeAttribute("style");
383 }
384 else
385 {
386 colorWell.removeAttribute("default");
387 // Use setAttribute so colorwell can be a XUL element, such as button
388 colorWell.setAttribute("style", "background-color:"+color);
389 }
390 }
391 }
392
getColorAndSetColorWell
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
393 function getColorAndSetColorWell(ColorPickerID, ColorWellID)
394 {
395 var color = getColor(ColorPickerID);
396 setColorWell(ColorWellID, color);
397 return color;
398 }
399
InitMoreFewer
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
400 function InitMoreFewer()
401 {
402 // Set SeeMore bool to the OPPOSITE of the current state,
403 // which is automatically saved by using the 'persist="more"'
404 // attribute on the gDialog.MoreFewerButton button
405 // onMoreFewer will toggle it and redraw the dialog
406 SeeMore = (gDialog.MoreFewerButton.getAttribute("more") != "1");
407 onMoreFewer();
408 gDialog.MoreFewerButton.setAttribute("accesskey",GetString("PropertiesAccessKey"));
409 }
410
onMoreFewer
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
411 function onMoreFewer()
412 {
413 if (SeeMore)
414 {
415 gDialog.MoreSection.collapsed = true;
416 gDialog.MoreFewerButton.setAttribute("more","0");
417 gDialog.MoreFewerButton.setAttribute("label",GetString("MoreProperties"));
418 SeeMore = false;
419 }
420 else
421 {
422 gDialog.MoreSection.collapsed = false;
423 gDialog.MoreFewerButton.setAttribute("more","1");
424 gDialog.MoreFewerButton.setAttribute("label",GetString("FewerProperties"));
425 SeeMore = true;
426 }
427 window.sizeToContent();
428 }
429
SwitchToValidatePanel
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
430 function SwitchToValidatePanel()
431 {
432 // no default implementation
433 // Only EdTableProps.js currently implements this
434 }
435
436 const nsIFilePicker = Components.interfaces.nsIFilePicker;
437
GetLocalFileURL
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
438 function GetLocalFileURL(filterType)
439 {
440 var fp = Components.classes["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker);
441 var fileType = "html";
442
443 if (filterType == "img")
444 {
445 fp.init(window, GetString("SelectImageFile"), nsIFilePicker.modeOpen);
446 fp.appendFilters(nsIFilePicker.filterImages);
447 fileType = "image";
448 }
449 // Current usage of this is in Link dialog,
450 // where we always want HTML first
451 else if (filterType.indexOf("html") == 0)
452 {
453 fp.init(window, GetString("OpenHTMLFile"), nsIFilePicker.modeOpen);
454
455 // When loading into Composer, direct user to prefer HTML files and text files,
456 // so we call separately to control the order of the filter list
457 fp.appendFilters(nsIFilePicker.filterHTML);
458 fp.appendFilters(nsIFilePicker.filterText);
459
460 // Link dialog also allows linking to images
461 if (filterType.indexOf("img") > 0)
462 fp.appendFilters(nsIFilePicker.filterImages);
463
464 }
465 // Default or last filter is "All Files"
466 fp.appendFilters(nsIFilePicker.filterAll);
467
468 // set the file picker's current directory to last-opened location saved in prefs
469 SetFilePickerDirectory(fp, fileType);
470
471
472 /* doesn't handle *.shtml files */
473 try {
474 var ret = fp.show();
475 if (ret == nsIFilePicker.returnCancel)
476 return null;
477 }
478 catch (ex) {
479 dump("filePicker.chooseInputFile threw an exception\n");
480 return null;
481 }
482 SaveFilePickerDirectory(fp, fileType);
483
484 var fileHandler = GetFileProtocolHandler();
485 return fp.file ? fileHandler.getURLSpecFromFile(fp.file) : null;
486 }
487
GetMetaElement
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
488 function GetMetaElement(name)
489 {
490 if (name)
491 {
492 name = name.toLowerCase();
493 if (name != "")
494 {
495 var editor = GetCurrentEditor();
496 try {
497 var metaNodes = editor.document.getElementsByTagName("meta");
498 for (var i = 0; i < metaNodes.length; i++)
499 {
500 var metaNode = metaNodes.item(i);
501 if (metaNode && metaNode.getAttribute("name") == name)
502 return metaNode;
503 }
504 } catch (e) {}
505 }
506 }
507 return null;
508 }
509
CreateMetaElement
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
510 function CreateMetaElement(name)
511 {
512 var editor = GetCurrentEditor();
513 try {
514 var metaElement = editor.createElementWithDefaults("meta");
515 metaElement.setAttribute("name", name);
516 return metaElement;
517 } catch (e) {}
518
519 return null;
520 }
521
GetHTTPEquivMetaElement
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
522 function GetHTTPEquivMetaElement(name)
523 {
524 if (name)
525 {
526 name = name.toLowerCase();
527 if (name != "")
528 {
529 var editor = GetCurrentEditor();
530 try {
531 var metaNodes = editor.document.getElementsByTagName("meta");
532 for (var i = 0; i < metaNodes.length; i++)
533 {
534 var metaNode = metaNodes.item(i);
535 if (metaNode)
536 {
537 var httpEquiv = metaNode.getAttribute("http-equiv");
538 if (httpEquiv && httpEquiv.toLowerCase() == name)
539 return metaNode;
540 }
541 }
542 } catch (e) {}
543 }
544 }
545 return null;
546 }
547
CreateHTTPEquivMetaElement
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
548 function CreateHTTPEquivMetaElement(name)
549 {
550 var editor = GetCurrentEditor();
551 try {
552 var metaElement = editor.createElementWithDefaults("meta");
553 metaElement.setAttribute("http-equiv", name);
554 return metaElement;
555 } catch (e) {}
556
557 return null;
558 }
559
CreateHTTPEquivElement
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
560 function CreateHTTPEquivElement(name)
561 {
562 var editor = GetCurrentEditor();
563 try {
564 var metaElement = editor.createElementWithDefaults("meta");
565 metaElement.setAttribute("http-equiv", name);
566 return metaElement;
567 } catch (e) {}
568
569 return null;
570 }
571
572 // Change "content" attribute on a META element,
573 // or delete entire element it if content is empty
574 // This uses undoable editor transactions
SetMetaElementContent
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
575 function SetMetaElementContent(metaElement, content, insertNew, prepend)
576 {
577 if (metaElement)
578 {
579 var editor = GetCurrentEditor();
580 try {
581 if(!content || content == "")
582 {
583 if (!insertNew)
584 editor.deleteNode(metaElement);
585 }
586 else
587 {
588 if (insertNew)
589 {
590 metaElement.setAttribute("content", content);
591 if (prepend)
592 PrependHeadElement(metaElement);
593 else
594 AppendHeadElement(metaElement);
595 }
596 else
597 editor.setAttribute(metaElement, "content", content);
598 }
599 } catch (e) {}
600 }
601 }
602
GetHeadElement
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
603 function GetHeadElement()
604 {
605 var editor = GetCurrentEditor();
606 try {
607 var headList = editor.document.getElementsByTagName("head");
608 return headList.item(0);
609 } catch (e) {}
610
611 return null;
612 }
613
PrependHeadElement
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
614 function PrependHeadElement(element)
615 {
616 var head = GetHeadElement();
617 if (head)
618 {
619 var editor = GetCurrentEditor();
620 try {
621 // Use editor's undoable transaction
622 // Last param "true" says "don't change the selection"
623 editor.insertNode(element, head, 0, true);
624 } catch (e) {}
625 }
626 }
627
AppendHeadElement
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
628 function AppendHeadElement(element)
629 {
630 var head = GetHeadElement();
631 if (head)
632 {
633 var position = 0;
634 if (head.hasChildNodes())
635 position = head.childNodes.length;
636
637 var editor = GetCurrentEditor();
638 try {
639 // Use editor's undoable transaction
640 // Last param "true" says "don't change the selection"
641 editor.insertNode(element, head, position, true);
642 } catch (e) {}
643 }
644 }
645
SetWindowLocation
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
646 function SetWindowLocation()
647 {
648 gLocation = document.getElementById("location");
649 if (gLocation)
650 {
651 window.screenX = Math.max(0, Math.min(window.opener.screenX + Number(gLocation.getAttribute("offsetX")),
652 screen.availWidth - window.outerWidth));
653 window.screenY = Math.max(0, Math.min(window.opener.screenY + Number(gLocation.getAttribute("offsetY")),
654 screen.availHeight - window.outerHeight));
655 }
656 }
657
SaveWindowLocation
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
658 function SaveWindowLocation()
659 {
660 if (gLocation)
661 {
662 var newOffsetX = window.screenX - window.opener.screenX;
663 var newOffsetY = window.screenY - window.opener.screenY;
664 gLocation.setAttribute("offsetX", window.screenX - window.opener.screenX);
665 gLocation.setAttribute("offsetY", window.screenY - window.opener.screenY);
666 }
667 }
668
onCancel
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
669 function onCancel()
670 {
671 SaveWindowLocation();
672 // Close dialog by returning true
673 return true;
674 }
675
SetRelativeCheckbox
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
676 function SetRelativeCheckbox(checkbox)
677 {
678 if (!checkbox) {
679 checkbox = document.getElementById("MakeRelativeCheckbox");
680 if (!checkbox)
681 return;
682 }
683
684 var editor = GetCurrentEditor();
685 // Mail never allows relative URLs, so hide the checkbox
686 if (editor && (editor.flags & Components.interfaces.nsIPlaintextEditor.eEditorMailMask))
687 {
688 checkbox.collapsed = true;
689 return;
690 }
691
692 var input = document.getElementById(checkbox.getAttribute("for"));
693 if (!input)
694 return;
695
696 var url = TrimString(input.value);
697 var urlScheme = GetScheme(url);
698
699 // Check it if url is relative (no scheme).
700 checkbox.checked = url.length > 0 && !urlScheme;
701
702 // Now do checkbox enabling:
703 var enable = false;
704
705 var docUrl = GetDocumentBaseUrl();
706 var docScheme = GetScheme(docUrl);
707
708 if (url && docUrl && docScheme)
709 {
710 if (urlScheme)
711 {
712 // Url is absolute
713 // If we can make a relative URL, then enable must be true!
714 // (this lets the smarts of MakeRelativeUrl do all the hard work)
715 enable = (GetScheme(MakeRelativeUrl(url)).length == 0);
716 }
717 else
718 {
719 // Url is relative
720 // Check if url is a named anchor
721 // but document doesn't have a filename
722 // (it's probably "index.html" or "index.htm",
723 // but we don't want to allow a malformed URL)
724 if (url[0] == "#")
725 {
726 var docFilename = GetFilename(docUrl);
727 enable = docFilename.length > 0;
728 }
729 else
730 {
731 // Any other url is assumed
732 // to be ok to try to make absolute
733 enable = true;
734 }
735 }
736 }
737
738 SetElementEnabled(checkbox, enable);
739 }
740
741 // oncommand handler for the Relativize checkbox in EditorOverlay.xul
MakeInputValueRelativeOrAbsolute
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
742 function MakeInputValueRelativeOrAbsolute(checkbox)
743 {
744 var input = document.getElementById(checkbox.getAttribute("for"));
745 if (!input)
746 return;
747
748 var docUrl = GetDocumentBaseUrl();
749 if (!docUrl)
750 {
751 // Checkbox should be disabled if not saved,
752 // but keep this error message in case we change that
753 AlertWithTitle("", GetString("SaveToUseRelativeUrl"));
754 window.focus();
755 }
756 else
757 {
758 // Note that "checked" is opposite of its last state,
759 // which determines what we want to do here
760 if (checkbox.checked)
761 input.value = MakeRelativeUrl(input.value);
762 else
763 input.value = MakeAbsoluteUrl(input.value);
764
765 // Reset checkbox to reflect url state
766 SetRelativeCheckbox(checkbox);
767 }
768 }
769
770 var IsBlockParent = {
771 APPLET: true,
772 BLOCKQUOTE: true,
773 BODY: true,
774 CENTER: true,
775 DD: true,
776 DIV: true,
777 FORM: true,
778 LI: true,
779 NOSCRIPT: true,
780 OBJECT: true,
781 TD: true,
782 TH: true
783 };
784
785 var NotAnInlineParent = {
786 COL: true,
787 COLGROUP: true,
788 DL: true,
789 DIR: true,
790 MENU: true,
791 OL: true,
792 TABLE: true,
793 TBODY: true,
794 TFOOT: true,
795 THEAD: true,
796 TR: true,
797 UL: true
798 };
799
nodeIsBreak
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
800 function nodeIsBreak(editor, node)
801 {
802 return !node || node.localName == 'BR' || editor.nodeIsBlock(node);
803 }
804
InsertElementAroundSelection
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
805 function InsertElementAroundSelection(element)
806 {
807 var editor = GetCurrentEditor();
808 editor.beginTransaction();
809
810 try {
811 // First get the selection as a single range
812 var range, start, end, offset;
813 var count = editor.selection.rangeCount;
814 if (count == 1)
815 range = editor.selection.getRangeAt(0).cloneRange();
816 else
817 {
818 range = editor.document.createRange();
819 start = editor.selection.getRangeAt(0)
820 range.setStart(start.startContainer, start.startOffset);
821 end = editor.selection.getRangeAt(--count);
822 range.setEnd(end.endContainer, end.endOffset);
823 }
824
825 // Flatten the selection to child nodes of the common ancestor
826 while (range.startContainer != range.commonAncestorContainer)
827 range.setStartBefore(range.startContainer);
828 while (range.endContainer != range.commonAncestorContainer)
829 range.setEndAfter(range.endContainer);
830
831 if (editor.nodeIsBlock(element))
832 // Block element parent must be a valid block
833 while (!(range.commonAncestorContainer.localName in IsBlockParent))
834 range.selectNode(range.commonAncestorContainer);
835 else
836 {
837 // Fail if we're not inserting a block (use setInlineProperty instead)
838 if (!nodeIsBreak(editor, range.commonAncestorContainer))
839 return false;
840 else if (range.commonAncestorContainer.localName in NotAnInlineParent)
841 // Inline element parent must not be an invalid block
842 do range.selectNode(range.commonAncestorContainer);
843 while (range.commonAncestorContainer.localName in NotAnInlineParent);
844 else
845 // Further insert block check
846 for (var i = range.startOffset; ; i++)
847 if (i == range.endOffset)
848 return false;
849 else if (nodeIsBreak(editor, range.commonAncestorContainer.childNodes[i]))
850 break;
851 }
852
853 // The range may be contained by body text, which should all be selected.
854 offset = range.startOffset;
855 start = range.startContainer.childNodes[offset];
856 if (!nodeIsBreak(editor, start))
857 {
858 while (!nodeIsBreak(editor, start.previousSibling))
859 {
860 start = start.previousSibling;
861 offset--;
862 }
863 }
864 end = range.endContainer.childNodes[range.endOffset];
865 if (end && !nodeIsBreak(editor, end.previousSibling))
866 {
867 while (!nodeIsBreak(editor, end))
868 end = end.nextSibling;
869 }
870
871 // Now insert the node
872 editor.insertNode(element, range.commonAncestorContainer, offset, true);
873 offset = element.childNodes.length;
874 if (!editor.nodeIsBlock(element))
875 editor.setShouldTxnSetSelection(false);
876
877 // Move all the old child nodes to the element
878 var empty = true;
879 while (start != end)
880 {
881 var next = start.nextSibling;
882 editor.deleteNode(start);
883 editor.insertNode(start, element, element.childNodes.length);
884 empty = false;
885 start = next;
886 }
887 if (!editor.nodeIsBlock(element))
888 editor.setShouldTxnSetSelection(true);
889 else
890 {
891 // Also move a trailing <br>
892 if (start && start.localName == 'BR')
893 {
894 editor.deleteNode(start);
895 editor.insertNode(start, element, element.childNodes.length);
896 empty = false;
897 }
898 // Still nothing? Insert a <br> so the node is not empty
899 if (empty)
900 editor.insertNode(editor.createElementWithDefaults("br"), element, element.childNodes.length);
901
902 // Hack to set the selection just inside the element
903 editor.insertNode(editor.document.createTextNode(""), element, offset);
904 }
905 }
906 finally {
907 editor.endTransaction();
908 }
909
910 return true;
911 }
912
nodeIsBlank
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
913 function nodeIsBlank(node)
914 {
915 return node && node.nodeType == Node.TEXT_NODE && !/\S/.test(node.data);
916 }
917
nodeBeginsBlock
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
918 function nodeBeginsBlock(editor, node)
919 {
920 while (nodeIsBlank(node))
921 node = node.nextSibling;
922 return nodeIsBreak(editor, node);
923 }
924
nodeEndsBlock
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
925 function nodeEndsBlock(editor, node)
926 {
927 while (nodeIsBlank(node))
928 node = node.previousSibling;
929 return nodeIsBreak(editor, node);
930 }
931
932 // C++ function isn't exposed to JS :-(
RemoveBlockContainer
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
933 function RemoveBlockContainer(element)
934 {
935 var editor = GetCurrentEditor();
936 editor.beginTransaction();
937
938 try {
939 var range = editor.document.createRange();
940 range.selectNode(element);
941 var offset = range.startOffset;
942 var parent = element.parentNode;
943
944 // May need to insert a break after the removed element
945 if (!nodeBeginsBlock(editor, element.nextSibling) &&
946 !nodeEndsBlock(editor, element.lastChild))
947 editor.insertNode(editor.createElementWithDefaults("br"), parent, range.endOffset);
948
949 // May need to insert a break before the removed element, or if it was empty
950 if (!nodeEndsBlock(editor, element.previousSibling) &&
951 !nodeBeginsBlock(editor, element.firstChild || element.nextSibling))
952 editor.insertNode(editor.createElementWithDefaults("br"), parent, offset++);
953
954 // Now remove the element
955 editor.deleteNode(element);
956
957 // Need to copy the contained nodes?
958 for (var i = 0; i < element.childNodes.length; i++)
959 editor.insertNode(element.childNodes[i].cloneNode(true), parent, offset++);
960 }
961 finally {
962 editor.endTransaction();
963 }
964 }
965
966 // C++ function isn't exposed to JS :-(
RemoveContainer
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
967 function RemoveContainer(element)
968 {
969 var editor = GetCurrentEditor();
970 editor.beginTransaction();
971
972 try {
973 var range = editor.document.createRange();
974 var parent = element.parentNode;
975 // Allow for automatic joining of text nodes
976 // so we can't delete the container yet
977 // so we need to copy the contained nodes
978 for (var i = 0; i < element.childNodes.length; i++) {
979 range.selectNode(element);
980 editor.insertNode(element.childNodes[i].cloneNode(true), parent, range.startOffset);
981 }
982 // Now remove the element
983 editor.deleteNode(element);
984 }
985 finally {
986 editor.endTransaction();
987 }
988 }
989
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
990 function FillLinkMenulist(linkMenulist, headingsArray)
991 {
992 var menupopup = linkMenulist.firstChild;
993 var editor = GetCurrentEditor();
994 try {
995 var treeWalker = editor.document.createTreeWalker(editor.document, 1, null, true);
996 var headingList = [];
997 var anchorList = []; // for sorting
998 var anchorMap = {}; // for weeding out duplicates and making heading anchors unique
999 var anchor;
1000 var i;
1001 for (var element = treeWalker.nextNode(); element; element = treeWalker.nextNode())
1002 {
1003 // grab headings
1004 // Skip headings that already have a named anchor as their first child
1005 // (this may miss nearby anchors, but at least we don't insert another
1006 // under the same heading)
1007 if (element instanceof HTMLHeadingElement && element.textContent &&
1008 !(element.firstChild instanceof HTMLAnchorElement && element.firstChild.name))
1009 headingList.push(element);
1010
1011 // grab named anchors
1012 if (element instanceof HTMLAnchorElement && element.name)
1013 {
1014 anchor = '#' + element.name;
1015 if (!(anchor in anchorMap))
1016 {
1017 anchorList.push({anchor: anchor, sortkey: anchor.toLowerCase()});
1018 anchorMap[anchor] = true;
1019 }
1020 }
1021
1022 // grab IDs
1023 if (element.id)
1024 {
1025 anchor = '#' + element.id;
1026 if (!(anchor in anchorMap))
1027 {
1028 anchorList.push({anchor: anchor, sortkey: anchor.toLowerCase()});
1029 anchorMap[anchor] = true;
1030 }
1031 }
1032 }
1033 // add anchor for headings
1034 for (i = 0; i < headingList.length; i++)
1035 {
1036 var heading = headingList[i];
1037
1038 // Use just first 40 characters, don't add "...",
1039 // and replace whitespace with "_" and strip non-word characters
1040 anchor = '#' + ConvertToCDATAString(TruncateStringAtWordEnd(heading.textContent, 40, false));
1041
1042 // Append "_" to any name already in the list
1043 while (anchor in anchorMap)
1044 anchor += "_";
1045 anchorList.push({anchor: anchor, sortkey: anchor.toLowerCase()});
1046 anchorMap[anchor] = true;
1047
1048 // Save nodes in an array so we can create anchor node under it later
1049 headingsArray[anchor] = heading;
1050 }
1051 if (anchorList.length)
1052 {
1053 // case insensitive sort
compare
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
1054 function compare(a, b)
1055 {
1056 if(a.sortkey < b.sortkey) return -1;
1057 if(a.sortkey > b.sortkey) return 1;
1058 return 0;
1059 }
1060 anchorList.sort(compare);
1061
1062 for (i = 0; i < anchorList.length; i++)
1063 createMenuItem(menupopup,anchorList[i].anchor);
1064 }
1065 else
1066 {
1067 var item = createMenuItem(menupopup, GetString("NoNamedAnchorsOrHeadings"));
1068 item.setAttribute("disabled", "true");
1069 }
1070 } catch (e) {}
1071 }
1072
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
1073 function createMenuItem(aMenuPopup, aLabel)
1074 {
1075 var menuitem = document.createElement("menuitem");
1076 menuitem.setAttribute("label", aLabel);
1077 aMenuPopup.appendChild(menuitem);
1078 return menuitem;
1079 }
1080
1081 // Shared by Image and Link dialogs for the "Choose" button for links
chooseLinkFile
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
1082 function chooseLinkFile()
1083 {
1084 // Get a local file, converted into URL format
1085 var fileName = GetLocalFileURL("html, img");
1086 if (fileName)
1087 {
1088 // Always try to relativize local file URLs
1089 if (gHaveDocumentUrl)
1090 fileName = MakeRelativeUrl(fileName);
1091
1092 gDialog.hrefInput.value = fileName;
1093
1094 // Do stuff specific to a particular dialog
1095 // (This is defined separately in Image and Link dialogs)
1096 ChangeLinkLocation();
1097 }
1098 // Put focus into the input field
1099 SetTextboxFocus(gDialog.hrefInput);
1100 }