78 | You can use ' / ' (Space, Forward Slash,
79 | Space) as a delimiter for creating subcategories.
80 |
81 | e.g.
82 | CategoryA / CategoryB / CategoryC
83 |
86 |
7 |
8 | **Add-ons for Thunderbird page: [CategoryManager](https://addons.thunderbird.net/en-US/thunderbird/addon/categorymanager/)**
9 |
10 | ## Icon sources
11 |
12 | * [slider-on.png] by [John Bieling](https://github.com/jobisoft/TbSync/blob/master/content/skin/src/LICENSE)
13 | * [slider-off.png] by [John Bieling](https://github.com/jobisoft/TbSync/blob/master/content/skin/src/LICENSE)
14 | * [checkbox-all/some/none.png] by [Cole Bemis](https://www.iconfinder.com/icons/226561/check_square_icon)
15 | * [icon.png] based on 'Venn Diagram' by [WARPAINT Media Inc., CA](https://thenounproject.com/search/?q=three%20circles&i=31898#) from Noun Project ([info](https://github.com/jobisoft/CategoryManager/tree/master/sendtocategory/content/skin/catman))
16 |
17 | ## Contributing
18 |
19 | Open an issue if you want to ...
20 |
21 | 1. report a bug (Please check if there are existing issues for the bug first)
22 | 2. give us feedbacks and suggestions
23 |
24 | If you know HTML, JavaScript and CSS, you can directly contribute to this repo by creating a pull request.
25 |
26 | Read [DEVELOP.md](./DEVELOP.md) to get started.
27 |
--------------------------------------------------------------------------------
/src/external/fontawesome/css/v4-font-face.min.css:
--------------------------------------------------------------------------------
1 | /*!
2 | * Font Awesome Free 6.2.0 by @fontawesome - https://fontawesome.com
3 | * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
4 | * Copyright 2022 Fonticons, Inc.
5 | */
6 | @font-face{font-family:"FontAwesome";font-display:block;src:url(../webfonts/fa-solid-900.woff2) format("woff2"),url(../webfonts/fa-solid-900.ttf) format("truetype")}@font-face{font-family:"FontAwesome";font-display:block;src:url(../webfonts/fa-brands-400.woff2) format("woff2"),url(../webfonts/fa-brands-400.ttf) format("truetype")}@font-face{font-family:"FontAwesome";font-display:block;src:url(../webfonts/fa-regular-400.woff2) format("woff2"),url(../webfonts/fa-regular-400.ttf) format("truetype");unicode-range:u+f003,u+f006,u+f014,u+f016-f017,u+f01a-f01b,u+f01d,u+f022,u+f03e,u+f044,u+f046,u+f05c-f05d,u+f06e,u+f070,u+f087-f088,u+f08a,u+f094,u+f096-f097,u+f09d,u+f0a0,u+f0a2,u+f0a4-f0a7,u+f0c5,u+f0c7,u+f0e5-f0e6,u+f0eb,u+f0f6-f0f8,u+f10c,u+f114-f115,u+f118-f11a,u+f11c-f11d,u+f133,u+f147,u+f14e,u+f150-f152,u+f185-f186,u+f18e,u+f190-f192,u+f196,u+f1c1-f1c9,u+f1d9,u+f1db,u+f1e3,u+f1ea,u+f1f7,u+f1f9,u+f20a,u+f247-f248,u+f24a,u+f24d,u+f255-f25b,u+f25d,u+f271-f274,u+f278,u+f27b,u+f28c,u+f28e,u+f29c,u+f2b5,u+f2b7,u+f2ba,u+f2bc,u+f2be,u+f2c0-f2c1,u+f2c3,u+f2d0,u+f2d2,u+f2d4,u+f2dc}@font-face{font-family:"FontAwesome";font-display:block;src:url(../webfonts/fa-v4compatibility.woff2) format("woff2"),url(../webfonts/fa-v4compatibility.ttf) format("truetype");unicode-range:u+f041,u+f047,u+f065-f066,u+f07d-f07e,u+f080,u+f08b,u+f08e,u+f090,u+f09a,u+f0ac,u+f0ae,u+f0b2,u+f0d0,u+f0d6,u+f0e4,u+f0ec,u+f10a-f10b,u+f123,u+f13e,u+f148-f149,u+f14c,u+f156,u+f15e,u+f160-f161,u+f163,u+f175-f178,u+f195,u+f1f8,u+f219,u+f27a}
--------------------------------------------------------------------------------
/src/popup/compose.mjs:
--------------------------------------------------------------------------------
1 | import { toRFC5322EmailAddress } from "../modules/contacts/contact.mjs";
2 |
3 | export async function addContactsToComposeDetails(fieldName, state, contacts) {
4 | const details = await browser.compose.getComposeDetails(state.tab.id);
5 | const addresses = details[fieldName];
6 |
7 | let map = new Map();
8 | addresses.forEach((addr) => {
9 | const { address, name } = emailAddresses.parseOneAddress(addr);
10 | map.set(address, name);
11 | });
12 |
13 | // Remove contacts that do not have an email address and map the filtered
14 | // contacts to rfc 5322 email address format.
15 | contacts.forEach(contact => {
16 | // Add this contact if it doesn't exist in the map
17 | const { name, email } = contact;
18 | if (email != null && !map.has(email)) map.set(email, name);
19 | });
20 |
21 | // Set compose details.
22 | const emailList = [...map.entries()].map(toRFC5322EmailAddress);
23 | return browser.compose.setComposeDetails(state.tab.id, {
24 | ...details,
25 | [fieldName]: emailList,
26 | });
27 | }
28 |
29 | export async function openComposeWindowWithContacts(
30 | fieldName,
31 | state,
32 | contacts,
33 | categoryPath
34 | ) {
35 | let map = new Map();
36 |
37 | // Remove contacts that do not have an email address and map the filtered
38 | // contacts to rfc 5322 email address format.
39 | contacts.forEach(contact => {
40 | const { name, email } = contact;
41 | if (email != null && !map.has(email)) map.set(email, name);
42 | });
43 |
44 | // Open compose window.
45 | const emailList = [...map.entries()].map(toRFC5322EmailAddress);
46 | return browser.compose.beginNew(null, {
47 | [fieldName]: emailList,
48 | subject: `[${categoryPath}]`,
49 | });
50 | }
51 |
--------------------------------------------------------------------------------
/src/popup/contact-list.mjs:
--------------------------------------------------------------------------------
1 | import {
2 | Component,
3 | escapeHtmlContent,
4 | escapeHtmlAttr,
5 | } from "../modules/ui/ui.mjs";
6 |
7 | export function createContactList(data) {
8 | let component = new Component({
9 | element: "#contacts",
10 | data,
11 | template(data) {
12 | let html = [];
13 | if (data?.addressBook != null) {
14 | data.contacts.forEach((contact, id) => {
15 | const { name, email, addressBookId } = contact;
16 | html.push(
17 | `