home *** CD-ROM | disk | FTP | other *** search
/ Chip 2004 April / CMCD0404.ISO / Software / Complet / thunderbird / chrome / mail.jar / content / editor / editorUtilities.js < prev    next >
Encoding:
Text File  |  2003-10-14  |  27.9 KB  |  1,094 lines

  1. /*
  2.  * The contents of this file are subject to the Netscape Public
  3.  * License Version 1.1 (the "License"); you may not use this file
  4.  * except in compliance with the License. You may obtain a copy of
  5.  * the License at http://www.mozilla.org/NPL/
  6.  *
  7.  * Software distributed under the License is distributed on an "AS
  8.  * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
  9.  * implied. See the License for the specific language governing
  10.  * rights and limitations under the License.
  11.  *
  12.  * The Original Code is Mozilla Communicator client code, released
  13.  * March 31, 1998.
  14.  *
  15.  * The Initial Developer of the Original Code is Netscape
  16.  * Communications Corporation. Portions created by Netscape are
  17.  * Copyright (C) 1998-1999 Netscape Communications Corporation. All
  18.  * Rights Reserved.
  19.  *
  20.  * Contributor(s):
  21.  *   Pete Collins
  22.  *   Brian King
  23.  *   Daniel Glazman <glazman@netscape.com>
  24.  */
  25.  
  26. /**** NAMESPACES ****/
  27. const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
  28.  
  29. // Each editor window must include this file
  30. // Variables  shared by all dialogs:
  31.  
  32. // Object to attach commonly-used widgets (all dialogs should use this)
  33. var gDialog = {};
  34.  
  35. // Bummer! Can't get at enums from nsIDocumentEncoder.h
  36. // http://lxr.mozilla.org/seamonkey/source/content/base/public/nsIDocumentEncoder.h#111
  37. var gStringBundle;
  38. var gIOService;
  39. var gPrefsService;
  40. var gPrefsBranch;
  41. var gFilePickerDirectory;
  42.  
  43. var gOS = "";
  44. const gWin = "Win";
  45. const gUNIX = "UNIX";
  46. const gMac = "Mac";
  47.  
  48. const kWebComposerWindowID = "editorWindow";
  49. const kMailComposerWindowID = "msgcomposeWindow";
  50.  
  51. var gIsHTMLEditor;
  52. /************* Message dialogs ***************/
  53.  
  54. function AlertWithTitle(title, message, parentWindow)
  55. {
  56.   if (!parentWindow)
  57.     parentWindow = window;
  58.  
  59.   var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"].getService();
  60.   promptService = promptService.QueryInterface(Components.interfaces.nsIPromptService);
  61.  
  62.   if (promptService)
  63.   {
  64.     if (!title)
  65.       title = GetString("Alert");
  66.  
  67.     // "window" is the calling dialog window
  68.     promptService.alert(parentWindow, title, message);
  69.   }
  70. }
  71.  
  72. // Optional: Caller may supply text to substitue for "Ok" and/or "Cancel"
  73. function ConfirmWithTitle(title, message, okButtonText, cancelButtonText)
  74. {
  75.   var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"].getService();
  76.   promptService = promptService.QueryInterface(Components.interfaces.nsIPromptService);
  77.  
  78.   if (promptService)
  79.   {
  80.     var okFlag = okButtonText ? promptService.BUTTON_TITLE_IS_STRING : promptService.BUTTON_TITLE_OK;
  81.     var cancelFlag = cancelButtonText ? promptService.BUTTON_TITLE_IS_STRING : promptService.BUTTON_TITLE_CANCEL;
  82.  
  83.     return promptService.confirmEx(window, title, message,
  84.                             (okFlag * promptService.BUTTON_POS_0) +
  85.                             (cancelFlag * promptService.BUTTON_POS_1),
  86.                             okButtonText, cancelButtonText, null, null, {value:0}) == 0;
  87.   }
  88.   return false;
  89. }
  90.  
  91. /************* String Utilities ***************/
  92.  
  93. function GetString(name)
  94. {
  95.   if (!gStringBundle)
  96.   {
  97.     try {
  98.       var strBundleService =
  99.           Components.classes["@mozilla.org/intl/stringbundle;1"].getService(); 
  100.       strBundleService = 
  101.           strBundleService.QueryInterface(Components.interfaces.nsIStringBundleService);
  102.  
  103.       gStringBundle = strBundleService.createBundle("chrome://editor/locale/editor.properties"); 
  104.  
  105.     } catch (ex) {}
  106.   }
  107.   if (gStringBundle)
  108.   {
  109.     try {
  110.       return gStringBundle.GetStringFromName(name);
  111.     } catch (e) {}
  112.   }
  113.   return null;
  114. }
  115.  
  116. function TrimStringLeft(string)
  117. {
  118.   if(!string) return "";
  119.   return string.replace(/^\s+/, "");
  120. }
  121.  
  122. function TrimStringRight(string)
  123. {
  124.   if (!string) return "";
  125.   return string.replace(/\s+$/, '');
  126. }
  127.  
  128. // Remove whitespace from both ends of a string
  129. function TrimString(string)
  130. {
  131.   if (!string) return "";
  132.   return string.replace(/(^\s+)|(\s+$)/g, '')
  133. }
  134.  
  135. function IsWhitespace(string)
  136. {
  137.   return /^\s/.test(string);
  138. }
  139.  
  140. function TruncateStringAtWordEnd(string, maxLength, addEllipses)
  141. {
  142.   // Return empty if string is null, undefined, or the empty string
  143.   if (!string)
  144.     return "";
  145.  
  146.   // We assume they probably don't want whitespace at the beginning
  147.   string = string.replace(/^\s+/, '');
  148.   if (string.length <= maxLength)
  149.     return string;
  150.  
  151.   // We need to truncate the string to maxLength or fewer chars
  152.   if (addEllipses)
  153.     maxLength -= 3;
  154.   string = string.replace(RegExp("(.{0," + maxLength + "})\\s.*"), "$1")
  155.  
  156.   if (string.length > maxLength)
  157.     string = string.slice(0, maxLength);
  158.  
  159.   if (addEllipses)
  160.     string += "...";
  161.   return string;
  162. }
  163.  
  164. // Replace all whitespace characters with supplied character
  165. // E.g.: Use charReplace = " ", to "unwrap" the string by removing line-end chars
  166. //       Use charReplace = "_" when you don't want spaces (like in a URL)
  167. function ReplaceWhitespace(string, charReplace)
  168. {
  169.   return string.replace(/(^\s+)|(\s+$)/g,'').replace(/\s+/g,charReplace)
  170. }
  171.  
  172. // Replace whitespace with "_" and allow only HTML CDATA
  173. //   characters: "a"-"z","A"-"Z","0"-"9", "_", ":", "-", ".",
  174. //   and characters above ASCII 127
  175. function ConvertToCDATAString(string)
  176. {
  177.   return string.replace(/\s+/g,"_").replace(/[^a-zA-Z0-9_\.\-\:\u0080-\uFFFF]+/g,'');
  178. }
  179.  
  180. function GetSelectionAsText()
  181. {
  182.   try {
  183.     return GetCurrentEditor().outputToString("text/plain", 1); // OutputSelectionOnly
  184.   } catch (e) {}
  185.  
  186.   return "";
  187. }
  188.  
  189.  
  190. /************* Get Current Editor and associated interfaces or info ***************/
  191. const nsIPlaintextEditor = Components.interfaces.nsIPlaintextEditor;
  192. const nsIHTMLEditor = Components.interfaces.nsIHTMLEditor;
  193. const nsITableEditor = Components.interfaces.nsITableEditor;
  194. const nsIEditorStyleSheets = Components.interfaces.nsIEditorStyleSheets;
  195. const nsIEditingSession = Components.interfaces.nsIEditingSession;
  196.  
  197. function GetCurrentEditor()
  198. {
  199.   // Get the active editor from the <editor> tag
  200.   // XXX This will probably change if we support > 1 editor in main Composer window
  201.   //      (e.g. a plaintext editor for HTMLSource)
  202.  
  203.   // For dialogs: Search up parent chain to find top window with editor
  204.   var editor;
  205.   try {
  206.     var editorElement = GetCurrentEditorElement();
  207.     editor = editorElement.getEditor(editorElement.contentWindow);
  208.  
  209.     // Do QIs now so editor users won't have to figure out which interface to use
  210.     // Using "instanceof" does the QI for us.
  211.     editor instanceof Components.interfaces.nsIPlaintextEditor;
  212.     editor instanceof Components.interfaces.nsIHTMLEditor;
  213.   } catch (e) { dump (e)+"\n"; }
  214.  
  215.   return editor;
  216. }
  217.  
  218. function GetCurrentTableEditor()
  219. {
  220.   var editor = GetCurrentEditor();
  221.   return (editor && (editor instanceof nsITableEditor)) ? editor : null;
  222. }
  223.  
  224. function GetCurrentEditorElement()
  225. {
  226.   var tmpWindow = window;
  227.   
  228.   do {
  229.     // Get the <editor> element(s)
  230.     var editorList = tmpWindow.document.getElementsByTagName("editor");
  231.  
  232.     // This will change if we support > 1 editor element
  233.     if (editorList.item(0))
  234.       return editorList.item(0);
  235.  
  236.     tmpWindow = tmpWindow.opener;
  237.   } 
  238.   while (tmpWindow);
  239.  
  240.   return null;
  241. }
  242.  
  243. function GetCurrentEditingSession()
  244. {
  245.   try {
  246.     return GetCurrentEditorElement().editingSession;
  247.   } catch (e) { dump (e)+"\n"; }
  248.  
  249.   return null;
  250. }
  251.  
  252. function GetCurrentCommandManager()
  253. {
  254.   try {
  255.     return GetCurrentEditorElement().commandManager;
  256.   } catch (e) { dump (e)+"\n"; }
  257.  
  258.   return null;
  259. }
  260.  
  261. function GetCurrentEditorType()
  262. {
  263.   try {
  264.     return GetCurrentEditorElement().editortype;
  265.   } catch (e) { dump (e)+"\n"; }
  266.  
  267.   return "";
  268. }
  269.  
  270. function IsHTMLEditor()
  271. {
  272.   // We don't have an editorElement, just return false
  273.   if (!GetCurrentEditorElement())
  274.     return false;
  275.  
  276.   var editortype = GetCurrentEditorType();
  277.   switch (editortype)
  278.   {
  279.       case "html":
  280.       case "htmlmail":
  281.         return true;
  282.  
  283.       case "text":
  284.       case "textmail":
  285.         return false
  286.  
  287.       default:
  288.         dump("INVALID EDITOR TYPE: " + editortype + "\n");
  289.         break;
  290.   }
  291.   return false;
  292. }
  293.  
  294. function PageIsEmptyAndUntouched()
  295. {
  296.   return IsDocumentEmpty() && !IsDocumentModified() && !IsHTMLSourceChanged();
  297. }
  298.  
  299. function IsInHTMLSourceMode()
  300. {
  301.   return (gEditorDisplayMode == kDisplayModeSource);
  302. }
  303.  
  304. // are we editing HTML (i.e. neither in HTML source mode, nor editing a text file)
  305. function IsEditingRenderedHTML()
  306. {
  307.   return IsHTMLEditor() && !IsInHTMLSourceMode();
  308. }
  309.  
  310. function IsWebComposer()
  311. {
  312.   return document.documentElement.id == "editorWindow";
  313. }
  314.  
  315. function IsDocumentEditable()
  316. {
  317.   try {
  318.     return GetCurrentEditor().isDocumentEditable;
  319.   } catch (e) {}
  320.   return false;
  321. }
  322.  
  323. function IsDocumentEmpty()
  324. {
  325.   try {
  326.     return GetCurrentEditor().documentIsEmpty;
  327.   } catch (e) {}
  328.   return false;
  329. }
  330.  
  331. function IsDocumentModified()
  332. {
  333.   try {
  334.     return GetCurrentEditor().documentModified;
  335.   } catch (e) {}
  336.   return false;
  337. }
  338.  
  339. function IsHTMLSourceChanged()
  340. {
  341.   return gSourceTextEditor.documentModified;
  342. }
  343.  
  344. function newCommandParams()
  345. {
  346.   try {
  347.     return Components.classes["@mozilla.org/embedcomp/command-params;1"].createInstance(Components.interfaces.nsICommandParams);
  348.   }
  349.   catch(e) { dump("error thrown in newCommandParams: "+e+"\n"); }
  350.   return null;
  351. }
  352.  
  353. /************* General editing command utilities ***************/
  354.  
  355. function GetDocumentTitle()
  356. {
  357.   try {
  358.     return new XPCNativeWrapper(GetCurrentEditor().document, "title").title;
  359.   } catch (e) {}
  360.  
  361.   return "";
  362. }
  363.  
  364. function SetDocumentTitle(title)
  365. {
  366.  
  367.   try {
  368.     GetCurrentEditor().setDocumentTitle(title);
  369.  
  370.     // Update window title (doesn't work if called from a dialog)
  371.     if ("UpdateWindowTitle" in window)
  372.       window.UpdateWindowTitle();
  373.   } catch (e) {}
  374. }
  375.  
  376. var gAtomService;
  377. function GetAtomService()
  378. {
  379.   gAtomService = Components.classes["@mozilla.org/atom-service;1"].getService(Components.interfaces.nsIAtomService);
  380. }
  381.  
  382. function EditorGetTextProperty(property, attribute, value, firstHas, anyHas, allHas)
  383. {
  384.   try {
  385.     if (!gAtomService) GetAtomService();
  386.     var propAtom = gAtomService.getAtom(property);
  387.  
  388.     GetCurrentEditor().getInlineProperty(propAtom, attribute, value,
  389.                                          firstHas, anyHas, allHas);
  390.   }
  391.   catch(e) {}
  392. }
  393.  
  394. function EditorSetTextProperty(property, attribute, value)
  395. {
  396.   try {
  397.     if (!gAtomService) GetAtomService();
  398.     var propAtom = gAtomService.getAtom(property);
  399.  
  400.     GetCurrentEditor().setInlineProperty(propAtom, attribute, value);
  401.     if ("gContentWindow" in window)
  402.       window.gContentWindow.focus();
  403.   }
  404.   catch(e) {}
  405. }
  406.  
  407. function EditorRemoveTextProperty(property, attribute)
  408. {
  409.   try {
  410.     if (!gAtomService) GetAtomService();
  411.     var propAtom = gAtomService.getAtom(property);
  412.  
  413.     GetCurrentEditor().removeInlineProperty(propAtom, attribute);
  414.     if ("gContentWindow" in window)
  415.       window.gContentWindow.focus();
  416.   }
  417.   catch(e) {}
  418. }
  419.  
  420. /************* Element enbabling/disabling ***************/
  421.  
  422. // this function takes an elementID and a flag
  423. // if the element can be found by ID, then it is either enabled (by removing "disabled" attr)
  424. // or disabled (setAttribute) as specified in the "doEnable" parameter
  425. function SetElementEnabledById(elementID, doEnable)
  426. {
  427.   SetElementEnabled(document.getElementById(elementID), doEnable);
  428. }
  429.  
  430. function SetElementEnabled(element, doEnable)
  431. {
  432.   if ( element )
  433.   {
  434.     if ( doEnable )
  435.       element.removeAttribute("disabled");
  436.     else
  437.       element.setAttribute("disabled", "true");
  438.   }
  439.   else
  440.   {
  441.     dump("Element  not found in SetElementEnabled\n");
  442.   }
  443. }
  444.  
  445. /************* Services / Prefs ***************/
  446.  
  447. function GetIOService()
  448. {
  449.   if (gIOService)
  450.     return gIOService;
  451.  
  452.   gIOService = Components.classes["@mozilla.org/network/io-service;1"]
  453.                .getService(Components.interfaces.nsIIOService);
  454.  
  455.   return gIOService;
  456. }
  457.  
  458. function GetFileProtocolHandler()
  459. {
  460.   var ios = GetIOService();
  461.   var handler = ios.getProtocolHandler("file");
  462.   return handler.QueryInterface(Components.interfaces.nsIFileProtocolHandler);
  463. }
  464.  
  465. function GetPrefsService()
  466. {
  467.   if (gPrefsService)
  468.     return gPrefsService;
  469.  
  470.   try {
  471.     gPrefsService = Components.classes["@mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefService);
  472.   }
  473.   catch(ex) {
  474.     dump("failed to get prefs service!\n");
  475.   }
  476.  
  477.   return gPrefsService;
  478. }
  479.  
  480. function GetPrefs()
  481. {
  482.   if (gPrefsBranch)
  483.     return gPrefsBranch;
  484.  
  485.   try {
  486.     var prefService = GetPrefsService();
  487.     if (prefService)
  488.       gPrefsBranch = prefService.getBranch(null);
  489.  
  490.     if (gPrefsBranch)
  491.       return gPrefsBranch;
  492.     else
  493.       dump("failed to get root prefs!\n");
  494.   }
  495.   catch(ex) {
  496.     dump("failed to get root prefs!\n");
  497.   }
  498.   return null;
  499. }
  500.  
  501. function GetStringPref(name)
  502. {
  503.   try {
  504.     return GetPrefs().getComplexValue(name, Components.interfaces.nsISupportsString).data;
  505.   } catch (e) {}
  506.   return "";
  507. }
  508.  
  509. function GetBoolPref(name)
  510. {
  511.   try {
  512.     return GetPrefs().getBoolPref(name);
  513.   } catch (e) {}
  514.   return false;
  515. }
  516.  
  517. function SetUnicharPref(aPrefName, aPrefValue)
  518. {
  519.   var prefs = GetPrefs();
  520.   if (prefs)
  521.   {
  522.     try {
  523.       var str = Components.classes["@mozilla.org/supports-string;1"]
  524.                           .createInstance(Components.interfaces.nsISupportsString);
  525.       str.data = aPrefValue;
  526.       prefs.setComplexValue(aPrefName, Components.interfaces.nsISupportsString, str);
  527.     }
  528.     catch(e) {}
  529.   }
  530. }
  531.  
  532. function GetUnicharPref(aPrefName, aDefVal)
  533. {
  534.   var prefs = GetPrefs();
  535.   if (prefs)
  536.   {
  537.     try {
  538.       return prefs.getComplexValue(aPrefName, Components.interfaces.nsISupportsString).data;
  539.     }
  540.     catch(e) {}
  541.   }
  542.   return "";
  543. }
  544.  
  545. // Set initial directory for a filepicker from URLs saved in prefs
  546. function SetFilePickerDirectory(filePicker, fileType)
  547. {
  548.   if (filePicker)
  549.   {
  550.     try {
  551.       var prefBranch = GetPrefs();
  552.       if (prefBranch)
  553.       {
  554.         // Save current directory so we can reset it in SaveFilePickerDirectory
  555.         gFilePickerDirectory = filePicker.displayDirectory;
  556.  
  557.         var location = prefBranch.getComplexValue("editor.lastFileLocation."+fileType, Components.interfaces.nsILocalFile);
  558.         if (location)
  559.           filePicker.displayDirectory = location;
  560.       }
  561.     }
  562.     catch(e) {}
  563.   }
  564. }
  565.  
  566. // Save the directory of the selected file to prefs
  567. function SaveFilePickerDirectory(filePicker, fileType)
  568. {
  569.   if (filePicker && filePicker.file)
  570.   {
  571.     try {
  572.       var prefBranch = GetPrefs();
  573.  
  574.       var fileDir;
  575.       if (filePicker.file.parent)
  576.         fileDir = filePicker.file.parent.QueryInterface(Components.interfaces.nsILocalFile);
  577.  
  578.       if (prefBranch)
  579.        prefBranch.setComplexValue("editor.lastFileLocation."+fileType, Components.interfaces.nsILocalFile, fileDir);
  580.     
  581.       var prefsService = GetPrefsService();
  582.         prefsService.savePrefFile(null);
  583.     } catch (e) {}
  584.   }
  585.  
  586.   // Restore the directory used before SetFilePickerDirectory was called;
  587.   // This reduces interference with Browser and other module directory defaults
  588.   if (gFilePickerDirectory)
  589.     filePicker.displayDirectory = gFilePickerDirectory;
  590.  
  591.   gFilePickerDirectory = null;
  592. }
  593.  
  594. function GetDefaultBrowserColors()
  595. {
  596.   var prefs = GetPrefs();
  597.   var colors = { TextColor:0, BackgroundColor:0, LinkColor:0, ActiveLinkColor:0 , VisitedLinkColor:0 };
  598.   var useSysColors = false;
  599.   try { useSysColors = prefs.getBoolPref("browser.display.use_system_colors"); } catch (e) {}
  600.  
  601.   if (!useSysColors)
  602.   {
  603.     try { colors.TextColor = prefs.getCharPref("browser.display.foreground_color"); } catch (e) {}
  604.  
  605.     try { colors.BackgroundColor = prefs.getCharPref("browser.display.background_color"); } catch (e) {}
  606.   }
  607.   // Use OS colors for text and background if explicitly asked or pref is not set
  608.   if (!colors.TextColor)
  609.     colors.TextColor = "windowtext";
  610.  
  611.   if (!colors.BackgroundColor)
  612.     colors.BackgroundColor = "window";
  613.  
  614.   colors.LinkColor = prefs.getCharPref("browser.anchor_color");
  615.   colors.ActiveLinkColor = prefs.getCharPref("browser.active_color");
  616.   colors.VisitedLinkColor = prefs.getCharPref("browser.visited_color");
  617.  
  618.   return colors;
  619. }
  620.  
  621. /************* URL handling ***************/
  622.  
  623. function TextIsURI(selectedText)
  624. {
  625.   return selectedText && /^http:\/\/|^https:\/\/|^file:\/\/|\
  626.     ^ftp:\/\/|^about:|^mailto:|^news:|^snews:|^telnet:|^ldap:|\
  627.     ^ldaps:|^gopher:|^finger:|^javascript:/i.test(selectedText);
  628. }
  629.  
  630. function IsUrlAboutBlank(urlString)
  631. {
  632.   return (urlString == "about:blank");
  633. }
  634.  
  635. function MakeRelativeUrl(url)
  636. {
  637.   var inputUrl = TrimString(url);
  638.   if (!inputUrl)
  639.     return inputUrl;
  640.  
  641.   // Get the filespec relative to current document's location
  642.   // NOTE: Can't do this if file isn't saved yet!
  643.   var docUrl = GetDocumentBaseUrl();
  644.   var docScheme = GetScheme(docUrl);
  645.  
  646.   // Can't relativize if no doc scheme (page hasn't been saved)
  647.   if (!docScheme)
  648.     return inputUrl;
  649.  
  650.   var urlScheme = GetScheme(inputUrl);
  651.  
  652.   // Do nothing if not the same scheme or url is already relativized
  653.   if (docScheme != urlScheme)
  654.     return inputUrl;
  655.  
  656.   var IOService = GetIOService();
  657.   if (!IOService)
  658.     return inputUrl;
  659.  
  660.   // Host must be the same
  661.   var docHost = GetHost(docUrl);
  662.   var urlHost = GetHost(inputUrl);
  663.   if (docHost != urlHost)
  664.     return inputUrl;
  665.  
  666.  
  667.   // Get just the file path part of the urls
  668.   // XXX Should we use GetCurrentEditor().documentCharacterSet for 2nd param ?
  669.   var docPath = IOService.newURI(docUrl, GetCurrentEditor().documentCharacterSet, null).path;
  670.   var urlPath = IOService.newURI(inputUrl, GetCurrentEditor().documentCharacterSet, null).path;
  671.  
  672.   // We only return "urlPath", so we can convert
  673.   //  the entire docPath for case-insensitive comparisons
  674.   var os = GetOS();
  675.   var doCaseInsensitive = (docScheme == "file" && os == gWin);
  676.   if (doCaseInsensitive)
  677.     docPath = docPath.toLowerCase();
  678.  
  679.   // Get document filename before we start chopping up the docPath
  680.   var docFilename = GetFilename(docPath);
  681.  
  682.   // Both url and doc paths now begin with "/"
  683.   // Look for shared dirs starting after that
  684.   urlPath = urlPath.slice(1);
  685.   docPath = docPath.slice(1);
  686.  
  687.   var firstDirTest = true;
  688.   var nextDocSlash = 0;
  689.   var done = false;
  690.  
  691.   // Remove all matching subdirs common to both doc and input urls
  692.   do {
  693.     nextDocSlash = docPath.indexOf("\/");
  694.     var nextUrlSlash = urlPath.indexOf("\/");
  695.  
  696.     if (nextUrlSlash == -1)
  697.     {
  698.       // We're done matching and all dirs in url
  699.       // what's left is the filename
  700.       done = true;
  701.  
  702.       // Remove filename for named anchors in the same file
  703.       if (nextDocSlash == -1 && docFilename)
  704.       { 
  705.         var anchorIndex = urlPath.indexOf("#");
  706.         if (anchorIndex > 0)
  707.         {
  708.           var urlFilename = doCaseInsensitive ? urlPath.toLowerCase() : urlPath;
  709.         
  710.           if (urlFilename.indexOf(docFilename) == 0)
  711.             urlPath = urlPath.slice(anchorIndex);
  712.         }
  713.       }
  714.     }
  715.     else if (nextDocSlash >= 0)
  716.     {
  717.       // Test for matching subdir
  718.       var docDir = docPath.slice(0, nextDocSlash);
  719.       var urlDir = urlPath.slice(0, nextUrlSlash);
  720.       if (doCaseInsensitive)
  721.         urlDir = urlDir.toLowerCase();
  722.  
  723.       if (urlDir == docDir)
  724.       {
  725.  
  726.         // Remove matching dir+"/" from each path
  727.         //  and continue to next dir
  728.         docPath = docPath.slice(nextDocSlash+1);
  729.         urlPath = urlPath.slice(nextUrlSlash+1);
  730.       }
  731.       else
  732.       {
  733.         // No match, we're done
  734.         done = true;
  735.  
  736.         // Be sure we are on the same local drive or volume 
  737.         //   (the first "dir" in the path) because we can't 
  738.         //   relativize to different drives/volumes.
  739.         // UNIX doesn't have volumes, so we must not do this else
  740.         //  the first directory will be misinterpreted as a volume name
  741.         if (firstDirTest && docScheme == "file" && os != gUNIX)
  742.           return inputUrl;
  743.       }
  744.     }
  745.     else  // No more doc dirs left, we're done
  746.       done = true;
  747.  
  748.     firstDirTest = false;
  749.   }
  750.   while (!done);
  751.  
  752.   // Add "../" for each dir left in docPath
  753.   while (nextDocSlash > 0)
  754.   {
  755.     urlPath = "../" + urlPath;
  756.     nextDocSlash = docPath.indexOf("\/", nextDocSlash+1);
  757.   }
  758.   return urlPath;
  759. }
  760.  
  761. function MakeAbsoluteUrl(url)
  762. {
  763.   var resultUrl = TrimString(url);
  764.   if (!resultUrl)
  765.     return resultUrl;
  766.  
  767.   // Check if URL is already absolute, i.e., it has a scheme
  768.   var urlScheme = GetScheme(resultUrl);
  769.  
  770.   if (urlScheme)
  771.     return resultUrl;
  772.  
  773.   var docUrl = GetDocumentBaseUrl();
  774.   var docScheme = GetScheme(docUrl);
  775.  
  776.   // Can't relativize if no doc scheme (page hasn't been saved)
  777.   if (!docScheme)
  778.     return resultUrl;
  779.  
  780.   var  IOService = GetIOService();
  781.   if (!IOService)
  782.     return resultUrl;
  783.   
  784.   // Make a URI object to use its "resolve" method
  785.   var absoluteUrl = resultUrl;
  786.   var docUri = IOService.newURI(docUrl, GetCurrentEditor().documentCharacterSet, null);
  787.  
  788.   try {
  789.     absoluteUrl = docUri.resolve(resultUrl);
  790.     // This is deprecated and buggy! 
  791.     // If used, we must make it a path for the parent directory (remove filename)
  792.     //absoluteUrl = IOService.resolveRelativePath(resultUrl, docUrl);
  793.   } catch (e) {}
  794.  
  795.   return absoluteUrl;
  796. }
  797.  
  798. // Get the HREF of the page's <base> tag or the document location
  799. // returns empty string if no base href and document hasn't been saved yet
  800. function GetDocumentBaseUrl()
  801. {
  802.   try {
  803.     var docUrl;
  804.  
  805.     // if document supplies a <base> tag, use that URL instead 
  806.     var baseList = GetCurrentEditor().document.getElementsByTagName("base");
  807.     if (baseList)
  808.     {
  809.       var base = baseList.item(0);
  810.       if (base)
  811.         docUrl = base.getAttribute("href");
  812.     }
  813.     if (!docUrl)
  814.       docUrl = GetDocumentUrl();
  815.  
  816.     if (!IsUrlAboutBlank(docUrl))
  817.       return docUrl;
  818.   } catch (e) {}
  819.   return "";
  820. }
  821.  
  822. function GetDocumentUrl()
  823. {
  824.   try {
  825.     var aDOMHTMLDoc = GetCurrentEditor().document.QueryInterface(Components.interfaces.nsIDOMHTMLDocument);
  826.     return aDOMHTMLDoc.URL;
  827.   }
  828.   catch (e) {}
  829.   return "";
  830. }
  831.  
  832. // Extract the scheme (e.g., 'file', 'http') from a URL string
  833. function GetScheme(urlspec)
  834. {
  835.   var resultUrl = TrimString(urlspec);
  836.   // Unsaved document URL has no acceptable scheme yet
  837.   if (!resultUrl || IsUrlAboutBlank(resultUrl))
  838.     return "";
  839.  
  840.   var IOService = GetIOService();
  841.   if (!IOService)
  842.     return "";
  843.  
  844.   var scheme = "";
  845.   try {
  846.     // This fails if there's no scheme
  847.     scheme = IOService.extractScheme(resultUrl);
  848.   } catch (e) {}
  849.  
  850.   return scheme ? scheme.toLowerCase() : "";
  851. }
  852.  
  853. function GetHost(urlspec)
  854. {
  855.   if (!urlspec)
  856.     return "";
  857.  
  858.   var IOService = GetIOService();
  859.   if (!IOService)
  860.     return "";
  861.  
  862.   var host = "";
  863.   try {
  864.     host = IOService.newURI(urlspec, null, null).host;
  865.    } catch (e) {}
  866.  
  867.   return host;
  868. }
  869.  
  870. function GetUsername(urlspec)
  871. {
  872.   if (!urlspec)
  873.     return "";
  874.  
  875.   var IOService = GetIOService();
  876.   if (!IOService)
  877.     return "";
  878.  
  879.   var username = "";
  880.   try {
  881.     username = IOService.newURI(urlspec, null, null).username;
  882.   } catch (e) {}
  883.  
  884.   return username;
  885. }
  886.  
  887. function GetFilename(urlspec)
  888. {
  889.   if (!urlspec || IsUrlAboutBlank(urlspec))
  890.     return "";
  891.  
  892.   var IOService = GetIOService();
  893.   if (!IOService)
  894.     return "";
  895.  
  896.   var filename;
  897.  
  898.   try {
  899.     var uri = IOService.newURI(urlspec, null, null);
  900.     if (uri)
  901.     {
  902.       var url = uri.QueryInterface(Components.interfaces.nsIURL);
  903.       if (url)
  904.         filename = url.fileName;
  905.     }
  906.   } catch (e) {}
  907.  
  908.   return filename ? filename : "";
  909. }
  910.  
  911. // Return the url without username and password
  912. // Optional output objects return extracted username and password strings
  913. // This uses just string routines via nsIIOServices
  914. function StripUsernamePassword(urlspec, usernameObj, passwordObj)
  915. {
  916.   urlspec = TrimString(urlspec);
  917.   if (!urlspec || IsUrlAboutBlank(urlspec))
  918.     return urlspec;
  919.  
  920.   if (usernameObj)
  921.     usernameObj.value = "";
  922.   if (passwordObj)
  923.     passwordObj.value = "";
  924.  
  925.   // "@" must exist else we will never detect username or password
  926.   var atIndex = urlspec.indexOf("@");
  927.   if (atIndex > 0)
  928.   {
  929.     try {
  930.       var IOService = GetIOService();
  931.       if (!IOService)
  932.         return urlspec;
  933.  
  934.       var uri = IOService.newURI(urlspec, null, null);
  935.       var username = uri.username;
  936.       var password = uri.password;
  937.  
  938.       if (usernameObj && username)
  939.         usernameObj.value = username;
  940.       if (passwordObj && password)
  941.         passwordObj.value = password;
  942.       if (username)
  943.       {
  944.         var usernameStart = urlspec.indexOf(username);
  945.         if (usernameStart != -1)
  946.           return urlspec.slice(0, usernameStart) + urlspec.slice(atIndex+1);
  947.       }
  948.     } catch (e) {}
  949.   }
  950.   return urlspec;
  951. }
  952.  
  953. function StripPassword(urlspec, passwordObj)
  954. {
  955.   urlspec = TrimString(urlspec);
  956.   if (!urlspec || IsUrlAboutBlank(urlspec))
  957.     return urlspec;
  958.  
  959.   if (passwordObj)
  960.     passwordObj.value = "";
  961.  
  962.   // "@" must exist else we will never detect password
  963.   var atIndex = urlspec.indexOf("@");
  964.   if (atIndex > 0)
  965.   {
  966.     try {
  967.       var IOService = GetIOService();
  968.       if (!IOService)
  969.         return urlspec;
  970.  
  971.       var password = IOService.newURI(urlspec, null, null).password;
  972.  
  973.       if (passwordObj && password)
  974.         passwordObj.value = password;
  975.       if (password)
  976.       {
  977.         // Find last ":" before "@"
  978.         var colon = urlspec.lastIndexOf(":", atIndex);
  979.         if (colon != -1)
  980.         {
  981.           // Include the "@"
  982.           return urlspec.slice(0, colon) + urlspec.slice(atIndex);
  983.         }
  984.       }
  985.     } catch (e) {}
  986.   }
  987.   return urlspec;
  988. }
  989.  
  990. // Version to use when you have an nsIURI object
  991. function StripUsernamePasswordFromURI(uri)
  992. {
  993.   var urlspec = "";
  994.   if (uri)
  995.   {
  996.     try {
  997.       urlspec = uri.spec;
  998.       var userPass = uri.userPass;
  999.       if (userPass)
  1000.       {
  1001.         start = urlspec.indexOf(userPass);
  1002.         urlspec = urlspec.slice(0, start) + urlspec.slice(start+userPass.length+1);
  1003.       }
  1004.     } catch (e) {}    
  1005.   }
  1006.   return urlspec;
  1007. }
  1008.  
  1009. function InsertUsernameIntoUrl(urlspec, username)
  1010. {
  1011.   if (!urlspec || !username)
  1012.     return urlspec;
  1013.  
  1014.   try {
  1015.     var ioService = GetIOService();
  1016.     var URI = ioService.newURI(urlspec, GetCurrentEditor().documentCharacterSet, null);
  1017.     URI.username = username;
  1018.     return URI.spec;
  1019.   } catch (e) {}
  1020.  
  1021.   return urlspec;
  1022. }
  1023.  
  1024. function GetOS()
  1025. {
  1026.   if (gOS)
  1027.     return gOS;
  1028.  
  1029.   var platform = navigator.platform.toLowerCase();
  1030.  
  1031.   if (platform.indexOf("win") != -1)
  1032.     gOS = gWin;
  1033.   else if (platform.indexOf("mac") != -1)
  1034.     gOS = gMac;
  1035.   else if (platform.indexOf("unix") != -1 || platform.indexOf("linux") != -1 || platform.indexOf("sun") != -1)
  1036.     gOS = gUNIX;
  1037.   else
  1038.     gOS = "";
  1039.   // Add other tests?
  1040.  
  1041.   return gOS;
  1042. }
  1043.  
  1044. function ConvertRGBColorIntoHEXColor(color)
  1045. {
  1046.   if ( /rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)/.test(color) ) {
  1047.     var r = Number(RegExp.$1).toString(16);
  1048.     if (r.length == 1) r = "0"+r;
  1049.     var g = Number(RegExp.$2).toString(16);
  1050.     if (g.length == 1) g = "0"+g;
  1051.     var b = Number(RegExp.$3).toString(16);
  1052.     if (b.length == 1) b = "0"+b;
  1053.     return "#"+r+g+b;
  1054.   }
  1055.   else
  1056.   {
  1057.     return color;
  1058.   }
  1059. }
  1060.  
  1061. /************* CSS ***************/
  1062.  
  1063. function GetHTMLOrCSSStyleValue(element, attrName, cssPropertyName)
  1064. {
  1065.   var prefs = GetPrefs();
  1066.   var IsCSSPrefChecked = prefs.getBoolPref("editor.use_css");
  1067.   var value;
  1068.   if (IsCSSPrefChecked && IsHTMLEditor())
  1069.     value = element.style.getPropertyValue(cssPropertyName);
  1070.  
  1071.   if (!value)
  1072.     value = element.getAttribute(attrName);
  1073.  
  1074.   if (!value)
  1075.     return "";
  1076.  
  1077.   return value;
  1078. }
  1079.  
  1080. /************* Miscellaneous ***************/
  1081. // Clone simple JS objects
  1082. function Clone(obj) 
  1083.   var clone = {};
  1084.   for (var i in obj)
  1085.   {
  1086.     if( typeof obj[i] == 'object')
  1087.       clone[i] = Clone(obj[i]);
  1088.     else
  1089.       clone[i] = obj[i];
  1090.   }
  1091.   return clone;
  1092. }
  1093.