/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* ***** BEGIN LICENSE BLOCK ***** * Version: NPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Netscape Public License * Version 1.1 (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * http://www.mozilla.org/NPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is mozilla.org code. * * The Initial Developer of the Original Code is * Netscape Communications Corporation. * Portions created by the Initial Developer are Copyright (C) 1998-1999 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Sammy Ford (sford@swbell.net) * Dan Haddix (dan6992@hotmail.com) * John Ratke (jratke@owc.net) * Ryan Cassin (rcassin@supernova.org) * Daniel Glazman (glazman@netscape.com) * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the NPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the NPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ /* Main Composer window UI control */ var gEditor; var editorShell; // XXX THIS NEEDS TO DIE var documentModified; var prefAuthorString = ""; var NormalMode = 1; var PreviewMode = 2; // These must match enums in nsIEditorShell.idl: var DisplayModePreview = 0; var DisplayModeNormal = 1; var DisplayModeAllTags = 2; var DisplayModeSource = 3; var PreviousNonSourceDisplayMode = 1; var gEditorDisplayMode = 1; // Normal Editor mode var WebCompose = false; // Set true for Web Composer, leave false for Messenger Composer var docWasModified = false; // Check if clean document, if clean then unload when user "Opens" var gContentWindow = 0; var gSourceContentWindow = 0; var gHTMLSourceChanged = false; var gContentWindowDeck; var gFormatToolbar; var gFormatToolbarHidden = false; var gFormatToolbarCollapsed; var gEditModeBar; var gNormalModeButton; var gTagModeButton; var gSourceModeButton; var gPreviewModeButton; var gColorObj = { LastTextColor:"", LastBackgroundColor:"", LastHighlightColor:"", Type:"", SelectedType:"", NoDefault:false, Cancel:false, HighlightColor:"", BackgroundColor:"", PageColor:"", TextColor:"", TableColor:"", CellColor:"" }; var gDefaultTextColor = ""; var gDefaultBackgroundColor = ""; var gCSSPrefListener; var gPrefs; // These must be kept in synch with the XUL lists var gFontSizeNames = ["xx-small","x-small","small","medium","large","x-large","xx-large"]; const nsIFilePicker = Components.interfaces.nsIFilePicker; function nsButtonPrefListener() { try { var pbi = pref.QueryInterface(Components.interfaces.nsIPrefBranchInternal); pbi.addObserver(this.domain, this, false); } catch(ex) { dump("Failed to observe prefs: " + ex + "\n"); } } // implements nsIObserver nsButtonPrefListener.prototype = { domain: "editor.use_css", observe: function(subject, topic, prefName) { if (!isHTMLEditor()) return; // verify that we're changing a button pref if (topic != "nsPref:changed") return; if (prefName.substr(0, this.domain.length) != this.domain) return; var cmd = document.getElementById("cmd_highlight"); if (cmd) { var prefs = GetPrefs(); var useCSS = prefs.getBoolPref(prefName); if (useCSS && gEditor) { var mixedObj = {}; var state = gEditor.getHighlightColorState(mixedObj); cmd.setAttribute("state", state); cmd.removeAttribute("collapsed"); } else { cmd.setAttribute("state", "transparent"); cmd.setAttribute("collapsed", "true"); } if (gEditor) gEditor.isCSSEnabled = useCSS; } } } function AfterHighlightColorChange() { if (!isHTMLEditor()) return; var button = document.getElementById("cmd_highlight"); if (button) { var mixedObj = {}; var state = gEditor.getHighlightColorState(mixedObj); button.setAttribute("state", state); onHighlightColorChange(); } } function EditorOnLoad() { // See if argument was passed. if ( window.arguments && window.arguments[0] ) { // Opened via window.openDialog with URL as argument. // Put argument where EditorStartup expects it. document.getElementById( "args" ).setAttribute( "value", window.arguments[0] ); } // get default character set if provided if ("arguments" in window && window.arguments.length > 1 && window.arguments[1]) { if (window.arguments[1].indexOf("charset=") != -1) { var arrayArgComponents = window.arguments[1].split("="); if (arrayArgComponents) { // Put argument where EditorStartup expects it. document.getElementById( "args" ).setAttribute("charset", arrayArgComponents[1]); } } } WebCompose = true; window.tryToClose = EditorCanClose; // Continue with normal startup. EditorStartup('html', document.getElementById("content-frame")); } function TextEditorOnLoad() { // See if argument was passed. if ( window.arguments && window.arguments[0] ) { // Opened via window.openDialog with URL as argument. // Put argument where EditorStartup expects it. document.getElementById( "args" ).setAttribute( "value", window.arguments[0] ); } // Continue with normal startup. EditorStartup('text', document.getElementById("content-frame")); } // This should be called by all editor users when they close their window // or other similar "done with editor" actions, like recycling a Mail Composer window. function EditorCleanup() { SwitchInsertCharToAnotherEditorOrClose(); } function PageIsEmptyAndUntouched() { return (gEditor != null) && gEditor.documentIsEmpty && !docWasModified && !gHTMLSourceChanged; } function IsInHTMLSourceMode() { return (gEditorDisplayMode == DisplayModeSource); } // are we editing HTML (i.e. neither in HTML source mode, nor editing a text file) function IsEditingRenderedHTML() { return isHTMLEditor() && !IsInHTMLSourceMode(); } var DocumentReloadListener = { NotifyDocumentCreated: function() {}, NotifyDocumentWillBeDestroyed: function() {}, NotifyDocumentStateChanged:function( isNowDirty ) { var charset = gEditor.documentCharacterSet; // unregister the listener to prevent multiple callbacks gEditor.removeDocumentStateListener( DocumentReloadListener ); // update the META charset with the current presentation charset gEditor.documentCharacterSet = charset; } }; function addEditorClickEventListener() { try { var bodyelement = GetBodyElement(); if (bodyelement) bodyelement.addEventListener("click", EditorClick, false); } catch (e) {} } var MessageComposeDocumentStateListener = { NotifyDocumentCreated: function() { gEditor = editorShell.editor; // do all of our QI'ing here so we don't need to do it elsewhere DoAllQueryInterfaceOnEditor(); addEditorClickEventListener(); }, NotifyDocumentWillBeDestroyed: function() { // note that the editor seems to be gone at this point // so we don't have a way to remove the click listener. // hopefully it is being cleaned up with all listeners }, NotifyDocumentStateChanged:function() { } }; function DoAllQueryInterfaceOnEditor() { // do QI here so the interfaces will be accessible on gEditor try { gEditor.QueryInterface(Components.interfaces.nsIHTMLEditor); } catch(e) {} try { gEditor.QueryInterface(Components.interfaces.nsIPlaintextEditor); } catch(e) {} try { gEditor.QueryInterface(Components.interfaces.nsITableEditor); } catch(e) {} try { gEditor.QueryInterface(Components.interfaces.nsIEditorStyleSheets); } catch(e) {} } // This is called when the real editor document is created, // before it's loaded. var DocumentStateListener = { NotifyDocumentCreated: function() { gEditor = editorShell.editor; // do all of our QI'ing here so we don't need to do it elsewhere DoAllQueryInterfaceOnEditor(); // Call EditorSetDefaultPrefsAndDoctype first so it gets the default author before initing toolbars EditorSetDefaultPrefsAndDoctype(); EditorInitToolbars(); BuildRecentMenu(true); // Build the recent files menu and save to prefs // Just for convenience gContentWindow = window._content; gContentWindow.focus(); // udpate menu items now that we have an editor to play with // Note: This must be AFTER gContentWindow.focus(); window.updateCommands("create"); if (!("InsertCharWindow" in window)) window.InsertCharWindow = null; // We must wait until document is created to get proper Url // (Windows may load with local file paths) SetSaveAndPublishUI(GetDocumentUrl()); // Add mouse click watcher if right type of editor if (isHTMLEditor()) addEditorClickEventListener(); }, // note that the editor seems to be gone at this point // so we don't have a way to remove the click listener. // hopefully it is being cleaned up with all listeners NotifyDocumentWillBeDestroyed: function() {}, NotifyDocumentStateChanged:function( isNowDirty ) { /* Notify our dirty detector so this window won't be closed if another document is opened */ if (isNowDirty) docWasModified = true; // hack! Should not need this updateCommands, but there is some controller // bug that this works around. ?? // comment out the following line because it cause 41573 IME problem on Mac //gContentWindow.focus(); window.updateCommands("create"); window.updateCommands("save"); } }; function EditorStartup(editorType, editorElement) { gIsHTMLEditor = (editorType == "html"); if (gIsHTMLEditor) { gSourceContentWindow = document.getElementById("content-source"); gEditModeBar = document.getElementById("EditModeToolbar"); gNormalModeButton = document.getElementById("NormalModeButton"); gTagModeButton = document.getElementById("TagModeButton"); gSourceModeButton = document.getElementById("SourceModeButton"); gPreviewModeButton = document.getElementById("PreviewModeButton"); // mark first tab as selected document.getElementById("EditModeTabs").selectedTab = gNormalModeButton; // XUL elements we use when switching from normal editor to edit source gContentWindowDeck = document.getElementById("ContentWindowDeck"); gFormatToolbar = document.getElementById("FormatToolbar"); } // store the editor shell in the window, so that child windows can get to it. // XXX This needs to go, but first, all the dialogs need to be changed // not to require it. editorShell = editorElement.editorShell; // this pattern exposes a JS/XBL bug that causes leaks editorShell.editorType = editorType; editorShell.webShellWindow = window; editorShell.contentWindow = window._content; // set up our global prefs object GetPrefsService(); // Startup also used by other editor users, such as Message Composer EditorSharedStartup(); // Commands specific to the Composer Application window, // (i.e., not embedded editors) // such as file-related commands, HTML Source editing, Edit Modes... SetupComposerWindowCommands(); gCSSPrefListener = new nsButtonPrefListener(); // hide Highlight button if we are in an HTML editor with CSS mode off var cmd = document.getElementById("cmd_highlight"); if (cmd) { var prefs = GetPrefs(); var useCSS = prefs.getBoolPref("editor.use_css"); if (!useCSS && gIsHTMLEditor) { cmd.setAttribute("collapsed", "true"); } } // Get url for editor content and load it. // the editor gets instantiated by the editor shell when the URL has finished loading. var url = document.getElementById("args").getAttribute("value"); var charset = document.getElementById("args").getAttribute("charset"); // XXX We can't call gEditor.documentCharacterSet = charset // XXX because gEditor isn't set until after the document loads. if (charset) editorShell.SetDocumentCharacterSet(charset); // We need the docshell to do a loadurl! // editorshell gets it from the window in PrepareDocumentForEditing. editorShell.LoadUrl(url); } // This is also called by Message Composer function EditorSharedStartup() { // Just for convenience gContentWindow = window._content; switch (editorShell.editorType) { case "html": case "htmlmail": gIsHTMLEditor = true; break; case "text": case "textmail": gIsHTMLEditor = false; break; default: dump("INVALID EDITOR TYPE: " + editorShell.editorType + "\n"); gIsHTMLEditor = false; break; } // Set up the mime type and register the commands. // We don't have an editor yet -- in fact, this is the listener which // will tell us when it's time to create the editor. // So we can't use gEditor.AddDocumentStateListener here. if (gIsHTMLEditor) { editorShell.contentsMIMEType = "text/html"; SetupHTMLEditorCommands(); } else { editorShell.contentsMIMEType = "text/plain"; SetupTextEditorCommands(); } // add a listener to be called when document is really done loading if (editorShell.editorType == "htmlmail" || editorShell.editorType == "textmail") editorShell.RegisterDocumentStateListener( MessageComposeDocumentStateListener ); else editorShell.RegisterDocumentStateListener( DocumentStateListener ); var isMac = (GetOS() == gMac); // Set platform-specific hints for how to select cells // Mac uses "Cmd", all others use "Ctrl" var tableKey = GetString(isMac ? "XulKeyMac" : "TableSelectKey"); var dragStr = tableKey+GetString("Drag"); var clickStr = tableKey+GetString("Click"); var delStr = GetString(isMac ? "Clear" : "Del"); SafeSetAttribute("menu_SelectCell", "acceltext", clickStr); SafeSetAttribute("menu_SelectRow", "acceltext", dragStr); SafeSetAttribute("menu_SelectColumn", "acceltext", dragStr); SafeSetAttribute("menu_SelectAllCells", "acceltext", dragStr); // And add "Del" or "Clear" SafeSetAttribute("menu_DeleteCellContents", "acceltext", delStr); // Set text for indent, outdent keybinding // hide UI that we don't have components for RemoveInapplicableUIElements(); gPrefs = GetPrefs(); // Use browser colors as initial values for editor's default colors var BrowserColors = GetDefaultBrowserColors(); if (BrowserColors) { gDefaultTextColor = BrowserColors.TextColor; gDefaultBackgroundColor = BrowserColors.BackgroundColor; } // For new window, no default last-picked colors gColorObj.LastTextColor = ""; gColorObj.LastBackgroundColor = ""; gColorObj.LastHighlightColor = ""; } // This method is only called by Message composer when recycling a compose window function EditorResetFontAndColorAttributes() { document.getElementById("cmd_fontFace").setAttribute("state", ""); EditorRemoveTextProperty("font", "color"); EditorRemoveTextProperty("font", "bgcolor"); EditorRemoveTextProperty("font", "size"); EditorRemoveTextProperty("small", ""); EditorRemoveTextProperty("big", ""); var bodyelement = GetBodyElement(); if (bodyelement) { bodyelement.removeAttribute("text"); bodyelement.removeAttribute("bgcolor"); bodyelement.removeAttribute("link"); bodyelement.removeAttribute("alink"); bodyelement.removeAttribute("vlink"); bodyelement.removeAttribute("background"); } gColorObj.LastTextColor = ""; gColorObj.LastBackgroundColor = ""; gColorObj.LastHighlightColor = ""; document.getElementById("cmd_fontColor").setAttribute("state", ""); document.getElementById("cmd_backgroundColor").setAttribute("state", ""); UpdateDefaultColors(); } function _EditorNotImplemented() { dump("Function not implemented\n"); } function EditorShutdown() { // nothing to do. Shutdown is called by the nsEditorBoxObject } function SafeSetAttribute(nodeID, attributeName, attributeValue) { var theNode = document.getElementById(nodeID); if (theNode) theNode.setAttribute(attributeName, attributeValue); } function DocumentHasBeenSaved() { var fileurl = ""; try { fileurl = GetDocumentUrl(); } catch (e) { return false; } if (!fileurl || IsUrlAboutBlank(fileurl)) return false; // We have a file URL already return true; } function CheckAndSaveDocument(command, allowDontSave) { var document; try { // if we don't have an editor or an document, bail if (!gEditor) return true; document = gEditor.document; if (!document) return true; } catch (e) { return true; } if (!gEditor.documentModified && !gHTMLSourceChanged) return true; // call window.focus, since we need to pop up a dialog // and therefore need to be visible (to prevent user confusion) window.focus(); var scheme = GetScheme(GetDocumentUrl()); var doPublish = (scheme && scheme != "file"); var strID; switch (command) { case "cmd_close": strID = "BeforeClosing"; break; case "cmd_preview": strID = "BeforePreview"; break; case "cmd_editSendPage": strID = "SendPageReason"; break; case "cmd_validate": strID = "BeforeValidate"; break; } var reasonToSave = strID ? GetString(strID) : ""; var title = document.title; if (!title) title = GetString("untitled"); var dialogTitle = GetString(doPublish ? "PublishPage" : "SaveDocument"); var dialogMsg = GetString(doPublish ? "PublishPrompt" : "SaveFilePrompt"); dialogMsg = (dialogMsg.replace(/%title%/,title)).replace(/%reason%/,reasonToSave); var promptService = GetPromptService(); if (!promptService) return false; var result = {value:0}; var promptFlags = promptService.BUTTON_TITLE_CANCEL * promptService.BUTTON_POS_1; var button1Title = null; var button3Title = null; if (doPublish) { promptFlags += promptService.BUTTON_TITLE_IS_STRING * promptService.BUTTON_POS_0; button1Title = GetString("Publish"); button3Title = GetString("DontPublish"); } else { promptFlags += promptService.BUTTON_TITLE_SAVE * promptService.BUTTON_POS_0; } // If allowing "Don't..." button, add that if (allowDontSave) promptFlags += doPublish ? (promptService.BUTTON_TITLE_IS_STRING * promptService.BUTTON_POS_2) : (promptService.BUTTON_TITLE_DONT_SAVE * promptService.BUTTON_POS_2); result = promptService.confirmEx(window, dialogTitle, dialogMsg, promptFlags, button1Title, null, button3Title, null, {value:0}); if (result == 0) { // Save, but first finish HTML source mode if (gHTMLSourceChanged) { try { FinishHTMLSource(); } catch (e) { return false;} } if (doPublish) { // We save the command the user wanted to do in a global // and return as if user canceled because publishing is asynchronous // This command will be fired when publishing finishes gCommandAfterPublishing = command; goDoCommand("cmd_publish"); return false; } // Save to local disk var contentsMIMEType; if (isHTMLEditor()) contentsMIMEType = "text/html"; else contentsMIMEType = "text/plain"; var success = SaveDocument(false, false, contentsMIMEType); return success; } if (result == 2) // "Don't Save" return true; // Default or result == 1 (Cancel) return false; } // --------------------------- File menu --------------------------- // used by openLocation. see openLocation.js for additional notes. function delayedOpenWindow(chrome, flags, url) { dump("setting timeout\n"); setTimeout("window.openDialog('"+chrome+"','_blank','"+flags+"','"+url+"')", 10); } function EditorNewPlaintext() { window.openDialog( "chrome://editor/content/TextEditorAppShell.xul", "_blank", "chrome,dialog=no,all", "about:blank"); } // Check for changes to document and allow saving before closing // This is hooked up to the OS's window close widget (e.g., "X" for Windows) function EditorCanClose() { // Returns FALSE only if user cancels save action // "true" means allow "Don't Save" button var canClose = CheckAndSaveDocument("cmd_close", true); // This is our only hook into closing via the "X" in the caption // or "Quit" (or other paths?) // so we must shift association to another // editor or close any non-modal windows now if (canClose && "InsertCharWindow" in window && window.InsertCharWindow) SwitchInsertCharToAnotherEditorOrClose(); return canClose; } // --------------------------- View menu --------------------------- function EditorSetDocumentCharacterSet(aCharset) { if (gEditor) { gEditor.documentCharacterSet = aCharset; var docUrl = GetDocumentUrl(); if( !IsUrlAboutBlank(docUrl)) { // reloading the document will reverse any changes to the META charset, // we need to put them back in, which is achieved by a dedicated listener gEditor.AddDocumentStateListener( DocumentReloadListener ); editorShell.LoadUrl(docUrl); } } } // ------------------------------------------------------------------ function updateCharsetPopupMenu(menuPopup) { if (gEditor.documentModified && !gEditor.documentIsEmpty) { for (var i = 0; i < menuPopup.childNodes.length; i++) { var menuItem = menuPopup.childNodes[i]; menuItem.setAttribute('disabled', 'true'); } } } // --------------------------- Text style --------------------------- function onParagraphFormatChange(paraMenuList, commandID) { if (!paraMenuList) return; var commandNode = document.getElementById(commandID); var state = commandNode.getAttribute("state"); // force match with "normal" if (state == "body") state = ""; if (state == "mixed") { //Selection is the "mixed" ( > 1 style) state paraMenuList.selectedItem = null; paraMenuList.setAttribute("label",GetString('Mixed')); } else { var menuPopup = document.getElementById("ParagraphPopup"); var menuItems = menuPopup.childNodes; for (var i=0; i < menuItems.length; i++) { var menuItem = menuItems.item(i); if ("value" in menuItem && menuItem.value == state) { paraMenuList.selectedItem = menuItem; break; } } } } function doStatefulCommand(commandID, newState) { var commandNode = document.getElementById(commandID); if (commandNode) commandNode.setAttribute("state", newState); gContentWindow.focus(); // needed for command dispatch to work goDoCommand(commandID); } function onFontFaceChange(fontFaceMenuList, commandID) { var commandNode = document.getElementById(commandID); var state = commandNode.getAttribute("state"); if (state == "mixed") { //Selection is the "mixed" ( > 1 style) state fontFaceMenuList.selectedItem = null; fontFaceMenuList.setAttribute("label",GetString('Mixed')); } else { var menuPopup = document.getElementById("FontFacePopup"); var menuItems = menuPopup.childNodes; for (var i=0; i < menuItems.length; i++) { var menuItem = menuItems.item(i); if (menuItem.getAttribute("label") && ("value" in menuItem && menuItem.value.toLowerCase() == state.toLowerCase())) { fontFaceMenuList.selectedItem = menuItem; break; } } } } function EditorSelectFontSize() { var select = document.getElementById("FontSizeSelect"); if (select) { if (select.selectedIndex == -1) return; EditorSetFontSize(gFontSizeNames[select.selectedIndex]); } } function onFontSizeChange(fontSizeMenulist, commandID) { // If we don't match anything, set to "0 (normal)" var newIndex = 2; var size = fontSizeMenulist.getAttribute("size"); if ( size == "mixed") { // No single type selected newIndex = -1; } else { for (var i = 0; i < gFontSizeNames.length; i++) { if( gFontSizeNames[i] == size ) { newIndex = i; break; } } } if (fontSizeMenulist.selectedIndex != newIndex) fontSizeMenulist.selectedIndex = newIndex; } function EditorSetFontSize(size) { if( size == "0" || size == "normal" || size == "medium" ) { EditorRemoveTextProperty("font", "size"); // Also remove big and small, // else it will seem like size isn't changing correctly EditorRemoveTextProperty("small", ""); EditorRemoveTextProperty("big", ""); } else { // Temp: convert from new CSS size strings to old HTML size strings switch (size) { case "xx-small": case "x-small": size = "-2"; break; case "small": size = "-1"; break; case "large": size = "+1"; break; case "x-large": size = "+2"; break; case "xx-large": size = "+3"; break; } EditorSetTextProperty("font", "size", size); } gContentWindow.focus(); } function initFontFaceMenu(menuPopup) { if (menuPopup) { var children = menuPopup.childNodes; if (!children) return; var firstHas = { value: false }; var anyHas = { value: false }; var allHas = { value: false }; // we need to set or clear the checkmark for each menu item since the selection // may be in a new location from where it was when the menu was previously opened // Fixed width (second menu item) is special case: old TT ("teletype") attribute EditorGetTextProperty("tt", "", "", firstHas, anyHas, allHas); children[1].setAttribute("checked", allHas.value); var fontWasFound = anyHas.value; // Skip over default, TT, and separator for (var i = 3; i < children.length; i++) { var menuItem = children[i]; var faceType = menuItem.getAttribute("value"); if (faceType) { EditorGetTextProperty("font", "face", faceType, firstHas, anyHas, allHas); // Check the item only if all of selection has the face... menuItem.setAttribute("checked", allHas.value); // ...but remember if ANY part of the selection has it fontWasFound |= anyHas.value; } } // Check the default item if no other item was checked // note that no item is checked in the case of "mixed" selection children[0].setAttribute("checked", !fontWasFound); } } function initFontSizeMenu(menuPopup) { if (menuPopup) { var children = menuPopup.childNodes; if (!children) return; var firstHas = { value: false }; var anyHas = { value: false }; var allHas = { value: false }; var sizeWasFound = false; // we need to set or clear the checkmark for each menu item since the selection // may be in a new location from where it was when the menu was previously opened // First 2 items add and tags // While it would be better to show the number of levels, // at least this tells user if either of them are set var menuItem = children[0]; if (menuItem) { EditorGetTextProperty("small", "", "", firstHas, anyHas, allHas); menuItem.setAttribute("checked", allHas.value); sizeWasFound = anyHas.value; } menuItem = children[1]; if (menuItem) { EditorGetTextProperty("big", "", "", firstHas, anyHas, allHas); menuItem.setAttribute("checked", allHas.value); sizeWasFound |= anyHas.value; } // Fixed size items start after menu separator var menuIndex = 3; // Index of the medium (default) item var mediumIndex = 5; // Scan through all supported "font size" attribute values for (var i = -2; i <= 3; i++) { menuItem = children[menuIndex]; // Skip over medium since it'll be set below. // If font size=0 is actually set, we'll toggle it off below if // we enter this loop in this case. if (menuItem && (i != 0)) { var sizeString = (i <= 0) ? String(i) : ("+" + String(i)); EditorGetTextProperty("font", "size", sizeString, firstHas, anyHas, allHas); // Check the item only if all of selection has the size... menuItem.setAttribute("checked", allHas.value); // ...but remember if ANY of of selection had size set sizeWasFound |= anyHas.value; } menuIndex++; } // if no size was found, then check default (medium) // note that no item is checked in the case of "mixed" selection children[mediumIndex].setAttribute("checked", !sizeWasFound); } } function onHighlightColorChange() { var commandNode = document.getElementById("cmd_highlight"); if (commandNode) { var color = commandNode.getAttribute("state"); var button = document.getElementById("HighlightColorButton"); if (button) { // No color set - get color set on page or other defaults if (!color) color = "transparent" ; button.setAttribute("style", "background-color:"+color+" !important"); } } } function onFontColorChange() { var commandNode = document.getElementById("cmd_fontColor"); if (commandNode) { var color = commandNode.getAttribute("state"); var button = document.getElementById("TextColorButton"); if (button) { // No color set - get color set on page or other defaults if (!color) color = gDefaultTextColor; button.setAttribute("style", "background-color:"+color); } } } function onBackgroundColorChange() { var commandNode = document.getElementById("cmd_backgroundColor"); if (commandNode) { var color = commandNode.getAttribute("state"); var button = document.getElementById("BackgroundColorButton"); if (button) { if (!color) color = gDefaultBackgroundColor; button.setAttribute("style", "background-color:"+color); } } } // Call this when user changes text and/or background colors of the page function UpdateDefaultColors() { var BrowserColors = GetDefaultBrowserColors(); var bodyelement = GetBodyElement(); var defTextColor = gDefaultTextColor; var defBackColor = gDefaultBackgroundColor; if (bodyelement) { var color = bodyelement.getAttribute("text"); if (color) gDefaultTextColor = color; else if (BrowserColors) gDefaultTextColor = BrowserColors.TextColor; color = bodyelement.getAttribute("bgcolor"); if (color) gDefaultBackgroundColor = color; else if (BrowserColors) gDefaultBackgroundColor = BrowserColors.BackgroundColor; } // Trigger update on toolbar if (defTextColor != gDefaultTextColor) { goUpdateCommand("cmd_fontColor"); onFontColorChange(); } if (defBackColor != gDefaultBackgroundColor) { goUpdateCommand("cmd_backgroundColor"); onBackgroundColorChange(); } } function GetBackgroundElementWithColor() { gColorObj.Type = ""; gColorObj.PageColor = ""; gColorObj.TableColor = ""; gColorObj.CellColor = ""; gColorObj.BackgroundColor = ""; gColorObj.SelectedType = ""; var tagNameObj = { value: "" }; var numSelected; var element; try { var elt = { value: null }; numSelected = gEditor.getSelectedOrParentTableElement(elt, tagNameObj); element = elt.value; } catch(e) {} if (element && tagNameObj && tagNameObj.value) { gColorObj.BackgroundColor = GetHTMLOrCSSStyleValue(element, "bgcolor", "background-color"); gColorObj.BackgroundColor = ConvertRGBColorIntoHEXColor(gColorObj.BackgroundColor); if (tagNameObj.value.toLowerCase() == "td") { gColorObj.Type = "Cell"; gColorObj.CellColor = gColorObj.BackgroundColor; // Get any color that might be on parent table var table = GetParentTable(element); gColorObj.TableColor = GetHTMLOrCSSStyleValue(table, "bgcolor", "background-color"); gColorObj.TableColor = ConvertRGBColorIntoHEXColor(gColorObj.TableColor); } else { gColorObj.Type = "Table"; gColorObj.TableColor = gColorObj.BackgroundColor; } gColorObj.SelectedType = gColorObj.Type; } else { var prefs = GetPrefs(); var IsCSSPrefChecked = prefs.getBoolPref("editor.use_css"); if (IsCSSPrefChecked && isHTMLEditor()) { var selection = gEditor.selection; if (selection) { element = selection.focusNode; while (!gEditor.nodeIsBlock(element)) element = element.parentNode; } else { element = GetBodyElement(); } } else { element = GetBodyElement(); } if (element) { gColorObj.Type = "Page"; gColorObj.BackgroundColor = GetHTMLOrCSSStyleValue(element, "bgcolor", "background-color"); if (gColorObj.BackgroundColor == "") { gColorObj.BackgroundColor = "transparent"; } else { gColorObj.BackgroundColor = ConvertRGBColorIntoHEXColor(gColorObj.BackgroundColor); } gColorObj.PageColor = gColorObj.BackgroundColor; } } return element; } function SetSmiley(smileyText) { try { gEditor.InsertText(smileyText); gContentWindow.focus(); } catch(e) {} } function EditorSelectColor(colorType, mouseEvent) { if (!gColorObj) return; // Shift + mouse click automatically applies last color, if available var useLastColor = mouseEvent ? ( mouseEvent.button == 0 && mouseEvent.shiftKey ) : false; var element; var table; var currentColor = ""; var commandNode; if (!colorType) colorType = ""; if (colorType == "Text") { gColorObj.Type = colorType; // Get color from command node state commandNode = document.getElementById("cmd_fontColor"); currentColor = commandNode.getAttribute("state"); gColorObj.TextColor = currentColor; if (useLastColor && gColorObj.LastTextColor ) gColorObj.TextColor = gColorObj.LastTextColor; else useLastColor = false; } else if (colorType == "Highlight") { gColorObj.Type = colorType; // Get color from command node state commandNode = document.getElementById("cmd_highlight"); currentColor = commandNode.getAttribute("state"); gColorObj.HighlightColor = currentColor; if (useLastColor && gColorObj.LastHighlightColor ) gColorObj.HighlightColor = gColorObj.LastHighlightColor; else useLastColor = false; } else { element = GetBackgroundElementWithColor(); if (!element) return; // Get the table if we found a cell if (gColorObj.Type == "Table") table = element; else if (gColorObj.Type == "Cell") table = GetParentTable(element); // Save to avoid resetting if not necessary currentColor = gColorObj.BackgroundColor; if (colorType == "TableOrCell" || colorType == "Cell") { if (gColorObj.Type == "Cell") gColorObj.Type = colorType; else if (gColorObj.Type != "Table") return; } else if (colorType == "Table" && gColorObj.Type == "Page") return; if (colorType == "" && gColorObj.Type == "Cell") { // Using empty string for requested type means // we can let user select cell or table gColorObj.Type = "TableOrCell"; } if (useLastColor && gColorObj.LastBackgroundColor ) gColorObj.BackgroundColor = gColorObj.LastBackgroundColor; else useLastColor = false; } // Save the type we are really requesting colorType = gColorObj.Type; if (!useLastColor) { // Avoid the JS warning gColorObj.NoDefault = false; // Launch the ColorPicker dialog // TODO: Figure out how to position this under the color buttons on the toolbar window.openDialog("chrome://editor/content/EdColorPicker.xul", "_blank", "chrome,close,titlebar,modal", "", gColorObj); // User canceled the dialog if (gColorObj.Cancel) return; } if (gColorObj.Type == "Text") { if (currentColor != gColorObj.TextColor) { if (gColorObj.TextColor) EditorSetTextProperty("font", "color", gColorObj.TextColor); else EditorRemoveTextProperty("font", "color"); } // Update the command state (this will trigger color button update) goUpdateCommand("cmd_fontColor"); } else if (gColorObj.Type == "Highlight") { if (currentColor != gColorObj.HighlightColor) { if (gColorObj.HighlightColor) EditorSetTextProperty("font", "bgcolor", gColorObj.HighlightColor); else EditorRemoveTextProperty("font", "bgcolor"); } // Update the command state (this will trigger color button update) goUpdateCommand("cmd_highlight"); } else if (element) { if (gColorObj.Type == "Table") { // Set background on a table // Note that we shouldn't trust "currentColor" because of "TableOrCell" behavior if (table) { var bgcolor = table.getAttribute("bgcolor"); if (bgcolor != gColorObj.BackgroundColor) { if (gColorObj.BackgroundColor) gEditor.setAttributeOrEquivalent(table, "bgcolor", gColorObj.BackgroundColor); else gEditor.removeAttributeOrEquivalent(table, "bgcolor"); } } } else if (currentColor != gColorObj.BackgroundColor && isHTMLEditor()) { gEditor.beginTransaction(); try { gEditor.setBackgroundColor(gColorObj.BackgroundColor); if (gColorObj.Type == "Page" && gColorObj.BackgroundColor) { // Set all page colors not explicitly set, // else you can end up with unreadable pages // because viewer's default colors may not be same as page author's var bodyelement = GetBodyElement(); if (bodyelement) { var defColors = GetDefaultBrowserColors(); if (defColors) { if (!bodyelement.getAttribute("text")) gEditor.setAttributeOrEquivalent(bodyelement, "text", defColors.TextColor); // The following attributes have no individual CSS declaration counterparts // Getting rid of them in favor of CSS implies CSS rules management if (!bodyelement.getAttribute("link")) gEditor.setAttribute(bodyelement, "link", defColors.LinkColor); if (!bodyelement.getAttribute("alink")) gEditor.setAttribute(bodyelement, "alink", defColors.LinkColor); if (!bodyelement.getAttribute("vlink")) gEditor.setAttribute(bodyelement, "vlink", defColors.VisitedLinkColor); } } } } catch(e) {} gEditor.endTransaction(); } goUpdateCommand("cmd_backgroundColor"); } gContentWindow.focus(); } function GetParentTable(element) { var node = element; while (node) { if (node.nodeName.toLowerCase() == "table") return node; node = node.parentNode; } return node; } function GetParentTableCell(element) { var node = element; while (node) { if (node.nodeName.toLowerCase() == "td" || node.nodeName.toLowerCase() == "th") return node; node = node.parentNode; } return node; } function EditorDblClick(event) { if (event.target) { // Only bring up properties if clicked on an element or selected link var element; try { element = event.target.QueryInterface(Components.interfaces.nsIDOMElement); } catch (e) {} // We use "href" instead of "a" to not be fooled by named anchor if (!element) element = gEditor.getSelectedElement("href"); if (element) { goDoCommand("cmd_objectProperties"); event.preventDefault(); } } } function EditorClick(event) { if (!event) return; if (event.detail == 2) { EditorDblClick(event); return; } // In Show All Tags Mode, // single click selects entire element, // except for body and table elements if (event.target && isHTMLEditor() && gEditorDisplayMode == DisplayModeAllTags) { try { var element = event.target.QueryInterface( Components.interfaces.nsIDOMElement); var name = element.localName.toLowerCase(); if (name != "body" && name != "table" && name != "td" && name != "th" && name != "caption" && name != "tr") { gEditor.selectElement(event.target); event.preventDefault(); } } catch (e) {} } } /*TODO: We need an oncreate hook to do enabling/disabling for the Format menu. There should be code like this for the object-specific "Properties" item */ // For property dialogs, we want the selected element, // but will accept a parent link, list, or table cell if inside one function GetObjectForProperties() { if (!isHTMLEditor()) return null; var element = gEditor.getSelectedElement(""); if (element) return element; // Find nearest parent of selection anchor node // that is a link, list, table cell, or table var anchorNode = gEditor.selection.anchorNode; if (!anchorNode) return null; var node; if (anchorNode.firstChild) { // Start at actual selected node var offset = gEditor.selection.anchorOffset; // Note: If collapsed, offset points to element AFTER caret, // thus node may be null node = anchorNode.childNodes.item(offset); } if (!node) node = anchorNode; while (node) { if (node.nodeName) { var nodeName = node.nodeName.toLowerCase(); // Done when we hit the body if (nodeName == "body") break; if ((nodeName == "a" && node.href) || nodeName == "ol" || nodeName == "ul" || nodeName == "dl" || nodeName == "td" || nodeName == "th" || nodeName == "table") { return node; } } node = node.parentNode; } return null; } function SetEditMode(mode) { if (!isHTMLEditor()) return; var bodyNode = gEditor.document.getElementsByTagName("body").item(0); if (!bodyNode) { dump("SetEditMode: We don't have a body node!\n"); return; } // Switch the UI mode before inserting contents // so user can't type in source window while new window is being filled var previousMode = gEditorDisplayMode; if (!SetDisplayMode(mode)) return; if (mode == DisplayModeSource) { // Display the DOCTYPE as a non-editable string above edit area var domdoc; try { domdoc = gEditor.document; } catch (e) { dump( e + "\n");} if (domdoc) { var doctypeNode = document.getElementById("doctype-text"); var dt = domdoc.doctype; if (doctypeNode) { if (dt) { doctypeNode.removeAttribute("collapsed"); var doctypeText = "" doctypeNode.setAttribute("value", doctypeText); } else doctypeNode.setAttribute("collapsed", "true"); } } // Get the entire document's source string var flags = 256; // OutputEncodeEntities; try { var prettyPrint = gPrefs.getBoolPref("editor.prettyprint"); if (prettyPrint) flags |= 2; // OutputFormatted } catch (e) {} var source = gEditor.outputToString("text/html", flags); var start = source.search(/ from the newly-parsed document // (must do this for proper conversion of "escaped" characters) var title = ""; var titlenodelist = gEditor.document.getElementsByTagName("title"); if (titlenodelist) { var titleNode = titlenodelist.item(0); if (titleNode && titleNode.firstChild && titleNode.firstChild.data) title = titleNode.firstChild.data; } if (gEditor.document.title != title) { gEditor.document.title = title; ResetWindowTitleWithFilename(); } // reset selection to top of doc (wish we could preserve it!) if (bodyNode) gEditor.selection.collapse(bodyNode, 0); } catch (ex) { dump(ex); } gEditor.endTransaction(); // Restore unlimited undo count try { gEditor.transactionManager.maxTransactionCount = -1; } catch (e) {} } else { // We don't need to call this again, so remove handler gSourceContentWindow.removeEventListener("input", oninputHTMLSource, false); } gHTMLSourceChanged = false; // Clear out the string buffers gSourceContentWindow.value = null; gContentWindow.focus(); } } function oninputHTMLSource() { gHTMLSourceChanged = true; // Trigger update of "Save" and "Publish" buttons goUpdateCommand("cmd_save"); goUpdateCommand("cmd_publish"); // We don't need to call this again, so remove handler gSourceContentWindow.removeEventListener("input", oninputHTMLSource, false); } function ResetWindowTitleWithFilename() { // Calling this resets the "Title [filename]" that we show on window caption. // The editorShell method calls the editor method, // then calls UpdateWindowTitleAndRecentMenu(). editorShell.SetDocumentTitle(gEditor.document.title); } function CancelHTMLSource() { // Don't convert source text back into the DOM document gSourceContentWindow.value = ""; gHTMLSourceChanged = false; SetDisplayMode(PreviousNonSourceDisplayMode); } function FinishHTMLSource() { //Here we need to check whether the HTML source contains and tags //Or RebuildDocumentFromSource() will fail. if (IsInHTMLSourceMode()) { var htmlSource = gSourceContentWindow.value; if (htmlSource.length > 0) { var beginHead = htmlSource.indexOf("