diff --git a/src/plugins/built-in/messageFolders/index.ts b/src/plugins/built-in/messageFolders/index.ts
index e8170e86..a3e9161e 100644
--- a/src/plugins/built-in/messageFolders/index.ts
+++ b/src/plugins/built-in/messageFolders/index.ts
@@ -22,6 +22,7 @@ interface Folder {
id: string;
name: string;
color: string;
+ emoji: string;
}
interface MessageFoldersStorage {
@@ -34,12 +35,33 @@ const FOLDER_COLORS = [
"#8b5cf6", "#ec4899", "#14b8a6", "#f97316",
];
+const FOLDER_HEROICONS = [
+ ``,
+ ``,
+ ``,
+ ``,
+ ``,
+ ``,
+ ``,
+ ``,
+ ``,
+ ``,
+ ``,
+ ``,
+ ``,
+ ``,
+ ``,
+ ``,
+];
+
const FOLDER_ICON_SVG = ``;
const PLUS_SVG = ``;
const CHECK_SVG_WHITE = ``;
const CLOSE_SVG = ``;
const EDIT_SVG = ``;
const TRASH_SVG = ``;
+const CHEVRON_SVG = ``;
+const DRAG_SVG = ``;
function generateId(): string {
return Date.now().toString(36) + Math.random().toString(36).slice(2, 7);
@@ -49,7 +71,7 @@ const messageFoldersPlugin: Plugin void) | null = null;
+ let foldedSection: HTMLElement | null = null;
const unregisters: Array<{ unregister: () => void }> = [];
- // ── Storage accessors ──
-
const getFolders = (): Folder[] => api.storage.folders ?? [];
const getAssignments = (): Record => api.storage.messageAssignments ?? {};
@@ -94,6 +115,18 @@ const messageFoldersPlugin: Plugin {
+ const assignments = getAssignments();
+ if (!assignments[folderId]) assignments[folderId] = [];
+ const idx = assignments[folderId].indexOf(messageId);
+ if (add && idx < 0) {
+ assignments[folderId].push(messageId);
+ } else if (!add && idx >= 0) {
+ assignments[folderId].splice(idx, 1);
+ }
+ saveAssignments(assignments);
+ };
+
const toggleMessageInFolder = (messageId: string, folderId: string) => {
const assignments = getAssignments();
if (!assignments[folderId]) assignments[folderId] = [];
@@ -129,16 +162,28 @@ const messageFoldersPlugin: Plugin {
+ const selectedMsg = document.querySelector("[class*='MessageList__selected___']");
+ return selectedMsg?.getAttribute("data-message") ?? null;
+ };
- const showConfirmModal = (
- title: string,
- message: string,
- onConfirm: () => void,
- ) => {
+ const getMessageIdFromEvent = (target: HTMLElement): string | null => {
+ const li = target.closest("li[data-message]");
+ return li?.getAttribute("data-message") ?? null;
+ };
+
+ const getAllVisibleMessageIds = (): string[] => {
+ const ids: string[] = [];
+ document.querySelectorAll("[class*='MessageList__MessageList___'] ol > li[data-message]").forEach((li) => {
+ const id = li.getAttribute("data-message");
+ if (id) ids.push(id);
+ });
+ return ids;
+ };
+
+ const showConfirmModal = (title: string, message: string, onConfirm: () => void) => {
const overlay = document.createElement("div");
overlay.className = "bsplus-modal-overlay";
-
const modal = document.createElement("div");
modal.className = "bsplus-modal";
modal.innerHTML = `
@@ -150,16 +195,13 @@ const messageFoldersPlugin: Plugin
`;
overlay.appendChild(modal);
-
const remove = () => {
overlay.remove();
document.removeEventListener("keydown", onKey);
};
-
const onKey = (e: KeyboardEvent) => {
if (e.key === "Escape") remove();
};
-
overlay.addEventListener("click", (e) => {
if (e.target === overlay) remove();
});
@@ -168,36 +210,42 @@ const messageFoldersPlugin: Plugin {
const sidebar = document.querySelector("[class*='Viewer__sidebar___']");
if (!sidebar) return;
-
const ol = sidebar.querySelector("ol");
if (!ol) return;
- let section = ol.querySelector(".bsplus-folders-section");
+ let section = ol.querySelector(".bsplus-folders-section") as HTMLElement;
if (!section) {
section = document.createElement("div");
section.className = "bsplus-folders-section";
ol.appendChild(section);
}
+ foldedSection = section;
const folders = getFolders();
- const existingInput = section.querySelector(".bsplus-folder-input");
- const existingColors = section.querySelector(".bsplus-folder-colors");
-
section.innerHTML = "";
- // Header
const header = document.createElement("div");
header.className = "bsplus-folders-header";
+ header.dataset.folded = "false";
+
+ const collapseBtn = document.createElement("button");
+ collapseBtn.className = "bsplus-folders-collapse";
+ collapseBtn.innerHTML = CHEVRON_SVG;
+ collapseBtn.title = "Collapse";
+ collapseBtn.addEventListener("click", (e) => {
+ e.stopPropagation();
+ const isFolded = collapseBtn.classList.toggle("bsplus-folded");
+ section.classList.toggle("bsplus-section-folded", isFolded);
+ collapseBtn.title = isFolded ? "Expand" : "Collapse";
+ });
+ header.appendChild(collapseBtn);
const label = document.createElement("span");
label.textContent = "Folders";
@@ -214,9 +262,8 @@ const messageFoldersPlugin: Plugin
All Messages
@@ -226,20 +273,34 @@ const messageFoldersPlugin: Plugin {
+ applyFolderFilter();
+ applyBadges();
+ }, 100);
});
section.appendChild(allItem);
- // Folder items
for (const folder of folders) {
const item = document.createElement("div");
item.className = `bsplus-folder-item${activeFolderId === folder.id ? " bsplus-folder-active" : ""}`;
item.dataset.folderId = folder.id;
+ item.draggable = true;
+
+ const dragHandle = document.createElement("div");
+ dragHandle.className = "bsplus-folder-drag";
+ dragHandle.innerHTML = DRAG_SVG;
+ item.appendChild(dragHandle);
const dot = document.createElement("div");
dot.className = "bsplus-folder-dot";
dot.style.background = folder.color;
item.appendChild(dot);
+ const iconSpan = document.createElement("span");
+ iconSpan.className = "bsplus-folder-icon";
+ iconSpan.innerHTML = folder.emoji || FOLDER_HEROICONS[0];
+ item.appendChild(iconSpan);
+
const name = document.createElement("span");
name.className = "bsplus-folder-name";
name.textContent = folder.name;
@@ -264,21 +325,17 @@ const messageFoldersPlugin: Plugin {
e.stopPropagation();
- showConfirmModal(
- "Delete folder",
- `Remove "${folder.name}"? Messages won't be deleted.`,
- () => {
- const folders = getFolders().filter((f) => f.id !== folder.id);
- saveFolders(folders);
- const assignments = getAssignments();
- delete assignments[folder.id];
- saveAssignments(assignments);
- if (activeFolderId === folder.id) activeFolderId = null;
- applyFolderFilter();
- applyBadges();
- renderSidebarFolders();
- },
- );
+ showConfirmModal("Delete folder", `Remove "${folder.name}"? Messages won't be deleted.`, () => {
+ const folders = getFolders().filter((f) => f.id !== folder.id);
+ saveFolders(folders);
+ const assignments = getAssignments();
+ delete assignments[folder.id];
+ saveAssignments(assignments);
+ if (activeFolderId === folder.id) activeFolderId = null;
+ applyFolderFilter();
+ applyBadges();
+ renderSidebarFolders();
+ });
});
actions.appendChild(deleteBtn);
@@ -295,15 +352,89 @@ const messageFoldersPlugin: Plugin {
+ applyFolderFilter();
+ applyBadges();
+ }, 100);
+ });
+
+ item.addEventListener("dragstart", (e) => {
+ e.dataTransfer?.setData("text/plain", `reorder:${folder.id}`);
+ item.classList.add("bsplus-dragging");
+ });
+ item.addEventListener("dragend", () => {
+ item.classList.remove("bsplus-dragging");
+ document.querySelectorAll(".bsplus-folder-item").forEach((el) => el.classList.remove("bsplus-drag-over"));
+ });
+ item.addEventListener("dragover", (e) => {
+ e.preventDefault();
+ const data = e.dataTransfer?.getData("text/plain") || "";
+ if (data.startsWith("reorder:") && !data.includes(folder.id)) {
+ item.classList.add("bsplus-drag-over");
+ }
+ });
+ item.addEventListener("dragleave", () => {
+ item.classList.remove("bsplus-drag-over");
+ });
+ item.addEventListener("drop", (e) => {
+ e.preventDefault();
+ item.classList.remove("bsplus-drag-over");
+ const data = e.dataTransfer?.getData("text/plain") || "";
+ if (data.startsWith("reorder:")) {
+ const draggedId = data.replace("reorder:", "");
+ const folders = getFolders();
+ const draggedIdx = folders.findIndex((f) => f.id === draggedId);
+ const targetIdx = folders.findIndex((f) => f.id === folder.id);
+ if (draggedIdx >= 0 && targetIdx >= 0 && draggedIdx !== targetIdx) {
+ const [removed] = folders.splice(draggedIdx, 1);
+ folders.splice(targetIdx, 0, removed);
+ saveFolders(folders);
+ renderSidebarFolders();
+ }
+ }
});
section.appendChild(item);
}
- // Restore input if it was open
- if (existingInput || existingColors) {
- // Don't restore – let user re-trigger
- }
+ section.addEventListener("dragover", (e) => {
+ e.preventDefault();
+ });
+ section.addEventListener("drop", (e) => {
+ e.preventDefault();
+ const data = e.dataTransfer?.getData("text/plain") || "";
+ if (data.startsWith("msg:")) {
+ const messageId = data.replace("msg:", "");
+ const folderId = (e.target as HTMLElement).closest("[data-folder-id]")?.getAttribute("data-folder-id");
+ if (messageId && folderId) {
+ assignMessageToFolder(messageId, folderId, true);
+ applyBadges();
+ applyFolderFilter();
+ renderSidebarFolders();
+ }
+ }
+ });
+
+ attachDragListeners();
+ };
+
+ const attachDragListeners = () => {
+ document.querySelectorAll("[class*='MessageList__MessageList___'] ol > li[data-message]").forEach((li) => {
+ if (li.getAttribute("data-bsplus-drag") === "true") return;
+ li.setAttribute("data-bsplus-drag", "true");
+ li.draggable = true;
+ li.addEventListener("dragstart", (e) => {
+ const id = li.getAttribute("data-message");
+ if (id) {
+ e.dataTransfer?.setData("text/plain", `msg:${id}`);
+ li.classList.add("bsplus-msg-dragging");
+ }
+ });
+ li.addEventListener("dragend", () => {
+ li.classList.remove("bsplus-msg-dragging");
+ document.querySelectorAll(".bsplus-folder-item").forEach((el) => el.classList.remove("bsplus-drag-over"));
+ });
+ });
};
const showNewFolderInput = (container: Element, editFolder?: Folder) => {
@@ -312,16 +443,34 @@ const messageFoldersPlugin: Plugin {
+ e.stopPropagation();
+ const picker = container.querySelector(".bsplus-icon-picker") as HTMLElement | null;
+ if (picker) {
+ picker.remove();
+ return;
+ }
+ showIconPicker(container, (iconSvg) => {
+ selectedIcon = iconSvg;
+ iconBtn.innerHTML = iconSvg;
+ });
+ });
+
const confirmBtn = document.createElement("button");
confirmBtn.className = "bsplus-folder-input-confirm";
confirmBtn.innerHTML = CHECK_SVG_WHITE;
@@ -330,11 +479,11 @@ const messageFoldersPlugin: Plugin {
const name = input.value.trim();
if (!name) return;
-
if (editFolder) {
const folders = getFolders().map((f) =>
- f.id === editFolder.id ? { ...f, name, color: selectedColor } : f,
+ f.id === editFolder.id ? { ...f, name, color: selectedColor, emoji: selectedIcon } : f,
);
saveFolders(folders);
} else {
- const folder: Folder = { id: generateId(), name, color: selectedColor };
+ const folder: Folder = { id: generateId(), name, color: selectedColor, emoji: selectedIcon };
saveFolders([...getFolders(), folder]);
}
applyBadges();
@@ -386,23 +534,38 @@ const messageFoldersPlugin: Plugin input.focus());
};
+ const showIconPicker = (container: Element, onSelect: (iconSvg: string) => void) => {
+ const existing = container.querySelector(".bsplus-icon-picker");
+ if (existing) existing.remove();
+
+ const picker = document.createElement("div");
+ picker.className = "bsplus-icon-picker";
+ for (const icon of FOLDER_HEROICONS) {
+ const btn = document.createElement("button");
+ btn.className = "bsplus-icon-opt";
+ btn.innerHTML = icon;
+ btn.addEventListener("click", (e) => {
+ e.stopPropagation();
+ onSelect(icon);
+ picker.remove();
+ });
+ picker.appendChild(btn);
+ }
+ container.appendChild(picker);
+ };
+
const showEditFolderInput = (container: Element, folder: Folder) => {
showNewFolderInput(container, folder);
};
- // ── Intercept native sidebar clicks to clear folder filter ──
-
const attachNativeSidebarListeners = () => {
const sidebar = document.querySelector("[class*='Viewer__sidebar___']");
if (!sidebar) return;
-
const ol = sidebar.querySelector("ol");
if (!ol) return;
-
ol.addEventListener("click", (e) => {
const target = e.target as HTMLElement;
if (target.closest(".bsplus-folders-section")) return;
-
const li = target.closest("li");
if (li && ol.contains(li)) {
if (activeFolderId !== null) {
@@ -415,47 +578,22 @@ const messageFoldersPlugin: Plugin {
- if (actionsBar.querySelector(".bsplus-folder-btn")) return;
-
- const wrapper = document.createElement("div");
- wrapper.className = "bsplus-folder-btn";
- wrapper.style.position = "relative";
- wrapper.style.display = "inline-block";
-
- const btn = document.createElement("button");
- const btnClasses = actionsBar.querySelector("button")?.className ?? "";
- btn.className = btnClasses;
- btn.title = "Add to folder";
- btn.innerHTML = FOLDER_ICON_SVG;
-
- btn.addEventListener("click", (e) => {
- e.preventDefault();
- e.stopPropagation();
- closeDropdown();
-
- const selectedMsg = document.querySelector("[class*='MessageList__selected___']");
- const messageId = selectedMsg?.getAttribute("data-message");
- if (!messageId) return;
-
- showFolderDropdown(wrapper, messageId);
- });
-
- wrapper.appendChild(btn);
-
- const moreMenu = actionsBar.querySelector("[class*='MenuButton__Menu___']");
- if (moreMenu) {
- actionsBar.insertBefore(wrapper, moreMenu);
- } else {
- actionsBar.appendChild(wrapper);
+ const closeDropdown = () => {
+ if (openDropdown) {
+ openDropdown.remove();
+ openDropdown = null;
+ }
+ if (dropdownCloseHandler) {
+ document.removeEventListener("click", dropdownCloseHandler, true);
+ dropdownCloseHandler = null;
}
};
const showFolderDropdown = (anchor: HTMLElement, messageId: string) => {
+ closeDropdown();
const dropdown = document.createElement("div");
dropdown.className = "bsplus-folder-dropdown";
+ dropdown.dataset.msgId = messageId;
const folders = getFolders();
const currentFolderIds = getMessageFolderIds(messageId);
@@ -470,6 +608,7 @@ const messageFoldersPlugin: Plugin {
e.stopPropagation();
+ e.preventDefault();
toggleMessageInFolder(messageId, folder.id);
-
const nowChecked = getMessageFolderIds(messageId).includes(folder.id);
item.classList.toggle("bsplus-checked", nowChecked);
check.style.borderColor = nowChecked ? folder.color : "";
check.style.background = nowChecked ? folder.color : "";
-
applyBadges();
applyFolderFilter();
renderSidebarFolders();
@@ -519,22 +662,105 @@ const messageFoldersPlugin: Plugin {
- if (openDropdown) {
- openDropdown.remove();
- openDropdown = null;
- }
- if (dropdownCloseHandler) {
- document.removeEventListener("click", dropdownCloseHandler, true);
- dropdownCloseHandler = null;
+ const injectFolderButton = (actionsBar: Element) => {
+ if (actionsBar.querySelector(".bsplus-folder-btn")) return;
+ const wrapper = document.createElement("div");
+ wrapper.className = "bsplus-folder-btn";
+ wrapper.style.position = "relative";
+ wrapper.style.display = "inline-block";
+ const btn = document.createElement("button");
+ const btnClasses = actionsBar.querySelector("button")?.className ?? "";
+ btn.className = btnClasses;
+ btn.title = "Add to folder";
+ btn.innerHTML = FOLDER_ICON_SVG;
+ btn.addEventListener("click", (e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ const selectedMsg = document.querySelector("[class*='MessageList__selected___']");
+ const messageId = selectedMsg?.getAttribute("data-message");
+ if (!messageId) return;
+ showFolderDropdown(wrapper, messageId);
+ });
+ wrapper.appendChild(btn);
+ const moreMenu = actionsBar.querySelector("[class*='MenuButton__Menu___']");
+ if (moreMenu) {
+ actionsBar.insertBefore(wrapper, moreMenu);
+ } else {
+ actionsBar.appendChild(wrapper);
}
};
- // ── Message badges ──
+ const showContextMenu = (e: MouseEvent, messageId: string) => {
+ e.preventDefault();
+ e.stopPropagation();
+ closeDropdown();
+ const existing = document.querySelector(".bsplus-context-menu");
+ if (existing) existing.remove();
+
+ const menu = document.createElement("div");
+ menu.className = "bsplus-context-menu";
+ menu.style.left = `${e.clientX}px`;
+ menu.style.top = `${e.clientY}px`;
+
+ const title = document.createElement("div");
+ title.className = "bsplus-context-title";
+ title.textContent = "Add to folder";
+ menu.appendChild(title);
+
+ const folders = getFolders();
+ const currentFolderIds = getMessageFolderIds(messageId);
+
+ if (folders.length === 0) {
+ const empty = document.createElement("div");
+ empty.className = "bsplus-context-empty";
+ empty.textContent = "No folders";
+ menu.appendChild(empty);
+ } else {
+ for (const folder of folders) {
+ const isChecked = currentFolderIds.includes(folder.id);
+ const item = document.createElement("button");
+ item.className = `bsplus-context-item${isChecked ? " bsplus-context-checked" : ""}`;
+ const dot = document.createElement("div");
+ dot.className = "bsplus-folder-dot";
+ dot.style.background = folder.color;
+ const iconSpan = document.createElement("span");
+ iconSpan.className = "bsplus-folder-icon";
+ iconSpan.innerHTML = folder.emoji || FOLDER_HEROICONS[0];
+ const name = document.createElement("span");
+ name.textContent = folder.name;
+ item.appendChild(dot);
+ item.appendChild(iconSpan);
+ item.appendChild(name);
+ if (isChecked) {
+ const check = document.createElement("span");
+ check.className = "bsplus-context-checkmark";
+ check.textContent = "\u2713";
+ item.appendChild(check);
+ }
+ item.addEventListener("click", (e) => {
+ e.stopPropagation();
+ toggleMessageInFolder(messageId, folder.id);
+ applyBadges();
+ applyFolderFilter();
+ renderSidebarFolders();
+ menu.remove();
+ });
+ menu.appendChild(item);
+ }
+ }
+
+ document.body.appendChild(menu);
+ const closeMenu = (ev: MouseEvent) => {
+ if (!menu.contains(ev.target as Node)) {
+ menu.remove();
+ document.removeEventListener("click", closeMenu);
+ }
+ };
+ setTimeout(() => document.addEventListener("click", closeMenu), 0);
+ };
const applyBadges = () => {
const messageItems = document.querySelectorAll("[class*='MessageList__MessageList___'] ol > li[data-message]");
-
if (!shouldShowBadgesInList()) {
for (const li of messageItems) {
const subject = li.querySelector("[class*='MessageList__subject___']");
@@ -546,26 +772,20 @@ const messageFoldersPlugin: Plugin f.id === fId);
@@ -591,7 +810,7 @@ const messageFoldersPlugin: Plugin${folder.emoji}` : ""}${folder.name}`;
badge.title = `Filter by "${folder.name}"`;
badge.addEventListener("click", (e) => {
e.stopPropagation();
@@ -605,12 +824,9 @@ const messageFoldersPlugin: Plugin {
const messageItems = document.querySelectorAll("[class*='MessageList__MessageList___'] ol > li[data-message]");
const moreBtn = document.querySelector("[class*='MessageList__MessageList___'] ol > button");
-
if (activeFolderId === null) {
if (api.settings.hideFolderedMessagesInAll) {
for (const li of messageItems) {
@@ -629,9 +845,7 @@ const messageFoldersPlugin: Plugin {
const messageList = document.querySelector("[class*='MessageList__MessageList___'] ol");
if (!messageList || messageListObserver) return;
-
messageListObserver = new MutationObserver(() => {
applyBadges();
applyFolderFilter();
+ attachDragListeners();
+ attachContextMenuListeners();
});
messageListObserver.observe(messageList, { childList: true, subtree: false });
};
+ const attachContextMenuListeners = () => {
+ document.querySelectorAll("[class*='MessageList__MessageList___'] ol > li[data-message]").forEach((li) => {
+ if (li.getAttribute("data-bsplus-ctx") === "true") return;
+ li.setAttribute("data-bsplus-ctx", "true");
+ li.addEventListener("contextmenu", (e) => {
+ const msgId = li.getAttribute("data-message");
+ if (msgId) {
+ showContextMenu(e, msgId);
+ }
+ });
+ });
+ };
+
const setupActionsObserver = () => {
if (actionsObserver) return;
-
const target = document.querySelector("[class*='Viewer__Viewer___']") ?? document.querySelector("div.messages");
if (!target) return;
-
actionsObserver = new MutationObserver(() => {
const actionsBar = document.querySelector("[class*='Message__actions___']");
if (actionsBar && !actionsBar.querySelector(".bsplus-folder-btn")) {
@@ -671,28 +895,19 @@ const messageFoldersPlugin: Plugin {
await waitForElm("[class*='Viewer__sidebar___'] ol", true, 50, 100);
-
renderSidebarFolders();
attachNativeSidebarListeners();
-
await waitForElm("[class*='MessageList__MessageList___'] ol", true, 50, 100);
applyBadges();
applyFolderFilter();
setupMessageListObserver();
-
- // The actions bar only exists when a message is selected/open,
- // so we observe the whole viewer for it to appear dynamically
setupActionsObserver();
-
- // If a message is already selected, inject immediately
+ attachDragListeners();
+ attachContextMenuListeners();
const actionsBar = document.querySelector("[class*='Message__actions___']");
if (actionsBar) injectFolderButton(actionsBar);
-
- // Re-observe the sidebar for SEQTA re-renders
const sidebar = document.querySelector("[class*='Viewer__sidebar___']");
if (sidebar && !sidebarObserver) {
sidebarObserver = new MutationObserver(() => {
@@ -706,11 +921,8 @@ const messageFoldersPlugin: Plugin {
applyBadges();
@@ -732,6 +944,7 @@ const messageFoldersPlugin: Plugin el.remove());
document.querySelectorAll(".bsplus-folder-btn").forEach((el) => el.remove());
document.querySelectorAll(".bsplus-msg-badges").forEach((el) => el.remove());
+ document.querySelectorAll(".bsplus-context-menu").forEach((el) => el.remove());
document.querySelectorAll("[class*='MessageList__subject___']").forEach((subject) => {
if (subject.querySelector(".bsplus-subject-text")) {
restoreSubjectPlain(subject);
@@ -741,6 +954,7 @@ const messageFoldersPlugin: Plugin el.remove());
+
};
},
};
diff --git a/src/plugins/built-in/messageFolders/styles.css b/src/plugins/built-in/messageFolders/styles.css
index e239a883..6b99b126 100644
--- a/src/plugins/built-in/messageFolders/styles.css
+++ b/src/plugins/built-in/messageFolders/styles.css
@@ -3,12 +3,21 @@
border-top: 1px solid var(--background-secondary, rgba(128, 128, 128, 0.2));
margin-top: 4px;
padding-top: 4px;
+ transition: opacity .2s;
+}
+
+.bsplus-folders-section.bsplus-section-folded .bsplus-folder-item,
+.bsplus-folders-section.bsplus-section-folded .bsplus-folder-input,
+.bsplus-folders-section.bsplus-section-folded .bsplus-folder-colors,
+.bsplus-folders-section.bsplus-section-folded .bsplus-emoji-picker,
+.bsplus-folders-section.bsplus-section-folded .bsplus-all-msgs {
+ display: none !important;
}
.bsplus-folders-header {
display: flex;
align-items: center;
- justify-content: space-between;
+ gap: 4px;
padding: 6px 12px 2px;
user-select: none;
}
@@ -20,6 +29,33 @@
letter-spacing: 0.5px;
color: var(--text-primary, #666);
opacity: 0.5;
+ flex: 1;
+}
+
+.bsplus-folders-collapse {
+ display: flex !important;
+ align-items: center !important;
+ justify-content: center !important;
+ width: 18px !important;
+ height: 18px !important;
+ min-width: 0 !important;
+ border: none !important;
+ background: transparent !important;
+ opacity: 0.4;
+ cursor: pointer;
+ border-radius: 4px !important;
+ padding: 0 !important;
+ margin: 0 !important;
+ transition: all .2s;
+}
+
+.bsplus-folders-collapse:hover {
+ opacity: 0.8;
+ background: var(--background-secondary, rgba(128, 128, 128, 0.1)) !important;
+}
+
+.bsplus-folders-collapse.bsplus-folded svg {
+ transform: rotate(-90deg);
}
.bsplus-folders-add-btn {
@@ -51,12 +87,21 @@
align-items: center;
padding: 6px 12px;
cursor: pointer;
- transition: background 0.15s ease;
+ transition: background 0.15s ease, opacity 0.2s;
position: relative;
- gap: 8px;
+ gap: 6px;
user-select: none;
}
+.bsplus-folder-item.bsplus-dragging {
+ opacity: 0.4;
+}
+
+.bsplus-folder-item.bsplus-drag-over {
+ background: var(--better-main, #007bff22) !important;
+ border-radius: 4px;
+}
+
.bsplus-folder-item:hover {
background: var(--theme-offset-bg-more, rgba(128, 128, 128, 0.08));
}
@@ -76,6 +121,18 @@
border-radius: 0 2px 2px 0;
}
+.bsplus-folder-drag {
+ display: flex;
+ align-items: center;
+ opacity: 0;
+ transition: opacity .15s;
+ margin-right: -4px;
+}
+
+.bsplus-folder-item:hover .bsplus-folder-drag {
+ opacity: 0.5;
+}
+
.bsplus-folder-dot {
width: 8px;
height: 8px;
@@ -83,6 +140,23 @@
flex-shrink: 0;
}
+.bsplus-folder-icon {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: 16px;
+ height: 16px;
+ flex-shrink: 0;
+ color: var(--text-primary, #333);
+}
+
+.bsplus-folder-icon svg {
+ width: 16px;
+ height: 16px;
+ stroke: currentColor;
+ fill: none;
+}
+
.bsplus-folder-name {
font-size: 13px;
color: var(--text-primary, #333);
@@ -97,6 +171,8 @@
color: var(--text-primary, #999);
opacity: 0.5;
flex-shrink: 0;
+ min-width: 16px;
+ text-align: right;
}
.bsplus-folder-actions {
@@ -158,6 +234,35 @@
box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.2);
}
+.bsplus-folder-icon-btn {
+ display: flex !important;
+ align-items: center !important;
+ justify-content: center !important;
+ width: 28px !important;
+ height: 28px !important;
+ min-width: 0 !important;
+ border: 1px solid var(--background-secondary, #ccc) !important;
+ border-radius: 6px !important;
+ background: var(--background-secondary, #f5f5f5) !important;
+ cursor: pointer;
+ padding: 0 !important;
+ margin: 0 !important;
+ transition: all .15s;
+ color: var(--text-primary, #333);
+}
+
+.bsplus-folder-icon-btn:hover {
+ transform: scale(1.1);
+ background: var(--theme-offset-bg-more, rgba(128, 128, 128, 0.1)) !important;
+}
+
+.bsplus-folder-icon-btn svg {
+ width: 18px;
+ height: 18px;
+ stroke: currentColor;
+ fill: none;
+}
+
.bsplus-folder-input-confirm,
.bsplus-folder-input-cancel {
display: flex !important;
@@ -192,6 +297,43 @@
background: var(--background-secondary, rgba(128, 128, 128, 0.1)) !important;
}
+/* ── Icon picker ── */
+.bsplus-icon-picker {
+ display: grid;
+ grid-template-columns: repeat(4, 1fr);
+ gap: 4px;
+ padding: 4px 12px 6px;
+ max-width: 140px;
+}
+
+.bsplus-icon-opt {
+ display: flex !important;
+ align-items: center !important;
+ justify-content: center !important;
+ width: 28px !important;
+ height: 28px !important;
+ min-width: 0 !important;
+ border: none !important;
+ border-radius: 6px !important;
+ background: transparent !important;
+ cursor: pointer;
+ padding: 0 !important;
+ transition: all .15s;
+ color: var(--text-primary, #333);
+}
+
+.bsplus-icon-opt svg {
+ width: 18px;
+ height: 18px;
+ stroke: currentColor;
+ fill: none;
+}
+
+.bsplus-icon-opt:hover {
+ transform: scale(1.3);
+ background: var(--theme-offset-bg-more, rgba(128, 128, 128, 0.1)) !important;
+}
+
/* ── Color picker row ── */
.bsplus-folder-colors {
display: grid;
@@ -322,14 +464,113 @@
opacity: 0.5;
}
-/* ── Let primary column use available space instead of being clipped ── */
+/* ── Context menu ── */
+.bsplus-context-menu {
+ position: fixed;
+ min-width: 160px;
+ background: var(--background-primary, #fff) !important;
+ border: 1px solid var(--background-secondary, #e0e0e0) !important;
+ border-radius: 8px;
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
+ z-index: 2147483646;
+ overflow: hidden;
+ animation: bsplus-dropdown-in 0.12s ease-out;
+ padding: 4px 0;
+}
+
+.bsplus-context-title {
+ padding: 6px 12px 4px;
+ font-size: 11px;
+ font-weight: 600;
+ text-transform: uppercase;
+ letter-spacing: 0.5px;
+ color: var(--text-primary, #999) !important;
+ opacity: 0.5;
+ user-select: none;
+}
+
+.bsplus-context-item:hover {
+ background: var(--theme-offset-bg-more, rgba(128, 128, 128, 0.08)) !important;
+}
+
+.bsplus-context-item span {
+ flex: 1;
+}
+
+.bsplus-context-checkmark {
+ color: var(--better-main, #007bff) !important;
+ font-weight: bold;
+ flex: 0 !important;
+}
+
+.bsplus-context-item {
+ display: flex !important;
+ align-items: center !important;
+ justify-content: flex-start !important;
+ gap: 8px;
+ padding: 7px 12px !important;
+ font-size: 13px;
+ cursor: pointer;
+ border: none !important;
+ background: transparent !important;
+ width: 100%;
+ text-align: left !important;
+ color: var(--text-primary, #333) !important;
+ transition: background .1s;
+ font-family: inherit;
+}
+
+.bsplus-context-item .bsplus-folder-icon {
+ color: var(--text-primary, #333) !important;
+ width: 16px;
+ height: 16px;
+}
+
+.bsplus-context-item .bsplus-folder-icon svg {
+ width: 16px;
+ height: 16px;
+ stroke: currentColor;
+ fill: none;
+}
+
+.bsplus-context-item:hover {
+ background: var(--theme-offset-bg-more, rgba(128, 128, 128, 0.08));
+}
+
+.bsplus-context-item span {
+ flex: 1;
+}
+
+.bsplus-context-checkmark {
+ color: var(--better-main, #007bff) !important;
+ font-weight: bold;
+ flex: 0 !important;
+}
+
+.bsplus-context-empty {
+ padding: 12px;
+ text-align: center;
+ font-size: 12px;
+ color: var(--text-primary, #999);
+ opacity: 0.5;
+}
+
+/* ── Drag feedback ── */
+.bsplus-msg-dragging {
+ opacity: 0.4;
+}
+
+[class*='MessageList__MessageList___'] ol > li[data-message] {
+ transition: opacity .15s;
+}
+
+/* ── Layout fixes ── */
[class*='MessageList__primary___'] {
flex: 1 1 0% !important;
min-width: 0 !important;
overflow: hidden !important;
}
-/* ── Make subject line a flex row so badges sit inline ── */
[class*='MessageList__subject___'] {
display: flex !important;
align-items: center;
@@ -338,7 +579,6 @@
overflow: hidden !important;
}
-/* ── Subject text truncates to make room for badges ── */
.bsplus-subject-text {
overflow: hidden;
text-overflow: ellipsis;
@@ -347,7 +587,6 @@
flex: 1 1 auto;
}
-/* ── Shrink the secondary column to its content ── */
[class*='MessageList__secondary___'] {
flex: 0 0 auto !important;
width: auto !important;
@@ -355,7 +594,6 @@
max-width: 200px !important;
}
-/* ── Constrain the flags/attachment icon column ── */
[class*='MessageList__flags___'] {
width: 24px !important;
min-width: 0 !important;
@@ -391,7 +629,7 @@
transform: scale(1.05);
}
-/* ── Folder filtering (hide messages not in active folder) ── */
+/* ── Folder filtering ── */
.bsplus-folder-hidden {
display: none !important;
}
@@ -489,3 +727,5 @@
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(229, 62, 62, 0.35);
}
+
+
diff --git a/src/plugins/index.ts b/src/plugins/index.ts
index aa8779ad..009e9e43 100644
--- a/src/plugins/index.ts
+++ b/src/plugins/index.ts
@@ -10,7 +10,7 @@ import assessmentsAveragePlugin from "./built-in/assessmentsAverage";
import profilePicturePlugin from "./built-in/profilePicture";
import assessmentsOverviewPlugin from "./built-in/assessmentsOverview";
import backgroundMusicPlugin from "./built-in/backgroundMusic";
-//import messageFoldersPlugin from "./built-in/messageFolders";
+import messageFoldersPlugin from "./built-in/messageFolders";
//import testPlugin from './built-in/test';
// Heavy plugins (lazy-loaded only when enabled)
@@ -29,7 +29,7 @@ pluginManager.registerPlugin(timetableEditPlugin);
pluginManager.registerPlugin(profilePicturePlugin);
pluginManager.registerPlugin(assessmentsOverviewPlugin);
pluginManager.registerPlugin(backgroundMusicPlugin);
-//pluginManager.registerPlugin(messageFoldersPlugin);
+pluginManager.registerPlugin(messageFoldersPlugin);
//pluginManager.registerPlugin(testPlugin);
// Register heavy plugins with lazy loading