home *** CD-ROM | disk | FTP | other *** search
/ Chip 2002 January / 01_02.iso / software / netscape62win / browser.xpi / bin / chrome / comm.jar / content / editor / EdLinkProps.js < prev    next >
Encoding:
JavaScript  |  2001-09-24  |  16.4 KB  |  546 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.  */
  22.  
  23. var anchorElement = null;
  24. var imageElement = null;
  25. var insertNew = false;
  26. var replaceExistingLink = false;
  27. var insertLinkAtCaret;
  28. var needLinkText = false;
  29. var href;
  30. var newLinkText;
  31. var HNodeArray;
  32. var gHaveNamedAnchors = false;
  33. var gHaveHeadings = false;
  34. var gCanChangeHeadingSelected = true;
  35. var gCanChangeAnchorSelected = true;
  36. var gHaveDocumentUrl = false;
  37. var dialog;
  38.  
  39. // NOTE: Use "href" instead of "a" to distinguish from Named Anchor
  40. // The returned node is has an "a" tagName
  41. var tagName = "href";
  42.  
  43. // dialog initialization code
  44. function Startup()
  45. {
  46.   if (!InitEditorShell())
  47.     return;
  48.  
  49.   doSetOKCancel(onOK, onCancel);
  50.  
  51.   dialog = new Object;
  52.   if (!dialog)
  53.   {
  54.     dump("Failed to create dialog object!!!\n");
  55.     window.close();
  56.     return;
  57.   }
  58.  
  59.   // Message was wrapped in a <label> or <div>, so actual text is a child text node
  60.   dialog.linkTextCaption     = document.getElementById("linkTextCaption");
  61.   dialog.linkTextMessage     = document.getElementById("linkTextMessage");
  62.   dialog.linkTextInput       = document.getElementById("linkTextInput");
  63.   dialog.hrefInput           = document.getElementById("hrefInput");
  64.   dialog.NamedAnchorList     = document.getElementById("NamedAnchorList");
  65.   dialog.HeadingsList        = document.getElementById("HeadingsList");
  66.   dialog.MoreSection         = document.getElementById("MoreSection");
  67.   dialog.MoreFewerButton     = document.getElementById("MoreFewerButton");
  68.   dialog.AdvancedEditSection = document.getElementById("AdvancedEdit");
  69.  
  70.   var selection = editorShell.editorSelection;
  71.   if (selection)
  72.     dump("There is a selection: collapsed = "+selection.isCollapsed+"\n");
  73.   else
  74.     dump("Failed to get selection\n");
  75.  
  76.   // See if we have a single selected image
  77.   imageElement = editorShell.GetSelectedElement("img");
  78.  
  79.   if (imageElement)
  80.   {
  81.     // Get the parent link if it exists -- more efficient than GetSelectedElement()
  82.     anchorElement = editorShell.GetElementOrParentByTagName("href", imageElement);
  83.     if (anchorElement)
  84.     {
  85.       if (anchorElement.childNodes.length > 1)
  86.       {
  87.         // If there are other children, then we want to break
  88.         //  this image away by inserting a new link around it,
  89.         //  so make a new node and copy existing attributes
  90.         anchorElement = anchorElement.cloneNode(false);
  91.         //insertNew = true;
  92.         replaceExistingLink = true;
  93.       }
  94.     }
  95.   }
  96.   else
  97.   {
  98.     // Get an anchor element if caret or
  99.     //   entire selection is within the link.
  100.     anchorElement = editorShell.GetSelectedElement(tagName);
  101.  
  102.     if (anchorElement)
  103.     {
  104.       // Select the entire link
  105.       editorShell.SelectElement(anchorElement);
  106.       selection = editorShell.editorSelection;
  107.     }
  108.     else
  109.     {
  110.       // If selection starts in a link, but extends beyond it,
  111.       //   the user probably wants to extend existing link to new selection,
  112.       //   so check if either end of selection is within a link
  113.       // POTENTIAL PROBLEM: This prevents user from selecting text in an existing
  114.       //   link and making 2 links. 
  115.       // Note that this isn't a problem with images, handled above
  116.  
  117.       anchorElement = editorShell.GetElementOrParentByTagName("href", selection.anchorNode);
  118.       if (!anchorElement)
  119.         anchorElement = editorShell.GetElementOrParentByTagName("href", selection.focusNode);
  120.  
  121.       if (anchorElement)
  122.       {
  123.         // But clone it for reinserting/merging around existing
  124.         //   link that only partially overlaps the selection
  125.         anchorElement = anchorElement.cloneNode(false);
  126.         //insertNew = true;
  127.         replaceExistingLink = true;
  128.       }
  129.     }
  130.   }
  131.  
  132.   if(!anchorElement)
  133.   {
  134.     // No existing link -- create a new one
  135.     anchorElement = editorShell.CreateElementWithDefaults(tagName);
  136.     insertNew = true;
  137.     // Hide message about removing existing link
  138.     document.getElementById("RemoveLinkMsg").setAttribute("hidden","true");
  139.   }
  140.   if(!anchorElement)
  141.   {
  142.     dump("Failed to get selected element or create a new one!\n");
  143.     window.close();
  144.     return;
  145.   } 
  146.  
  147.   // We insert at caret only when nothing is selected
  148.   insertLinkAtCaret = selection.isCollapsed;
  149.   
  150.   var selectedText;
  151.   if (insertLinkAtCaret)
  152.   {
  153.     // Groupbox caption:
  154.     dialog.linkTextCaption.setAttribute("value", GetString("LinkText"));
  155.  
  156.     // Message above input field:
  157.     dialog.linkTextMessage.setAttribute("value", GetString("EnterLinkText"));
  158.   }
  159.   else
  160.   {
  161.     if (!imageElement)
  162.     {
  163.       // We get here if selection is exactly around a link node
  164.       // Check if selection has some text - use that first
  165.       selectedText = GetSelectionAsText();
  166.       if (!selectedText) 
  167.       {
  168.         // No text, look for first image in the selection
  169.         var children = anchorElement.childNodes;
  170.         if (children)
  171.         {
  172.           for(var i=0; i < children.length; i++) 
  173.           {
  174.             var nodeName = children.item(i).nodeName.toLowerCase();
  175.             if (nodeName == "img")
  176.             {
  177.               imageElement = children.item(i);
  178.               break;
  179.             }
  180.           }
  181.         }
  182.       }
  183.     }
  184.     // Set "caption" for link source and the source text or image URL
  185.     if (imageElement)
  186.     {
  187.       dialog.linkTextCaption.setAttribute("value",GetString("LinkImage"));
  188.       // Link source string is the source URL of image
  189.       // TODO: THIS DOESN'T HANDLE MULTIPLE SELECTED IMAGES!
  190.       dialog.linkTextMessage.setAttribute("value",imageElement.src);
  191.     } else {
  192.       dialog.linkTextCaption.setAttribute("value",GetString("LinkText"));
  193.       if (selectedText) 
  194.       {
  195.         // Use just the first 60 characters and add "..."
  196.         dialog.linkTextMessage.setAttribute("value",TruncateStringAtWordEnd(ReplaceWhitespace(selectedText, " "), 60, true));
  197.       } else {
  198.         dialog.linkTextMessage.setAttribute("value",GetString("MixedSelection"));
  199.       }
  200.     }
  201.   }
  202.  
  203.   // Make a copy to use for AdvancedEdit and onSaveDefault
  204.   globalElement = anchorElement.cloneNode(false);
  205.  
  206.   // Get the list of existing named anchors and headings
  207.   FillListboxes();
  208.  
  209.   // We only need to test for this once per dialog load
  210.   gHaveDocumentUrl = GetDocumentBaseUrl();
  211.  
  212.   // Set data for the dialog controls
  213.   InitDialog();
  214.   
  215.   // Search for a URI pattern in the selected text
  216.   //  as candidate href
  217.   selectedText = TrimString(selectedText); 
  218.   if (!dialog.hrefInput.value && TextIsURI(selectedText))
  219.       dialog.hrefInput.value = selectedText;
  220.  
  221.   // Set initial focus
  222.   if (insertLinkAtCaret) {
  223.     // We will be using the HREF inputbox, so text message
  224.     SetTextboxFocus(dialog.linkTextInput);
  225.   } else {
  226.     SetTextboxFocus(dialog.hrefInput);
  227.  
  228.     // We will not insert a new link at caret, so remove link text input field
  229.     dialog.linkTextInput.setAttribute("hidden","true");
  230.     dialog.linkTextInput = null;
  231.   }
  232.  
  233.   InitMoreFewer();
  234.     
  235.   // This sets enable state on OK button
  236.   doEnabling();
  237.  
  238.   SetWindowLocation();
  239. }
  240.  
  241. // Set dialog widgets with attribute data
  242. // We get them from globalElement copy so this can be used
  243. //   by AdvancedEdit(), which is shared by all property dialogs
  244. function InitDialog()
  245. {
  246.   // Must use getAttribute, not "globalElement.href", 
  247.   //  or foreign chars aren't coverted correctly!
  248.   dialog.hrefInput.value = globalElement.getAttribute("href");
  249.  
  250.   // Set "Relativize" checkbox according to current URL state
  251.   SetRelativeCheckbox();
  252. }
  253.  
  254. function chooseFile()
  255. {
  256.   // Get a local file, converted into URL format
  257.   var fileName = GetLocalFileURL("html");
  258.   if (fileName) 
  259.   {
  260.     // Always try to relativize local file URLs
  261.     if (gHaveDocumentUrl)
  262.       fileName = MakeRelativeUrl(fileName);
  263.  
  264.     dialog.hrefInput.value = fileName;
  265.  
  266.     SetRelativeCheckbox();
  267.     doEnabling();
  268.   }
  269.   // Put focus into the input field
  270.   SetTextboxFocus(dialog.hrefInput);
  271. }
  272.  
  273. function FillListboxes()
  274. {
  275.   var NamedAnchorNodeList = editorShell.editorDocument.anchors;
  276.   var NamedAnchorCount = NamedAnchorNodeList.length;
  277.   var item;
  278.   if (NamedAnchorCount > 0)
  279.   {
  280.     for (var i = 0; i < NamedAnchorCount; i++)
  281.       AppendStringToTreelist(dialog.NamedAnchorList, NamedAnchorNodeList.item(i).name);
  282.  
  283.     gHaveNamedAnchors = true;
  284.   } 
  285.   else 
  286.   {
  287.     // Message to tell user there are none
  288.     item = AppendStringToTreelistById(dialog.NamedAnchorList, "NoNamedAnchors");
  289.     if (item) item.setAttribute("disabled", "true");
  290.   }
  291.   var firstHeading = true;
  292.   for (var j = 1; j <= 6; j++)
  293.   {
  294.     var headingList = editorShell.editorDocument.getElementsByTagName("h"+String(j));
  295.     if (headingList.length > 0)
  296.     {
  297.       var heading = headingList.item(0);
  298.  
  299.       // Skip headings that already have a named anchor as their first child
  300.       //  (this may miss nearby anchors, but at least we don't insert another
  301.       //   under the same heading)
  302.       var child = heading.firstChild;
  303.       if (child && child.nodeName == "A" && child.name && (child.name.length>0))
  304.         continue;
  305.  
  306.       var range = editorShell.editorDocument.createRange();
  307.       range.setStart(heading,0);
  308.       var lastChildIndex = heading.childNodes.length;
  309.       range.setEnd(heading,lastChildIndex);
  310.       var text = range.toString();
  311.       if (text)
  312.       {
  313.         // Use just first 40 characters, don't add "...",
  314.         //  and replace whitespace with "_" and strip non-word characters
  315.         text = ConvertToCDATAString(TruncateStringAtWordEnd(text, 40, false));
  316.         // Append "_" to any name already in the list
  317.         if (GetExistingHeadingIndex(text) > -1)
  318.           text += "_";
  319.         AppendStringToTreelist(dialog.HeadingsList, text);
  320.  
  321.         // Save nodes in an array so we can create anchor node under it later
  322.         if (!HNodeArray)
  323.           HNodeArray = new Array(heading)
  324.         else
  325.           HNodeArray[HNodeArray.length] = heading;
  326.       }
  327.     }
  328.   }
  329.   if (HNodeArray)
  330.   {
  331.     gHaveHeadings = true;
  332.   } else {
  333.     // Message to tell user there are none
  334.     item = AppendStringToTreelistById(dialog.HeadingsList, "NoHeadings");
  335.     if (item) item.setAttribute("disabled", "true");
  336.   }
  337. }
  338.  
  339. function doEnabling()
  340. {
  341.   // We disable Ok button when there's no href text only if inserting a new link
  342.   var enable = insertNew ? (dialog.hrefInput.value.trimString().length > 0) : true;
  343.  
  344.   SetElementEnabledById( "ok", enable);
  345. }
  346.  
  347. var gClearListSelections = true;
  348.  
  349. function ChangeLocation()
  350. {
  351.   if (gClearListSelections)
  352.   {
  353.     // Unselect the treelists
  354.     UnselectNamedAnchor();
  355.     UnselectHeadings();
  356.   }  
  357.  
  358.   SetRelativeCheckbox();
  359.  
  360.   // Set OK button enable state
  361.   doEnabling();
  362. }
  363.  
  364. function GetExistingHeadingIndex(text)
  365. {
  366.   var len = dialog.HeadingsList.getAttribute("length");
  367.   for (var i=0; i < len; i++)
  368.   {
  369.     if (GetTreelistValueAt(dialog.HeadingsList, i) == text)
  370.       return i;
  371.   }
  372.   return -1;
  373. }
  374.  
  375. function SelectNamedAnchor()
  376. {
  377.   if (gCanChangeAnchorSelected)
  378.   {
  379.     if (gHaveNamedAnchors)
  380.     {
  381.       // Prevent ChangeLocation() from unselecting the list
  382.       gClearListSelections = false;
  383.       dialog.hrefInput.value = "#"+GetSelectedTreelistValue(dialog.NamedAnchorList);
  384.       gClearListSelections = true;
  385.  
  386.       SetRelativeCheckbox();
  387.  
  388.       // ChangeLocation isn't always called, so be sure Ok is enabled
  389.       doEnabling();
  390.     }
  391.     else
  392.       UnselectNamedAnchor();
  393.   
  394.     UnselectHeadings();
  395.   }
  396. }
  397.  
  398. function SelectHeading()
  399. {
  400.   if (gCanChangeHeadingSelected)
  401.   {
  402.     if (gHaveHeadings)
  403.     {
  404.       gClearListSelections = false;
  405.       dialog.hrefInput.value = "#"+GetSelectedTreelistValue(dialog.HeadingsList);
  406.       gClearListSelections = true;
  407.  
  408.       SetRelativeCheckbox();
  409.       doEnabling();
  410.     }
  411.     else
  412.       UnselectHeadings();
  413.  
  414.     UnselectNamedAnchor();
  415.   }
  416. }
  417.  
  418. function UnselectNamedAnchor()
  419. {
  420.   // Prevent recursive calling of SelectNamedAnchor()
  421.   gCanChangeAnchorSelected = false;
  422.   dialog.NamedAnchorList.selectedIndex = -1;  
  423.   gCanChangeAnchorSelected = true;
  424. }
  425.  
  426. function UnselectHeadings()
  427. {
  428.   // Prevent recursive calling of SelectHeading()
  429.   gCanChangeHeadingSelected = false;
  430.   dialog.HeadingsList.selectedIndex = -1;  
  431.   gCanChangeHeadingSelected = true;
  432. }
  433.  
  434. // Get and validate data from widgets.
  435. // Set attributes on globalElement so they can be accessed by AdvancedEdit()
  436. function ValidateData()
  437. {
  438.   href = dialog.hrefInput.value.trimString();
  439.   if (href)
  440.   {
  441.     // Set the HREF directly on the editor document's anchor node
  442.     //  or on the newly-created node if insertNew is true
  443.     globalElement.setAttribute("href",href);
  444.   }
  445.   else if (insertNew)
  446.   {
  447.     // We must have a URL to insert a new link
  448.     //NOTE: We accept an empty HREF on existing link to indicate removing the link
  449.     ShowInputErrorMessage(GetString("EmptyHREFError"));
  450.     return false;
  451.   }
  452.   if (dialog.linkTextInput)
  453.   {
  454.     // The text we will insert isn't really an attribute,
  455.     //  but it makes sense to validate it
  456.     newLinkText = TrimString(dialog.linkTextInput.value);
  457.     if (!newLinkText)
  458.     {
  459.       if (href)
  460.         newLinkText = href
  461.       else
  462.       {
  463.         ShowInputErrorMessage(GetString("EmptyLinkTextError"));
  464.         SetTextboxFocus(dialog.linkTextInput);
  465.         return false;
  466.       }
  467.     }
  468.   }
  469.   return true;
  470. }
  471.  
  472. function doHelpButton()
  473. {
  474.   openHelp("chrome://help/content/help.xul?link_properties");
  475. }
  476.  
  477. function onOK()
  478. {
  479.   if (ValidateData())
  480.   {
  481.     if (href.length > 0)
  482.     {
  483.       // Copy attributes to element we are changing or inserting
  484.       editorShell.CloneAttributes(anchorElement, globalElement);
  485.  
  486.       // Coalesce into one undo transaction
  487.       editorShell.BeginBatchChanges();
  488.  
  489.       // Get text to use for a new link
  490.       if (insertLinkAtCaret)
  491.       {
  492.         // Append the link text as the last child node 
  493.         //   of the anchor node
  494.         var textNode = editorShell.editorDocument.createTextNode(newLinkText);
  495.         if (textNode)
  496.           anchorElement.appendChild(textNode);
  497.         try {
  498.           editorShell.InsertElementAtSelection(anchorElement, false);
  499.         } catch (e) {
  500.           dump("Exception occured in InsertElementAtSelection\n");
  501.           return true;
  502.         }
  503.       } else if (insertNew || replaceExistingLink)
  504.       {
  505.         //  Link source was supplied by the selection,
  506.         //  so insert a link node as parent of this
  507.         //  (may be text, image, or other inline content)
  508.         try {
  509.           editorShell.InsertLinkAroundSelection(anchorElement);
  510.         } catch (e) {
  511.           dump("Exception occured in InsertElementAtSelection\n");
  512.           return true;
  513.         }
  514.       }
  515.       // Check if the link was to a heading 
  516.       if (href[0] == "#")
  517.       {
  518.         var name = href.substr(1);
  519.         var index = GetExistingHeadingIndex(name);
  520.         if (index >= 0) {
  521.           // We need to create a named anchor 
  522.           //  and insert it as the first child of the heading element
  523.           var headNode = HNodeArray[index];
  524.           var anchorNode = editorShell.editorDocument.createElement("a");
  525.           if (anchorNode) {
  526.             anchorNode.name = name;
  527.             // Remember to use editorShell method so it is undoable!
  528.             editorShell.InsertElement(anchorNode, headNode, 0, false);
  529.           }
  530.         } else {
  531.           dump("HREF is a heading but is not in the list!\n");
  532.         }
  533.       }
  534.       editorShell.EndBatchChanges();
  535.     } 
  536.     else if (!insertNew)
  537.     {
  538.       // We already had a link, but empty HREF means remove it
  539.       editorShell.RemoveTextProperty("a", "");
  540.     }
  541.     SaveWindowLocation();
  542.     return true;
  543.   }
  544.   return false;
  545. }
  546.