Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dev115 #201

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
182 changes: 182 additions & 0 deletions api/CustomFolderSort/implementation.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
var { ExtensionCommon } = ChromeUtils.import("resource://gre/modules/ExtensionCommon.jsm");
var { MailServices } = ChromeUtils.import("resource:///modules/MailServices.jsm");

function getAbout3PaneWindow(nativeTab) {
if (nativeTab.mode && nativeTab.mode.name == "mail3PaneTab") {
return nativeTab.chromeBrowser.contentWindow
}
return null;
}

function reloadFolders(folderPane) {
for (let mode of Object.values(folderPane._modes)) {
if (!mode.active) {
continue;
}
mode.containerList.replaceChildren();
folderPane._initMode(mode);
}
};

function folderURIToPath(accountId, uri) {
let server = MailServices.accounts.getAccount(accountId).incomingServer;
let rootURI = server.rootFolder.URI;
if (rootURI == uri) {
return "/";
}
// The .URI property of an IMAP folder doesn't have %-encoded characters, but
// may include literal % chars. Services.io.newURI(uri) applies encodeURI to
// the returned filePath, but will not encode any literal % chars, which will
// cause decodeURIComponent to fail (bug 1707408).
if (server.type == "imap") {
return uri.substring(rootURI.length);
}
let path = Services.io.newURI(uri).filePath;
return path.split("/").map(decodeURIComponent).join("/");
}

function getFolderId(accountId, path) {
let data = `${accountId}:${path}`;
return data;

let arr = new TextEncoder().encode(data);
let str = "";
for (let i = 0; i < arr.length; i += 65536) {
str += String.fromCharCode.apply(null, arr.subarray(i, i + 65536));
}
return btoa(str);
}

async function install(window, preferences) {
for (let i = 0; i < 20; i++) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a brief comment like "wait up to 2500 seconds to folder pane to appear"

if (window.folderPane && window.folderPane._initialized) {
break;
}
await new Promise(r => window.setTimeout(r, 125))
}
if (!window.folderPane || window.folderPane._manualSortFolderBackupAddSubFolders) {
return;
}

window.folderPane._manualSortFolderBackupAddSubFolders = window.folderPane._addSubFolders;
window.folderPane._addSubFolders = function (parentFolder, parentRow, modeName, filterFunction) {
console.log("ManualSortFolders: about:3pane is patched");

let subFolders = parentFolder.subFolders;
if (!subFolders.length) {
return;
}

for (let i = 0; i < subFolders.length; i++) {
let folder = subFolders[i];
if (this._isGmailFolder(folder)) {
subFolders.splice(i, 1, ...folder.subFolders);
}
}

let server = parentFolder.server;
let accountId = MailServices.accounts.FindAccountForServer(server).key;
let sortType = preferences.accountSettings.has(accountId)
? preferences.accountSettings.get(accountId).type
: null

switch (sortType) {
case "2": // custom
{
let sortData = new Map();
// Add custom sortKey
for (let i = 0; i < subFolders.length; i++) {
let folder = subFolders[i];
let path = folderURIToPath(accountId, folder.URI);

if (preferences.folderSort.has(getFolderId(accountId, path))) {
sortData.set(folder.URI, preferences.folderSort.get(getFolderId(accountId, path)));
} else {
sortData.set(folder.URI, `_${i}`);
}
}
// Custom sort
subFolders.sort((a, b) => sortData.get(a.URI) > sortData.get(b.URI));
}
break;

case "1":
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a brief comment which case this is, as done above. Same for the remaining cases.

subFolders.sort((a, b) => a.prettyName > b.prettyName)
break;

case "3":
subFolders.sort((a, b) => a.prettyName < b.prettyName)
break;

default:
subFolders.sort((a, b) => a.compareSortKeys(b));
}

for (let folder of subFolders) {
if (typeof filterFunction == "function" && !filterFunction(folder)) {
continue;
}
let folderRow = this._createFolderRow(modeName, folder);
this._addSubFolders(folder, folderRow, modeName, filterFunction);
parentRow.childList.appendChild(folderRow);
}
}

console.log("ManualSortFolders: patched _addSubFolders(), Reload")
reloadFolders(window.folderPane);
}

