Initial working state

This commit is contained in:
Mahesh Asolkar 2018-10-23 23:19:50 -07:00
commit cbdd1e3231
14 changed files with 550 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
*.xpi
*.zip

17
Makefile Normal file
View File

@ -0,0 +1,17 @@
#
# Build extension package
#
VERSION = 2.0
SOURCES = manifest.json data/* icons/*
PACKAGE = ../emptyem-$(VERSION).xpi
EXCLUDES = Makefile README.md *.DS_Store
build: $(PACKAGE)
@echo "Done"
$(PACKAGE): $(SOURCES)
zip -r -FS $(PACKAGE) * -x $(EXCLUDES)
@echo "Built $(PACKAGE)"
clean:
rm $(PACKAGE)

25
README.md Normal file
View File

@ -0,0 +1,25 @@
# Empty 'em
This is a simple Thunderbird extension that empties all _Junk_ and _Trash_
folders. The extension provides a menu item (`Tools` -> `Empty 'em`) and a
toolbar button to do so.
## Background
In Thunderbird, I usually have around half-a-dozen email accounts set up. Most
of them [Gmail](http://gmail.com) accounts. Very often, Gmail's Junk filter
catches tons of Junk email and populates the _Junk_ folders in Thunderbird.
After manually cleaning up the _Inbox_ folders and deleting unwanted email, the
_Trash_ folders are also populated.
Manually going through all the _Junk_ and _Trash_ folders to empty them, is
tedious.
That is what prompted me to write this extension. I hope someone out there
finds it helpful too.
# References
* https://developer.mozilla.org/en-US/docs/Mozilla/Thunderbird/Thunderbird_extensions
* https://developer.mozilla.org/en-US/docs/Mozilla/Thunderbird/Thunderbird_extensions/Demo_Addon
* https://developer.mozilla.org/en-US/docs/Mozilla/Thunderbird/Thunderbird_extensions/Building_a_Thunderbird_extension

21
RESEARCH.md Normal file
View File

@ -0,0 +1,21 @@
# About New WebExtensions API
## IRC: inforation from #maildev
11:21:30 : moz_mma : In Thunderbird 64.0a1, using ChromeUtils.import() in webextension results in "ReferenceError: ChromeUtils is not defined" error. Any pointers?
12:00:15 : &Fallen : moz_mma: unless you are in an WebExtensions Experiment context, ChromeUtils and all the chrome code is not available
you need to make due with the APIs available, and create wrappers for APIs that are missing
12:04:25 : moz_mma : Fallen, how do I get in WebExtensions Experiment context?
12:04:45 : &Fallen : moz_mma: https://github.com/thundernest/tb-web-ext-experiments/ explains it mostly
for the cloudfile experiment, you would have access in e.g. https://github.com/thundernest/tb-web-ext-experiments/blob/master/cloudfile/cloudfile-api/api.js but not https://github.com/thundernest/tb-web-ext-experiments/blob/master/cloudfile/background/background.js
please be sure to read the intro to that repo so you know what the APIs should look like
12:07:30 : moz_mma : Thanks Fallen
12:14:03 : +aceman : Fallen: the ChromeUtils question above
## Demo add-on
https://developer.mozilla.org/en-US/docs/Mozilla/Thunderbird/Thunderbird_extensions/Demo_Addon
## Utils in comm-central mailnews/base
https://github.com/mozilla/releases-comm-central/tree/master/mailnews/base/util

17
data/actions.js Normal file
View File

@ -0,0 +1,17 @@
//
// Empty 'em actions
//
"use strict";
// Browser actions
function emptyEm() {
console.log("Empty-em: Emptying.. Start");
browser.folder_actions.emptyTrashFolders();
browser.folder_actions.emptyJunkFolders();
console.log("Empty-em: Emptying.. Done");
}
// Handlers
browser.browserAction.onClicked.addListener(emptyEm);
// vim: ts=2 sw=2 sts=2 et

13
data/jquery-ui.min.js vendored Normal file

File diff suppressed because one or more lines are too long

4
data/jquery.min.js vendored Normal file

File diff suppressed because one or more lines are too long

125
data/options.html Normal file
View File

@ -0,0 +1,125 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style type="text/css">
body {
font-family: sans-serif;
}
.option-cont {
display: inline-box;
vertical-align: top;
padding-top: 1.2em;
padding-bottom: 1.2em;
border-bottom: thin solid #ddd;
}
.option-info {
display: inline-block;
vertical-align: top;
width: 400px;
}
.option-val {
display: inline-block;
width: 50px;
}
.option-head {
font-weight: bold;
font-size: medium;
}
.option-desc {
font-size: small;
margin-top: 1.2em;
}
</style>
</head>
<body>
<div style="width: 600px">
<label>
<div class="option-cont">
<div class="option-info">
<div class="option-head">Override folder's confirmation setting</div>
<div class="option-desc">
By default, Thunderbird will prompt you to confirm deletion of selected
folders. Check this box to override this and suppress the conform
dialog
</div>
</div>
<div class="option-val">
<input type="checkbox" id="overrideDeleteConfirm" style="margin-bottom:1em"></input>
</div>
</div>
</label>
<label>
<div class="option-cont">
<div class="option-info">
<div class="option-head">Include Trash folders</div>
<div class="option-desc">
Include Trash folders in the process of emptying
</div>
</div>
<div class="option-val">
<input type="checkbox" id="selectTrashDelete" style="margin-bottom:1em"></input>
</div>
</div>
</label>
<label>
<div class="option-cont">
<div class="option-info">
<div class="option-head">Include Junk folders</div>
<div class="option-desc">
Include Junk folders in the process of emptying
</div>
</div>
<div class="option-val">
<input type="checkbox" id="selectJunkDelete" style="margin-bottom:1em"></input>
</div>
</div>
</label>
<label>
<div class="option-cont">
<div class="option-info">
<div class="option-head">Enable console debug messages</div>
<div class="option-desc">
Check this box to enable debug messages in Error Console. Provide
these messages along with bug reports you file
</div>
</div>
<div class="option-val">
<input type="checkbox" id="consoleDebug" style="margin-bottom:1em"></input>
</div>
</div>
</label>
<label>
<div class="option-cont">
<div class="option-info">
<div class="option-head">Disable done notification</div>
<div class="option-desc">
Check this box to disable notification that appears after all
configured folders have been emptied
</div>
</div>
<div class="option-val">
<input type="checkbox" id="disableDoneNotification" style="margin-bottom:1em"></input>
</div>
</div>
</label>
<label>
<div class="option-cont">
<div class="option-info">
<div class="option-head">Also compact emptied folders</div>
<div class="option-desc">
Also compact the folder that was emptied
</div>
</div>
<div class="option-val">
<input type="checkbox" id="alsoCompact" style="margin-bottom:1em"></input>
</div>
</div>
</label>
</div>
<script src="jquery.min.js"></script>
<script src="options.js"></script>
</body>
</html>

3
data/options.js Normal file
View File

@ -0,0 +1,3 @@
// -----------------------------------------------
// Options UI and handling
// -----------------------------------------------

192
folder_actions-api/api.js Normal file
View File

@ -0,0 +1,192 @@
"use strict";
ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
ChromeUtils.import("resource:///modules/MailServices.jsm");
ChromeUtils.import("resource:///modules/iteratorUtils.jsm");
ChromeUtils.import("resource:///modules/folderUtils.jsm");
// API provider for Folder Actions
class FolderActionsProvider extends ExtensionCommon.EventEmitter {
constructor(extension) {
super();
this.extension = extension;
}
get type() {
return this.extension.id;
}
get displayName() {
return this.extension.manifest.folder_actions.name;
}
get iconClass() {
let { icon } = ExtensionParent.IconDetails.getPreferredIcon(this.extension.manifest.icons, this.extension, 32);
return this.extension.getURL(icon);
}
register() {
let uuidgen = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator);
let registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
let contractID = "@mozilla.org/mail/folder_actions;1?type=" + this.extension.id.replace(/@/g, "-");
let self = this;
// unregisterFactory does not clear the contract id from Components.classes, therefore re-use
// the class id from the unregistered factory
if (contractID in Cc) {
this.classID = Components.ID(Cc[contractID].number);
} else {
this.classID = Components.ID(uuidgen.generateUUID().toString());
}
let factory = {
QueryInterface: ChromeUtils.generateQI([Ci.nsIFactory]),
createInstance: function(outer, iid) {
if (outer !== null) {
throw Cr.NS_ERROR_NO_AGGREGATION;
}
return self.QueryInterface(iid);
},
lockFactory: function(doLock) {
throw Cr.NS_ERROR_NOT_IMPLEMENTED;
}
};
this.factory = factory.QueryInterface(Ci.nsIFactory);
registrar.registerFactory(
this.classID, `Cloud file provider for ${this.extension.id}`, contractID, this.factory
);
XPCOMUtils.categoryManager.addCategoryEntry(
"cloud-files", this.extension.id, contractID, false, true
);
}
unregister() {
let registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
registrar.unregisterFactory(this.classID, this.factory);
XPCOMUtils.categoryManager.deleteCategoryEntry("cloud-files", this.extension.id, false);
}
}
FolderActionsProvider.prototype.QueryInterface = ChromeUtils.generateQI([Ci.nsIMsgFolderActionsProvider]);
//
// Implementation of folder_actions API
//
this.folder_actions = class extends ExtensionAPI {
async onManifestEntry(entryName) {
if (entryName == "folder_actions" && !this.provider) {
this.provider = new FolderActionsProvider(this.extension);
this.provider.register();
}
}
onShutdown() {
if (this.provider) {
this.provider.unregister();
}
}
getFolderInfo() {
let data = [];
toArray(fixIterator(MailServices.accounts.accounts,Components.interfaces.nsIMsgAccount)).map(function(account) {
let info = {
server: null,
type: null,
emails: [],
trashFolder: null,
junkFolder: null,
name: null,
};
let server = account.incomingServer;
let find_flagged_folders = function(folder,inf) {
if (folder.getFlag(Ci.nsMsgFolderFlags.Trash)) {
console.log("Folder: " + folder.URI + " has Trash flag ");
inf.trashFolder = folder;
console.log(folder);
} else if (folder.getFlag(Ci.nsMsgFolderFlags.Junk)) {
console.log("Folder: " + folder.URI + " has Junk flag ");
inf.junkFolder = folder;
console.log(folder);
} else {
if (folder.hasSubFolders) {
toArray(fixIterator(folder.subFolders, Ci.nsMsgFolder)).map(function (f) {
find_flagged_folders(f,inf);
});
}
}
};
if (server) {
info.server = server.prettyName;
info.type = server.type;
if (info.type != "none") {
find_flagged_folders(server.rootFolder,info);
}
}
toArray(fixIterator(account.identities, Ci.nsIMsgIdentity)).map(function(id) {
if (id.email) info.emails.push(id.email);
});
data.push(info);
});
console.log(data);
return data;
}
getAPI(context) {
const EventManager = ExtensionCommon.EventManager;
let self = this;
let mw = Components.classes["@mozilla.org/messenger/msgwindow;1"]
.createInstance(Components.interfaces.nsIMsgWindow);;
return {
folder_actions: {
emptyFolder: function(folderName) {
let finfo = self.getFolderInfo();
},
emptyTrashFolders: function() {
let finfo = self.getFolderInfo();
finfo.map(function(info) {
if (info.trashFolder) {
console.log("emptyTrashFolders: Considering " + info.trashFolder.URI);
console.log("emptyTrashFolders: canDeleteMessages? " + info.trashFolder.canDeleteMessages);
console.log("emptyTrashFolders: hasSubFolders? " + info.trashFolder.hasSubFolders);
console.log(info.trashFolder);
info.trashFolder.emptyTrash(null,null);
}
});
},
emptyJunkFolders: function() {
let finfo = self.getFolderInfo();
finfo.map(function(info) {
if (info.junkFolder) {
console.log("emptyTrashFolders: Considering " + info.junkFolder.URI);
console.log("emptyTrashFolders: canDeleteMessages? " + info.junkFolder.canDeleteMessages);
console.log("emptyTrashFolders: hasSubFolders? " + info.junkFolder.hasSubFolders);
console.log(info.junkFolder);
var junk_msgs = Cc["@mozilla.org/array;1"]
.createInstance(Ci.nsIMutableArray);
var enumerator = info.junkFolder.messages;
while (enumerator.hasMoreElements())
{
var msg_hdr = enumerator.getNext().QueryInterface(Ci.nsIMsgDBHdr);
junk_msgs.appendElement(msg_hdr, false);
}
if (junk_msgs.length) {
info.junkFolder.deleteMessages(junk_msgs, mw, false, false, null, true);
}
}
});
}
}
};
}
};
// vim: ts=2 sw=2 sts=2 et

View File

@ -0,0 +1,78 @@
[
{
"namespace": "manifest",
"types": [
{
"$extend": "WebExtensionManifest",
"properties": {
"folder_actions": {
"type": "object",
"additionalProperties": { "$ref": "UnrecognizedProperty" },
"properties": {
"name": { "type": "string", "preprocess": "localize" }
},
"optional": true
}
}
}
]
},
{
"namespace": "folder_actions",
"events": [
{
"name": "onEmptyFolder",
"type": "function",
"description": "Fired when a folder is emptied",
"parameters": [
{
"name": "folderName",
"$ref": "MailFolder",
"description": "The folder to be emptied"
}
]
}
],
"types": [
{
"id": "MailFolder",
"type": "object",
"description": "Information about a mail folder",
"properties": {
"name": {
"type": "string",
"description": "Name of the folder to be emptied"
}
}
}
],
"functions": [
{
"name": "emptyFolder",
"type": "function",
"description": "Empties specified mail folder",
"parameters": [
{
"name": "folderName",
"$ref": "MailFolder",
"description": "The mail folder to empty"
}
]
},
{
"name": "emptyTrashFolders",
"type": "function",
"description": "Empties Trash folders in all accounts",
"parameters": []
},
{
"name": "emptyJunkFolders",
"type": "function",
"description": "Empties Junk folders in all accounts",
"parameters": []
}
]
}
]
// vim: ts=2 sw=2 sts=2 et

BIN
icons/button.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

BIN
icons/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

53
manifest.json Normal file
View File

@ -0,0 +1,53 @@
{
"manifest_version": 2,
"name": "Empty 'em",
"version": "2.0",
"description": "Empty all your Trash and Junk folders in one fell swoop!",
"homepage_url": "https://github.com/asolkar/emptyem",
"icons": {
"256": "icons/icon.png"
},
"applications": {
"gecko": {
"id": "@emptyem",
"strict_min_version": "63.0"
}
},
"browser_action": {
"default_icon": {
"16": "icons/icon.png",
"32": "icons/icon.png"
},
"default_title": "Empty 'em"
},
// "folder_actions": {
// "name": "EmptyEm"
// },
"options_ui": {
"page": "data/options.html"
},
"background": {
"scripts": ["data/actions.js"]
},
"experiment_apis": {
"folder_actions": {
"schema": "folder_actions-api/schema.json",
"parent": {
"scopes": ["addon_parent"],
"script": "folder_actions-api/api.js",
"paths": [
["folder_actions"]
]
}
}
}
}
// vim: ts=2 sw=2 sts=2 et