| jens.hatlak.de | Über mich | ||||
| Atari | PHP | ||||
| Dreamweaver Manual | Linux | ||||
| Akronyme | Mozilla | Mozilla Dualboot | |||
| SciTE | Mozilla bauen | ||||
in English |
FASTA-Parser | Mozilla installieren | |||
| Ich spreche... | Bookmark Indicator | ||||
| Custom Buttons | |||||
| MailNews Status Icons | |||||
Do you prefer reading pages in English? This page is also available in English.
Check All Checkboxes / Compact All Folders / Empty All Trashes / Get All Messages / Copy Mails / Switch View / Reload View / Prepare SMTT / Bookmark Feed Website
Es gibt leider einige Funktionen, die SeaMonkey (insbesondere die MailNews-Komponente) vermissen lässt. Wenn man sich wie ich mit den Interna auskennt, kann man für fast alles eine Erweiterung schreiben, die das jeweilige Problem löst. Doch das dauert, weil man sich dann schnell mehr mit der Einbindung in die Oberfläche (UI) beschäftigt, als mit der eigentlichen Funktionalität.
Die Erweiterung Custom Buttons ermöglicht es nun, beliebigen JavaScript-Code beim Anklicken einer selbstdefinierten Schaltfläche (Button, inklusive frei wählbarem Bild) auszuführen. Somit entfällt der UI-Teil und es bleibt die Funktionalität, kodiert in JavaScript.
Im Folgenden präsentiere ich einige Buttons für Funktionen, die SeaMonkey von Haus aus nicht beherrscht (jedenfalls nicht genau so). Sicherlich ist es jeweils möglich, SeaMonkey selbst anzupassen, doch jedweder SeaMonkey-Code muss den Review-Prozess durchlaufen, und das kann dauern – mit ungewissem Ausgang. Ähnliches gilt übrigens auch für Erweiterungen, die auf AMO gehostet werden.
Alle unten aufgeführten Buttons wurden mit SeaMonkey 2.0 getestet. Einige funktionieren vielleicht auch mit Thunderbird; da ich diesen jedoch nicht benutze, habe ich es nicht getestet. Für Rückmeldungen jeder Art bin ich aber dankbar.
Sämtlicher Code auf dieser Seite kann sowohl privat als auch kommerziell genutzt werden, aber in jedem Fall auf eigene Gefahr. Ich übernehme keinerlei Verantwortung für die Richtigkeit des Codes oder irgendwelche Konsequenzen, die sich aus der Ausführung desselben ergeben.
Wer den Code nicht weiter gibt, kann damit machen, was er will. Wer den Code weiter gibt (veröffentlicht), muss entweder den Code inklusive Kommentaren unverändert lassen, sich selbst als Autor mit eintragen, falls signifikante Änderungen vorgenommen werden, oder mich nicht als Autor aufführen, falls nur kleine Teile oder aus anderen Quellen stammende Teile übernommen werden.
Das Basismodul JHBF stellt einige Grundfunktionen zur Verfügung. Jeder Button, dessen Code JHBF benutzt, muss diese Datei importieren. Da viele meiner Buttons dieses Modul benötigen, habe ich dafür gesorgt, dass man es nur einmal auf der Festplatte ablegen muss und überall dort importieren kann, wo es benötigt wird. Das geschieht mittels des Codes aus loadJHBF.js (s.u.). Zweck des Moduls ist es, häufig verwendete Funktionalitäten so zu kapseln, dass sie möglichst einfach verwendet werden können (etwa mittels Array.forEach).
/**
* JH Button Functions
* @author Jens Hatlak <jh@junetz.de>
* with parts from:
* - http://developer.mozilla.org/
* - http://code.google.com/p/colorediffs/wiki/HowToGetMessageTextInThunderbird
* - http://www.fstoffel.de/tbblog/2009/04/11/how-to-get-a-message-body/
* @version 2011-12-13
*/
var EXPORTED_SYMBOLS = ["JHBF"];
var Ci = Components.interfaces;
const JHBF = {
/**
* MailNews functions
*/
mailnews: {
/**
* View functions
*/
view: {
/**
* Return view flags and type
*/
getFlagsAndType: function(gDBView) {
if (gDBView)
return [gDBView.viewFlags, gDBView.viewType];
return [0, 0];
},
/**
* Return whether the view is threaded
*/
isThreaded: function(aViewFlags) {
return (aViewFlags & Ci.nsMsgViewFlagsType.kThreadedDisplay) != 0;
},
/**
* Return whether the view shows only unread messages
*/
showsOnlyUnread: function(aViewFlags) {
return (aViewFlags & Ci.nsMsgViewFlagsType.kUnreadOnly) != 0;
}
},
/**
* Change the status bar text
*/
setStatusText: function(aDocument, aText) {
aDocument.getElementById("statusText").setAttribute("label", aText);
},
/**
* Get all servers as an array
*/
get allServers() {
let servers = [];
let allServers = Components.classes["@mozilla.org/messenger/account-manager;1"]
.getService(Ci.nsIMsgAccountManager).allServers;
for (let i = 0; i < allServers.Count(); ++i)
servers.push(allServers.GetElementAt(i).QueryInterface(Ci.nsIMsgIncomingServer));
return servers;
},
/**
* Get all subfolders of a folder as an array
*/
getSubFolders: function(aFolder) {
let subFolders = [];
let allFolders = Components.classes["@mozilla.org/supports-array;1"]
.createInstance(Ci.nsISupportsArray);
aFolder.ListDescendents(allFolders);
for (let i = 0; i < allFolders.Count(); ++i)
subFolders.push(allFolders.GetElementAt(i).QueryInterface(Ci.nsIMsgFolder));
return subFolders;
},
/**
* Get the folder which has aFlag set
*/
getFolderWithFlag: function(aServer, aFlag) {
let rootMsgFolder = aServer.rootMsgFolder;
if (rootMsgFolder && aFlag in Ci.nsMsgFolderFlags)
return rootMsgFolder.getFolderWithFlags(Ci.nsMsgFolderFlags[aFlag]);
return null;
},
/**
* Get the subject from a message header object
*/
getSubjectFromMsgHdr: function(aMsgHdr) {
if (aMsgHdr.mime2DecodedSubject)
return aMsgHdr.mime2DecodedSubject;
return aMsgHdr.subject;
},
/**
* Get the raw message (headers and body) from a message URI
*/
getRawMessageFromURI: function(aURI) {
let messenger = Components.classes["@mozilla.org/messenger;1"]
.createInstance(Ci.nsIMessenger);
let messageService = messenger.messageServiceFromURI(aURI);
let messageStream = Components.classes["@mozilla.org/network/sync-stream-listener;1"]
.createInstance(Ci.nsIInputStream);
let inputStream = Components.classes["@mozilla.org/scriptableinputstream;1"]
.createInstance(Ci.nsIScriptableInputStream);
inputStream.init(messageStream);
messageService.streamMessage(aURI, messageStream, {}, null, false, null);
let rawMessage = "";
inputStream.available();
while (inputStream.available())
rawMessage += inputStream.read(512);
messageStream.close();
inputStream.close();
return rawMessage;
},
/**
* Get the message body from a message URI
*/
getBodyFromURI: function(aURI) {
let rawMessage = this.getRawMessageFromURI(aURI);
let stringStream = Components.classes["@mozilla.org/io/string-input-stream;1"]
.createInstance(Ci.nsIStringInputStream);
stringStream.setData(rawMessage, rawMessage.length);
let messenger = Components.classes["@mozilla.org/messenger;1"]
.createInstance(Ci.nsIMessenger);
let messageService = messenger.messageServiceFromURI(aURI);
let msgHdr = messageService.messageURIToMsgHdr(aURI);
let body = msgHdr.folder.getMsgTextFromStream(stringStream, msgHdr.Charset,
msgHdr.messageSize, msgHdr.messageSize, false, true, {});
return body;
}
},
/**
* Copy HTML (and UTF-8) to the clipboard
*/
copyHTMLToClipboard: function(aHTMLText) {
let strlen = aHTMLText.length * 2;
let str = Components.classes["@mozilla.org/supports-string;1"]
.createInstance(Ci.nsISupportsString);
if (!str)
return false;
str.data = aHTMLText;
var trans = Components.classes["@mozilla.org/widget/transferable;1"]
.createInstance(Ci.nsITransferable);
if (!trans)
return false;
trans.addDataFlavor("text/unicode");
trans.setTransferData("text/unicode", str, strlen);
trans.addDataFlavor("text/html");
trans.setTransferData("text/html", str, strlen);
var clipboard = Components.classes["@mozilla.org/widget/clipboard;1"]
.getService(Ci.nsIClipboard);
if (!clipboard)
return false;
clipboard.setData(trans, null, Ci.nsIClipboard.kGlobalClipboard);
return true;
},
/**
* Write HTML (UTF-8) to a file
*/
writeHTMLToFile: function(aHTMLText, aSuggestedName) {
let dirService = Components.classes["@mozilla.org/file/directory_service;1"]
.getService(Ci.nsIProperties);
let file = dirService.get("Desk", Ci.nsIFile);
file.append(aSuggestedName);
file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0666);
var foStream = Components.classes["@mozilla.org/network/file-output-stream;1"]
.createInstance(Ci.nsIFileOutputStream);
foStream.init(file, 0x02 | 0x08 | 0x20, 0666, 0);
var converter = Components.classes["@mozilla.org/intl/converter-output-stream;1"]
.createInstance(Ci.nsIConverterOutputStream);
converter.init(foStream, "UTF-8", 0, 0);
converter.writeString(aHTMLText);
converter.close();
return file.path;
},
/**
* Open a file in Composer
*/
openFileInComposer: function(aWindow, aFile) {
aWindow.openDialog("chrome://editor/content/", "_blank",
"chrome,all,dialog=no", aFile);
},
/**
* Launch a file
*/
launchFile: function(aFile) {
let file = Components.classes["@mozilla.org/file/local;1"]
.createInstance(Ci.nsILocalFile);
file.initWithPath(aFile);
file.launch();
},
/**
* Change the status bar text
*/
setStatusText: function(aDocument, aText) {
aDocument.getElementById("statusbar-display").setAttribute("label", aText);
}
};
Der Code aus dieser Datei sorgt dafür, dass die Datei JHBF.jsm gefunden und
importiert wird. Gesucht wird sie in dem Pfad, auf den die Umgebungsvariable JHBFPATH
zeigt (d.h. der Pfad kann selbst gewählt werden). Die Pfadangabe ist
abhängig vom Betriebssystem, also etwa E:\Mozilla\buttons unter
Windows und /home/moz/buttons unter Linux. Die "custombutton://"-Links
unten stellen den Inhalt dieser Datei automatisch dem eigentlichen Button-Code voran,
falls der jeweilige Button-Code es erfordert.
/**
* Load JHBF (prepend this to any Custom Buttons code using JHBF!)
* You need to set the JHBFPATH environment variable on your computer
* to the path where you placed JHBF.jsm to make this work.
* @author Jens Hatlak <jh@junetz.de>
* @version 2010-07-15
*/
let envSvc = Components.classes["@mozilla.org/process/environment;1"]
.getService(Components.interfaces.nsIEnvironment);
let file = Components.classes["@mozilla.org/file/local;1"]
.createInstance(Components.interfaces.nsILocalFile);
if (!envSvc.exists("JHBFPATH"))
throw {message: "Environment variable JHBFPATH undefined"};
file.initWithPath(envSvc.get("JHBFPATH"));
file.append("JHBF.jsm");
if (!file.exists())
throw {message: "File not found: " + file.path};
let ios = Components.classes["@mozilla.org/network/io-service;1"]
.getService(Components.interfaces.nsIIOService);
let uri = ios.newFileURI(file);
Components.utils.import(uri.spec);
Hakt alle Checkboxen auf der aktuellen Seite an.
/**
* Check All Checkboxes
* @author Jens Hatlak <jh@junetz.de>
* @version 2010-07-15
*/
let fields = window.content.document.getElementsByTagName('input');
for (let i = 0; i < fields.length; ++i)
if (fields[i].type == 'checkbox' && fields[i].disabled == false)
fields[i].checked = true;Komprimiert alle Ordner aller Accounts. Umschalt festhalten, um die Abfrage zu überspringen.
/**
* Compact All Folders
* @author Jens Hatlak <jh@junetz.de>
* @version 2010-07-15
*/
let doIt = false;
if (event.shiftKey)
doIt = true;
else
doIt = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
.getService(Components.interfaces.nsIPromptService)
.confirm(window, "Compact All Folders", "Really compact all folders?");
if (doIt) {
JHBF.mailnews.allServers.forEach(function(server) {
let isImap = server.type == "imap";
let isNews = server.type == "nntp";
let compactOfflineAlso = isImap || isNews;
let folder = server.rootMsgFolder;
try {
if (!isImap || (server.canCompactFoldersOnServer &&
folder.isCommandEnabled("cmd_compactFolder")))
folder.compactAll(null, msgWindow, compactOfflineAlso);
} catch (e) {}
});
}Leert alle Papierkörbe aller Accounts. Umschalt festhalten, um die Abfrage zu überspringen.
/**
* Empty All Trashes
* @author Jens Hatlak <jh@junetz.de>
* @version 2010-07-15
*/
let doIt = false;
if (event.shiftKey)
doIt = true;
else
doIt = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
.getService(Components.interfaces.nsIPromptService)
.confirm(window, "Empty All Trashes", "Really empty all trashes?");
if (doIt) {
JHBF.mailnews.allServers.forEach(function(server) {
let folder = JHBF.mailnews.getFolderWithFlag(server, "Trash");
if (folder)
folder.emptyTrash(msgWindow, null);
});
JHBF.mailnews.setStatusText(document, "Emptied all trashes.");
}Ruft neue Nachrichten für alle Accounts ab, inklusive News-Accounts.
/**
* Get All Messages
* @author Jens Hatlak <jh@junetz.de>
* @version 2010-07-15
*/
MailTasksGetMessagesForAllServers(false, null, null); // all but News
const nsIMsgIncomingServer = Components.interfaces.nsIMsgIncomingServer;
JHBF.mailnews.allServers.forEach(function(server) {
if (server instanceof nsIMsgIncomingServer && server.type == "nntp")
server.performExpand(msgWindow);
});Kopiert alle selektierten Nachrichten in die Zwischenablage. Umschalt festhalten, um auch die Header mitzukopieren.
/**
* Copy Mails
* @author Jens Hatlak <jh@junetz.de>
* @version 2010-07-15
*/
let text = "";
let uris = gFolderDisplay.selectedMessageUris;
if (event.shiftKey) {
for (let i = 0; i < uris.length; ++i) {
let rawMail = JHBF.mailnews.getRawMessageFromURI(uris[i]);
var skip = false;
rawMail.split("\r\n").forEach(function(line) {
if (line.match(/^(From - )|(X-)/)) {
skip = true;
} else if (!skip || !line.match(/^\s+/)) {
skip = false;
text += line + "\r\n";
}
});
}
} else {
for (let i = 0; i < uris.length; ++i) {
let body = JHBF.mailnews.getBodyFromURI(uris[i]);
text += body;
}
}
JHBF.copyHTMLToClipboard(text);Wechelt die Ansicht zyklisch (Threads-Modus: Alle/Ungelesene/Threads mit ungelesenen/Beobachtete Thread mit ungelesenen, sonst: Alle/Ungelesen).
/**
* Switch View
* @author Jens Hatlak <jh@junetz.de>
* @version 2010-07-15
*/
let [viewFlags, viewType] = JHBF.mailnews.view.getFlagsAndType(gDBView);
let unreadOnly = JHBF.mailnews.view.showsOnlyUnread(viewFlags);
let threaded = JHBF.mailnews.view.isThreaded(viewFlags);
if (threaded) {
// cf. nsIMsgDBView.idl
let modes = {
AllMessages: !unreadOnly && viewType == nsMsgViewType.eShowAllThreads,
UnreadMessages: unreadOnly,
ThreadsWithUnread: viewType == nsMsgViewType.eShowThreadsWithUnread,
WatchedThreadsWithUnread: viewType == nsMsgViewType.eShowWatchedThreadsWithUnread
};
let currentIndex = null;
let candidates = [];
let menuItemNames = ["AllMessages", "UnreadMessages", "ThreadsWithUnread", "WatchedThreadsWithUnread"];
menuItemNames.forEach(function(aItem, aIndex) {
// The checked and disabled states have not been initialized here yet, but we only need the command
let menuItem = document.getElementById("view" + aItem + "MenuItem");
if (modes[aItem])
currentIndex = aIndex;
else if (DefaultController.isCommandEnabled(menuItem.command))
candidates[aIndex] = menuItem;
});
if (candidates.length > 0) {
let nextIndex = currentIndex + 1;
while (!candidates[nextIndex]) {
if (nextIndex < candidates.length)
nextIndex++;
else
nextIndex = 0;
}
if (candidates[nextIndex]) {
JHBF.mailnews.setStatusText(document, "Threads: " + candidates[nextIndex].label);
goDoCommand(candidates[nextIndex].command);
}
}
} else {
// Toggle between All and Unread views
let viewValue = gCurrentViewValue == kViewItemUnread ? kViewItemAll : kViewItemUnread;
let viewLabel = GetLabelForValue(viewValue);
ViewChange(viewValue, viewLabel);
JHBF.mailnews.setStatusText(document, "View: " + viewLabel);
}Lädt die aktuelle Ansicht neu.
/** * Reload View * @author Jens Hatlak <jh@junetz.de> * @version 2010-09-05 */ gMsgFolderSelected = null; FolderPaneSelectionChange(); // reload virtual folder ViewChangeByFolder(gMsgFolderSelected); // reload view
Erzeugt aus den selektierten Nachrichten (Pushlog-Feeds) die Rohform eines SMTT-Beitrags, kopiert den HTML-Code in die Zwischenablage, schreibt ihn in eine Datei und öffnet diese in einem neuen Composer-Fenster.
/**
* Prepare SMTT
* @author Jens Hatlak <jh@junetz.de>
* @version 2010-07-15
*/
let categories = {
"MailNews": [
"mail", "imap", "pop", "attachment", "newsgroup", "local folders", "inbox",
"message", "compose", "archiv", "account", "signature"
],
"Address Book": [ "addrbook", "address" ],
"Bookmarks": [ "bookmark" ],
"Download Manager": [ "download" ],
"History": [ "history" ],
"Help": [ "help", "document" ],
"Locales": [ "locale" ],
"Audio/Video": [ "audio", "video", "webm", "ogg", "vorbis", "theora", "vp8" ],
"Session Store": [ "session", "restore" ],
"Preferences": [ "preferences" ],
"Linux": [ "linux", "unix", "gtk" ],
"Mac": [ "mac", "os x" ],
"Compiling": [ "compiling", "build option", "client\.py" ],
"General": [],
};
function subject4smtt(aSubject) {
let subject = aSubject;
subject = subject.replace(/^[^-]* - /, "");
subject = subject.replace(/^.*RELBRANCH \| /, "");
subject = subject.replace(/(.*)(bug \d+)$/i, "$2 - $1");
subject = subject.replace(/^(bustage )?(fix )?(for )?bug (\d+)[ ,;:]*/i, "bug $4");
subject = subject.replace(/&/g, "&");
subject = subject.replace(/</g, "<");
subject = subject.replace(/>/g, ">");
subject = subject.replace(/"/g, """);
subject = subject.replace(/[ .,;]*[afmors/+]+=\S+[, ]/g, "");
subject = subject.replace(/[ .,;]*[afmors/+]+=\S+$/g, "");
subject = subject.replace(/^(Bug \d+)[- ;:]*(.*)/i, "$2 ($1)");
subject = subject.replace(/Bug (\d+)/gi, "<a href=\"https://bugzilla.mozilla.org/show_bug.cgi?id=$1\">Bug $1</a>");
return subject;
}
function catmatch(aSubject, aCat) {
for (let i = 0; i < aCat.length; ++i) {
let re = new RegExp(aCat[i], "i");
if (aSubject.search(re) != -1)
return true;
}
return false;
}
let catdata = {};
for (let cat in categories)
catdata[cat] = [];
let msgs = gFolderDisplay.selectedMessages;
for (let i = 0; i < msgs.length; ++i) {
let msgHdr = msgs[i];
let subject = JHBF.mailnews.getSubjectFromMsgHdr(msgHdr);
subject = subject4smtt(subject);
let pushed = false;
for (let cat in categories) {
if (catmatch(subject, categories[cat])) {
catdata[cat].push(subject);
pushed = true;
break;
}
}
if (!pushed)
catdata.General.push(subject);
}
let html = "";
for (let cat in catdata) {
if (catdata[cat].length) {
html += "<span style=\"font-weight: bold\">" + cat + ":</span>\n";
html += "<ul>\n";
for each (let subject in catdata[cat])
html += " <li>" + subject + "</li>\n";
html += "</ul>\n";
}
}
JHBF.copyHTMLToClipboard(html);
let file = JHBF.writeHTMLToFile(html, "smtt-prepare.html");
JHBF.openFileInComposer(window, file);Legt ein Lesezeichen für die Website der angezeigten Feed-Nachricht an.
/**
* Bookmark Feed Website
* @author Jens Hatlak <jh@junetz.de>
* @version 2011-12-13
*/
if (currentHeaderData && "content-base" in currentHeaderData) {
let url = currentHeaderData["content-base"].headerValue;
let title = currentHeaderData["subject"].headerValue;
PlacesUIUtils.showMinimalAddBookmarkUI(makeURI(url), title);
}
25. Dezember 2011 |