async function uninstall(window) {
for (let i = 0; i < 20; i++) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a timeout comment here, to.

if (window.folderPane && window.folderPane._initialized) {
break;
}
await new Promise(r => window.setTimeout(r, 125))
}
if (!window.folderPane || !window.folderPane._manualSortFolderBackupAddSubFolders) {
return;
}

window.folderPane._addSubFolders = window.folderPane._manualSortFolderBackupAddSubFolders;
delete window.folderPane._manualSortFolderBackupAddSubFolders;

console.log("ManualSortFolders: restored _addSubFolders(), Reload")
reloadFolders(window.folderPane);
}

var CustomFolderSort = class extends ExtensionCommon.ExtensionAPI {
getAPI(context) {
return {
CustomFolderSort: {
async patch(tabId, preferences) {
let { nativeTab } = context.extension.tabManager.get(tabId);
let about3PaneWindow = getAbout3PaneWindow(nativeTab);
if (about3PaneWindow) {
install(about3PaneWindow, preferences);
}
},
async update(tabId, preferences) {
let { nativeTab } = context.extension.tabManager.get(tabId);
let about3PaneWindow = getAbout3PaneWindow(nativeTab);
if (about3PaneWindow) {
uninstall(about3PaneWindow);
install(about3PaneWindow, preferences);
}
},
},
};
}

onShutdown(isAppShutdown) {
if (isAppShutdown) return;

for (let window of Services.wm.getEnumerator("mail:3pane")) {
for (let nativeTab of window.gTabmail.tabInfo) {
let about3PaneWindow = getAbout3PaneWindow(nativeTab);
if (about3PaneWindow) {
uninstall(about3PaneWindow);
}
}
}
}
};
37 changes: 37 additions & 0 deletions api/CustomFolderSort/schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
[
{
"namespace": "CustomFolderSort",
"functions": [
{
"name": "patch",
"type": "function",
"async": true,
"parameters": [
{
"type": "integer",
"name": "tabId"
},
{
"type": "any",
"name": "preferences"
}
]
},
{
"name": "update",
"type": "function",
"async": true,
"parameters": [
{
"type": "integer",
"name": "tabId"
},
{
"type": "any",
"name": "preferences"
}
]
}
]
}
]
60 changes: 60 additions & 0 deletions api/LegacyPrefs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
## Objective

Use this API to access Thunderbird's system preferences or to migrate your own preferences from the Thunderbird preference system to the local storage of your MailExtension.

## Usage

Add the [LegacyPrefs API](https://github.com/thundernest/addon-developer-support/tree/master/auxiliary-apis/LegacyPrefs) to your add-on. Your `manifest.json` needs an entry like this:

```
"experiment_apis": {
"LegacyPrefs": {
"schema": "api/LegacyPrefs/schema.json",
"parent": {
"scopes": ["addon_parent"],
"paths": [["LegacyPrefs"]],
"script": "api/LegacyPrefs/implementation.js"
}
}
},
```

## API Functions

This API provides the following functions:

### async getPref(aName, [aFallback])

Returns the value for the ``aName`` preference. If it is not defined or has no default value assigned, ``aFallback`` will be returned (which defaults to ``null``).

### async getUserPref(aName)

Returns the user defined value for the ``aName`` preference. This will ignore any defined default value and will only return an explicitly set value, which differs from the default. Otherwise it will return ``null``.

### clearUserPref(aName)

Clears the user defined value for preference ``aName``. Subsequent calls to ``getUserPref(aName)`` will return ``null``.

### async setPref(aName, aValue)

Set the ``aName`` preference to the given value. Will return false and log an error to the console, if the type of ``aValue`` does not match the type of the preference.

## API Events

This API provides the following events:

### onChanged.addListener(listener, branch)

Register a listener which is notified each time a value in the specified branch is changed. The listener returns the name and the new value of the changed preference.

Example:

```
browser.LegacyPrefs.onChanged.addListener(async (name, value) => {
console.log(`Changed value in "mailnews.": ${name} = ${value}`);
}, "mailnews.");
```

---

A detailed example using the LegacyPref API to migrate add-on preferences to the local storage can be found in [/scripts/preferences/](https://github.com/thundernest/addon-developer-support/tree/master/scripts/preferences).
Loading