From b4598668d43f46289230c9201bb97be9c1253c9a Mon Sep 17 00:00:00 2001 From: Aden Lindsay Date: Wed, 13 May 2026 13:30:27 +0930 Subject: [PATCH] feat: re enable message folders with improvments --- src/plugins/built-in/messageFolders/index.ts | 482 +++++++++++++----- .../built-in/messageFolders/styles.css | 258 +++++++++- src/plugins/index.ts | 4 +- 3 files changed, 599 insertions(+), 145 deletions(-) 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