Bug 498519 - "make attachment reminder an inline notification bar" [r=mkmelin,ui-r=clarkbw]
This commit is contained in:
Родитель
8d65b52a84
Коммит
3973860a10
|
@ -46,6 +46,7 @@ include $(DEPTH)/config/autoconf.mk
|
|||
EXTRA_JS_MODULES = \
|
||||
MailConsts.js \
|
||||
MailUtils.js \
|
||||
attachmentChecker.js \
|
||||
$(NULL)
|
||||
|
||||
include $(topsrcdir)/config/rules.mk
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
/* ***** BEGIN LICENSE BLOCK *****
|
||||
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla 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/MPL/
|
||||
*
|
||||
* 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
|
||||
* Mozilla Messaging, Inc.
|
||||
* Portions created by the Initial Developer are Copyright (C) 2009
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
* Blake Winton <bwinton@latte.ca>
|
||||
*
|
||||
* 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 MPL, 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 MPL, the GPL or the LGPL.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
const EXPORTED_SYMBOLS = ["GetAttachmentKeywords"];
|
||||
|
||||
/**
|
||||
* Get the (possibly-empty) list of attachment keywords in this message.
|
||||
*
|
||||
* @return the (possibly-empty) list of attachment keywords in this message
|
||||
**/
|
||||
function GetAttachmentKeywords(mailData,keywordsInCsv)
|
||||
{
|
||||
// The empty string would get split to an array of size 1. Avoid that...
|
||||
var keywordsArray = (keywordsInCsv) ? keywordsInCsv.split(",") : [];
|
||||
|
||||
function escapeRegxpSpecials(inputString)
|
||||
{
|
||||
const specials = [".", "\\", "^", "$", "*", "+", "?", "|",
|
||||
"(", ")" , "[", "]", "{", "}"];
|
||||
var re = new RegExp("(\\"+specials.join("|\\")+")", "g");
|
||||
inputString = inputString.replace(re, "\\$1");
|
||||
return inputString.replace(" ", "\\s+");
|
||||
}
|
||||
|
||||
var keywordsFound = [];
|
||||
for (var i = 0; i < keywordsArray.length; i++) {
|
||||
var kw = escapeRegxpSpecials(keywordsArray[i]);
|
||||
var re = new RegExp("(([^\\s]*)\\b|\\s*)" + kw + "\\b", "i");
|
||||
var matching = re.exec(mailData);
|
||||
// Ignore the match if it was a URL.
|
||||
if (matching && !(/^http|^ftp/i.test(matching[0])))
|
||||
keywordsFound.push(keywordsArray[i]);
|
||||
}
|
||||
return keywordsFound;
|
||||
}
|
||||
|
||||
function onmessage(event)
|
||||
{
|
||||
var keywordsFound = GetAttachmentKeywords(event.data[0], event.data[1]);
|
||||
postMessage(keywordsFound);
|
||||
};
|
||||
|
|
@ -41,6 +41,8 @@
|
|||
|
||||
// Ensure the activity modules are loaded for this window.
|
||||
Components.utils.import("resource://app/modules/activity/activityModules.js");
|
||||
Components.utils.import("resource://gre/modules/PluralForm.jsm");
|
||||
Components.utils.import("resource://app/modules/attachmentChecker.js");
|
||||
|
||||
/**
|
||||
* interfaces
|
||||
|
@ -92,6 +94,7 @@ var gMsgAddressingWidgetTreeElement;
|
|||
var gMsgSubjectElement;
|
||||
var gMsgAttachmentElement;
|
||||
var gMsgHeadersToolbarElement;
|
||||
var gRemindLater;
|
||||
|
||||
// i18n globals
|
||||
var gSendDefaultCharset;
|
||||
|
@ -137,6 +140,7 @@ function InitializeGlobalVariables()
|
|||
gCharsetConvertManager = Components.classes['@mozilla.org/charset-converter-manager;1'].getService(Components.interfaces.nsICharsetConverterManager);
|
||||
gMailSession = Components.classes["@mozilla.org/messenger/services/session;1"].getService(Components.interfaces.nsIMsgMailSession);
|
||||
gHideMenus = false;
|
||||
gRemindLater = false;
|
||||
|
||||
gLastWindowToHaveFocus = null;
|
||||
gReceiptOptionChanged = false;
|
||||
|
@ -1166,6 +1170,181 @@ function handleMailtoArgs(mailtoUrl)
|
|||
return null;
|
||||
}
|
||||
|
||||
var attachmentWorker = new Worker("resource://app/modules/attachmentChecker.js");
|
||||
|
||||
attachmentWorker.lastMessage = null;
|
||||
|
||||
attachmentWorker.onerror = function(error)
|
||||
{
|
||||
dump("Attachment Notification Worker error!!! " + error.message + "\n");
|
||||
throw error;
|
||||
};
|
||||
|
||||
|
||||
attachmentWorker.onmessage = function(event)
|
||||
{
|
||||
let keywordsFound = event.data;
|
||||
let bundle = document.getElementById("bundle_composeMsgs");
|
||||
let msg = null;
|
||||
let nBox = document.getElementById("attachmentNotificationBox");
|
||||
let notification = nBox.getNotificationWithValue("1");
|
||||
let removeNotification = false;
|
||||
|
||||
if (keywordsFound.length > 0) {
|
||||
msg = document.createElement("hbox");
|
||||
msg.setAttribute("flex", "100");
|
||||
|
||||
msg.onclick = function(event)
|
||||
{
|
||||
openOptionsDialog("paneCompose");
|
||||
};
|
||||
|
||||
let msgText = document.createElement("label");
|
||||
msg.appendChild(msgText);
|
||||
msgText.id = "attachmentReminderText";
|
||||
msgText.setAttribute("crop", "end");
|
||||
msgText.setAttribute("flex", "1");
|
||||
msgText.setAttribute("value", PluralForm.get(keywordsFound.length,
|
||||
bundle.getString("attachmentReminderKeywordsMsg")));
|
||||
|
||||
let keywords = keywordsFound.join(", ");
|
||||
let msgKeywords = document.createElement("label");
|
||||
msg.appendChild(msgKeywords);
|
||||
msgKeywords.id = "attachmentKeywords";
|
||||
msgKeywords.setAttribute("crop", "end");
|
||||
msgKeywords.setAttribute("flex", "1000");
|
||||
msgKeywords.setAttribute("value", keywords);
|
||||
|
||||
if (notification) {
|
||||
let description = notification.querySelector("#attachmentReminderText");
|
||||
description.setAttribute("value", msgText.getAttribute("value"));
|
||||
description = notification.querySelector("#attachmentKeywords")
|
||||
description.setAttribute("value", keywords);
|
||||
msg = null;
|
||||
}
|
||||
if (keywords == this.lastMessage) {
|
||||
// The user closed the notification, and we have nothing new to say.
|
||||
msg = null;
|
||||
}
|
||||
this.lastMessage = keywords;
|
||||
}
|
||||
else {
|
||||
removeNotification = true;
|
||||
this.lastMessage = null;
|
||||
}
|
||||
if (notification && removeNotification)
|
||||
nBox.removeNotification(notification);
|
||||
if (msg) {
|
||||
var addButton = {
|
||||
accessKey : bundle.getString("addAttachmentButton.accessskey"),
|
||||
label: bundle.getString("addAttachmentButton"),
|
||||
callback: function (aNotificationBar, aButton)
|
||||
{
|
||||
goDoCommand("cmd_attachFile");
|
||||
}
|
||||
};
|
||||
|
||||
var remindButton = {
|
||||
accessKey : bundle.getString("remindLaterButton.accessskey"),
|
||||
label: bundle.getString("remindLaterButton"),
|
||||
callback: function (aNotificationBar, aButton)
|
||||
{
|
||||
gRemindLater = true;
|
||||
}
|
||||
};
|
||||
|
||||
notification = nBox.appendNotification("", "1",
|
||||
/* fake out the image so we can do it in CSS */
|
||||
"null",
|
||||
nBox.PRIORITY_WARNING_MEDIUM,
|
||||
[addButton, remindButton]);
|
||||
let buttons = notification.childNodes[0];
|
||||
notification.insertBefore(msg, buttons);
|
||||
}
|
||||
CheckForAttachmentNotification.shouldFire = true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Determine whether we should show the attachment notification or not.
|
||||
*
|
||||
* @param async Whether we should run the regex checker asynchronously or not.
|
||||
* @return true if we should show the attachment notification
|
||||
*/
|
||||
function ShouldShowAttachmentNotification(async)
|
||||
{
|
||||
let bucket = document.getElementById("attachmentBucket");
|
||||
let warn = getPref("mail.compose.attachment_reminder");
|
||||
if (warn && !bucket.itemCount) {
|
||||
let prefs = Components.classes["@mozilla.org/preferences-service;1"]
|
||||
.getService(Components.interfaces.nsIPrefBranch);
|
||||
let keywordsInCsv = prefs.getComplexValue(
|
||||
"mail.compose.attachment_reminder_keywords",
|
||||
Components.interfaces.nsIPrefLocalizedString).data;
|
||||
let mailBody = document.getElementById("content-frame")
|
||||
.contentDocument.getElementsByTagName("body")[0];
|
||||
let mailBodyNode = mailBody.cloneNode(true);
|
||||
|
||||
// Don't check quoted text from reply.
|
||||
let blockquotes = mailBodyNode.getElementsByTagName("blockquote");
|
||||
for (let i = 0; i < blockquotes.length; i++) {
|
||||
blockquotes[i].parentNode.removeChild(blockquotes[i]);
|
||||
}
|
||||
// For plaintext composition the quotes we need to find and exclude are
|
||||
// normally <span _moz_quote="true">. If editor.quotesPreformatted is
|
||||
// set we should exclude <pre _moz_quote="true"> nodes instead.
|
||||
if (!getPref("editor.quotesPreformatted")) {
|
||||
let spans = mailBodyNode.getElementsByTagName("span");
|
||||
for (let i = 0; i < spans.length; i++) {
|
||||
if (spans[i].hasAttribute("_moz_quote"))
|
||||
spans[i].parentNode.removeChild(spans[i]);
|
||||
}
|
||||
}
|
||||
else {
|
||||
let pres = mailBodyNode.getElementsByTagName("pre");
|
||||
for (let i = 0; i < pres.length; i++) {
|
||||
if (pres[i].hasAttribute("_moz_quote"))
|
||||
pres[i].parentNode.removeChild(pres[i]);
|
||||
}
|
||||
}
|
||||
let brs = mailBodyNode.getElementsByTagName("br");
|
||||
for (let i = 0; i < brs.length; i++) {
|
||||
brs[i].parentNode.replaceChild(document.createTextNode("\n"), brs[i]);
|
||||
}
|
||||
let mailData = mailBodyNode.textContent;
|
||||
if (!async)
|
||||
return GetAttachmentKeywords(mailData, keywordsInCsv).length != 0;
|
||||
attachmentWorker.postMessage([mailData, keywordsInCsv]);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for attachment keywords, and display a notification if it's
|
||||
* appropriate.
|
||||
*/
|
||||
function CheckForAttachmentNotification(event)
|
||||
{
|
||||
if (!CheckForAttachmentNotification.shouldFire || gRemindLater)
|
||||
return;
|
||||
if (!event)
|
||||
attachmentWorker.lastMessage = null;
|
||||
CheckForAttachmentNotification.shouldFire = false;
|
||||
let nBox = document.getElementById("attachmentNotificationBox");
|
||||
let notification = nBox.getNotificationWithValue("1");
|
||||
let removeNotification = false;
|
||||
|
||||
if (!ShouldShowAttachmentNotification(true)) {
|
||||
removeNotification = true;
|
||||
CheckForAttachmentNotification.shouldFire = true;
|
||||
}
|
||||
|
||||
if (notification && removeNotification)
|
||||
nBox.removeNotification(notification);
|
||||
};
|
||||
|
||||
CheckForAttachmentNotification.shouldFire = true;
|
||||
|
||||
function ComposeStartup(recycled, aParams)
|
||||
{
|
||||
var params = null; // New way to pass parameters to the compose window as a nsIMsgComposeParameters object
|
||||
|
@ -1212,6 +1391,8 @@ function ComposeStartup(recycled, aParams)
|
|||
var identityList = document.getElementById("msgIdentity");
|
||||
|
||||
document.addEventListener("keypress", awDocumentKeyPress, true);
|
||||
var contentFrame = document.getElementById("content-frame");
|
||||
contentFrame.addEventListener("click", CheckForAttachmentNotification, true);
|
||||
|
||||
if (identityList)
|
||||
FillIdentityList(identityList);
|
||||
|
@ -1706,71 +1887,7 @@ function GenericSendMessage( msgType )
|
|||
}
|
||||
}
|
||||
|
||||
// Attachment Reminder stuff...
|
||||
var bucket = document.getElementById("attachmentBucket");
|
||||
var warn = getPref("mail.compose.attachment_reminder");
|
||||
if (warn && !bucket.itemCount)
|
||||
{
|
||||
var prefs = Components.classes["@mozilla.org/preferences-service;1"]
|
||||
.getService(Components.interfaces.nsIPrefBranch);
|
||||
var keywordsInCsv = prefs.getComplexValue("mail.compose.attachment_reminder_keywords",
|
||||
Components.interfaces.nsIPrefLocalizedString).data;
|
||||
// And empty string pref is still going to get split to an array of
|
||||
// size 1. Avoid that...
|
||||
var keywordsArray = (keywordsInCsv) ? keywordsInCsv.split(",") : [];
|
||||
|
||||
var mailBody = document.getElementById("content-frame")
|
||||
.contentDocument.getElementsByTagName("body")[0];
|
||||
var mailBodyNode = mailBody.cloneNode(true);
|
||||
|
||||
// Don't check quoted text from reply.
|
||||
var blockquotes = mailBodyNode.getElementsByTagName("blockquote");
|
||||
for (let i = 0; i < blockquotes.length; i++)
|
||||
{
|
||||
blockquotes[i].parentNode.removeChild(blockquotes[i]);
|
||||
}
|
||||
// For plaintext composition the quotes we need to find and exclude are
|
||||
// normally <span _moz_quote="true">. If editor.quotesPreformatted is
|
||||
// set we should exclude <pre _moz_quote="true"> nodes instead.
|
||||
if (!getPref("editor.quotesPreformatted"))
|
||||
{
|
||||
let spans = mailBodyNode.getElementsByTagName("span");
|
||||
for (let i = 0; i < spans.length; i++)
|
||||
{
|
||||
if (spans[i].hasAttribute("_moz_quote"))
|
||||
spans[i].parentNode.removeChild(spans[i]);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
let pres = mailBodyNode.getElementsByTagName("pre");
|
||||
for (let i = 0; i < pres.length; i++)
|
||||
{
|
||||
if (pres[i].hasAttribute("_moz_quote"))
|
||||
pres[i].parentNode.removeChild(pres[i]);
|
||||
}
|
||||
}
|
||||
var mailData = mailBodyNode.textContent;
|
||||
|
||||
function escapeRegxpSpecials(inputString) {
|
||||
const specials = [ ".", "\\", "^", "$", "*", "+", "?", , "|",
|
||||
"(", ")" , "[", "]", "{", "}" ];
|
||||
var re = new RegExp("(\\"+specials.join("|\\")+")", "g");
|
||||
return inputString.replace(re, "\\$1");
|
||||
}
|
||||
|
||||
var keywordFound;
|
||||
for (let i = 0; i < keywordsArray.length && !keywordFound; i++)
|
||||
{
|
||||
let kw = escapeRegxpSpecials(keywordsArray[i]);
|
||||
let re = new RegExp("(([^\\s]*)\\b|\\s*)" + kw + "\\b", "i");
|
||||
let matching = re.exec(mailData);
|
||||
// Ignore the match if it was a URL.
|
||||
keywordFound = matching && !(/^http|^ftp/i.test(matching[0]));
|
||||
}
|
||||
|
||||
if (keywordFound)
|
||||
{
|
||||
if (gRemindLater && ShouldShowAttachmentNotification(false)) {
|
||||
var bundle = document.getElementById("bundle_composeMsgs");
|
||||
var flags = gPromptService.BUTTON_POS_0 * gPromptService.BUTTON_TITLE_IS_STRING +
|
||||
gPromptService.BUTTON_POS_1 * gPromptService.BUTTON_TITLE_IS_STRING;
|
||||
|
@ -1784,7 +1901,6 @@ function GenericSendMessage( msgType )
|
|||
if (hadForgotten)
|
||||
return;
|
||||
}
|
||||
} // End of Attachment Reminder.
|
||||
|
||||
// check if the user tries to send a message to a newsgroup through a mail account
|
||||
var currentAccountKey = getCurrentAccountKey();
|
||||
|
@ -2713,6 +2829,7 @@ function AddUrlAttachment(attachment)
|
|||
|
||||
ChangeAttachmentBucketVisibility(false);
|
||||
gContentChanged = true;
|
||||
CheckForAttachmentNotification(null);
|
||||
}
|
||||
|
||||
function SelectAllAttachments()
|
||||
|
@ -2804,6 +2921,7 @@ function RemoveAllAttachments()
|
|||
}
|
||||
|
||||
ChangeAttachmentBucketVisibility(true);
|
||||
CheckForAttachmentNotification(null);
|
||||
}
|
||||
|
||||
function ChangeAttachmentBucketVisibility(aHideBucket)
|
||||
|
@ -2825,6 +2943,7 @@ function RemoveSelectedAttachment()
|
|||
}
|
||||
gContentChanged = true;
|
||||
}
|
||||
CheckForAttachmentNotification(null);
|
||||
}
|
||||
|
||||
function RenameSelectedAttachment()
|
||||
|
@ -3620,6 +3739,26 @@ function AutoSave()
|
|||
gAutoSaveTimeout = setTimeout(AutoSave, gAutoSaveInterval);
|
||||
}
|
||||
|
||||
const gAttachmentNotifier =
|
||||
{
|
||||
event: {
|
||||
notify: function(timer)
|
||||
{
|
||||
CheckForAttachmentNotification(true);
|
||||
}
|
||||
},
|
||||
|
||||
timer: Components.classes["@mozilla.org/timer;1"]
|
||||
.createInstance(Components.interfaces.nsITimer),
|
||||
|
||||
EditAction: function EditAction()
|
||||
{
|
||||
this.timer.cancel();
|
||||
this.timer.initWithCallback(this.event, 500,
|
||||
Components.interfaces.nsITimer.TYPE_ONE_SHOT);
|
||||
}
|
||||
};
|
||||
|
||||
function InitEditor()
|
||||
{
|
||||
var editor = GetCurrentEditor();
|
||||
|
@ -3635,6 +3774,7 @@ function InitEditor()
|
|||
InlineSpellCheckerUI.init(editor);
|
||||
enableInlineSpellCheck(getPref("mail.spellcheck.inline"));
|
||||
document.getElementById('menu_inlineSpellCheck').setAttribute('disabled', !InlineSpellCheckerUI.canSpellCheck);
|
||||
editor.addEditorObserver(gAttachmentNotifier);
|
||||
}
|
||||
|
||||
function enableInlineSpellCheck(aEnableInlineSpellCheck)
|
||||
|
|
|
@ -794,6 +794,10 @@
|
|||
hidden="true"/>
|
||||
</panel>
|
||||
|
||||
<hbox>
|
||||
<notificationbox id="attachmentNotificationBox" flex="1" />
|
||||
</hbox>
|
||||
|
||||
<statusbar id="status-bar" class="chromeclass-status">
|
||||
<statusbarpanel id="statusText" flex="1"/>
|
||||
<statusbarpanel class="statusbarpanel-progress" id="statusbar-progresspanel">
|
||||
|
|
|
@ -337,8 +337,16 @@ renameAttachmentMessage=New attachment name:
|
|||
## LOCALIZATION NOTE (mail.compose.attachment_reminder_keywords): comma separated
|
||||
## words that that should trigger an attachment reminder.
|
||||
mail.compose.attachment_reminder_keywords=.doc,.pdf,attachment,attach,attached,attaching,enclosed,CV,cover letter
|
||||
|
||||
addAttachmentButton=Add Attachment…
|
||||
addAttachmentButton.accessskey=A
|
||||
remindLaterButton=Remind Me Later
|
||||
remindLaterButton.accessskey=R
|
||||
|
||||
attachmentReminderTitle=Attachment Reminder
|
||||
attachmentReminderMsg=Did you forget to add an attachment?
|
||||
attachmentReminderKeywordsMsg=Found an attachment keyword: ;Found some attachment keywords:
|
||||
attachmentReminderOptionsMsg=Attachment reminder words can be configured in your preferences
|
||||
attachmentReminderYesIForgot=Oh, I did!
|
||||
attachmentReminderFalseAlarm=No, Send Now
|
||||
|
||||
|
|
|
@ -224,6 +224,27 @@ toolbar[iconsize="small"] #paste-button[disabled="true"] {
|
|||
border-bottom: none;
|
||||
}
|
||||
|
||||
/* ::::: attachment reminder ::::: */
|
||||
|
||||
#attachmentNotificationBox > notification .messageImage {
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
background-image: url(chrome://messenger/skin/messengercompose/compose-toolbar.png);
|
||||
background-position: left -66px;
|
||||
}
|
||||
|
||||
#attachmentReminderText {
|
||||
-moz-margin-start: 0px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#attachmentKeywords {
|
||||
font-weight: bold;
|
||||
-moz-margin-start: 0px;
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* XXX I should really have a selector rule here to select just .listcell-icon objects underneath the attachmentList listbox */
|
||||
|
||||
.listcell-icon
|
||||
|
|
|
@ -346,6 +346,27 @@ toolbar[iconsize="small"] #paste-button[disabled="true"] {
|
|||
height: 7px;
|
||||
}
|
||||
|
||||
/* ::::: attachment reminder ::::: */
|
||||
|
||||
#attachmentNotificationBox > notification .messageImage {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
background-image: url(chrome://messenger/skin/messengercompose/compose-toolbar.png);
|
||||
background-position: -96px top;
|
||||
}
|
||||
|
||||
#attachmentReminderText {
|
||||
-moz-margin-start: 0px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#attachmentKeywords {
|
||||
font-weight: bold;
|
||||
-moz-margin-start: 0px;
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* XXX I should really have a selector rule here to select just .listcell-icon objects underneath the attachmentList listbox */
|
||||
|
||||
.listcell-icon
|
||||
|
|
|
@ -307,6 +307,27 @@ toolbar[iconsize="small"] #paste-button[disabled="true"] {
|
|||
border-bottom: none;
|
||||
}
|
||||
|
||||
/* ::::: attachment reminder ::::: */
|
||||
|
||||
#attachmentNotificationBox > notification .messageImage {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
background-image: url(chrome://messenger/skin/messengercompose/compose-toolbar.png);
|
||||
background-position: -72px top;
|
||||
}
|
||||
|
||||
#attachmentReminderText {
|
||||
-moz-margin-start: 0px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#attachmentKeywords {
|
||||
font-weight: bold;
|
||||
-moz-margin-start: 0px;
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* XXX I should really have a selector rule here to select just .listcell-icon objects underneath the attachmentList listbox */
|
||||
|
||||
.listcell-icon
|
||||
|
|
Загрузка…
Ссылка в новой задаче