mirror of
https://github.com/karatektus/pl_mail.git
synced 2026-10-25 01:54:10 +00:00
Eleven of the twelve items. The twelfth, a deleted label coming back after the next sync on IMAP, is left for an issue of its own: there a label is a folder, and what deleting one should do to the mail in it needs deciding first. A selection survives the pager. It was nothing but the ticked checkboxes, so "next page" replaced the rows and the selection with them. The rows that are not on screen are now remembered in assets/mail_selection.js, per list, and the toolbar, the label menu, the move menu and a drag all read the selection from there. The checkbox stays the truth for anything visible. The whole view can be selected from the select menu. It was only offered by a banner that appears once a page is fully ticked. And a whole-view selection no longer survives a row being unticked: the action still ran on everything. A sync no longer logs a stored message as one it could not build. MessageSyncer reads the UIDs a folder holds once, before it fetches, and a move made meanwhile files rows under UIDs that list does not have. The INSERT was refused on (mailbox, uid), rightly, and each message was then held back as a failure. The list is read again when a batch is refused. A pairing code and the JMAP address under App passwords carry the configured public address, and the browser's only when none is set. This reverses the earlier choice in DevicePairingController, which handed a phone paired at home a LAN address. Hiding quoted text gives the height back. scrollHeight cannot report less than the frame it is measured in, so the collapse reports the root element's box instead and holds other reports until the parent has resized. Mail in Sent shows its images without asking. Read off the Sent label and not the From line, which a forged mail fills in as it likes. Spam still wins. Smaller: placeholders are lighter than typed text, the actions on a connected integration are buttons on one baseline, Save signature and four composer buttons no longer hover to white on white, the Spam badge loses the accent, and German says "vor 12s". Refs #37 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
116 lines
4.0 KiB
JavaScript
116 lines
4.0 KiB
JavaScript
/**
|
|
* Which conversations are selected — including the ones on another page.
|
|
*
|
|
* A selection used to be nothing but the ticked checkboxes, so it was exactly
|
|
* as long-lived as the rows carrying them: pressing "next page" replaced the
|
|
* rows and the selection with them (#37). Ticking three conversations on page
|
|
* one and two on page two is one selection to the person making it, and the
|
|
* toolbar then acted on two.
|
|
*
|
|
* So the rows that are NOT on screen are remembered here, and the rows that
|
|
* are on screen are never remembered at all — the checkbox is the truth for
|
|
* anything visible, which is what lets every existing writer of a checkbox
|
|
* (the master box, the select menu, a background refresh putting ticks back,
|
|
* a drop clearing them) go on doing only that. selection() reconciles the two
|
|
* every time it is asked.
|
|
*
|
|
* ## One list at a time
|
|
*
|
|
* A selection belongs to the list it was made in. enterView() is told which
|
|
* list is on screen by the toolbar, each time one connects, and anything
|
|
* carried from a different list is dropped there: "archive" pressed in Sent
|
|
* must not reach five conversations ticked in the Inbox a minute ago. The page
|
|
* number is not part of the key, which is the whole point; everything else
|
|
* about the view is.
|
|
*
|
|
* ## Why the labels and the account travel with the id
|
|
*
|
|
* The label menu ticks a label when every selected conversation has it, the
|
|
* move menu hides destinations they all share, and a drag refuses a folder of
|
|
* an account it does not belong to. All three read those facts off the row,
|
|
* and a row on another page is not there to be read.
|
|
*
|
|
* Module state rather than a Stimulus value: the toolbar lives inside the
|
|
* list frame and is replaced with it on every page change, which is the very
|
|
* moment this has to survive.
|
|
*/
|
|
|
|
const ROW_SELECT = "[data-thread-select]";
|
|
|
|
/** @type {string|null} */
|
|
let view = null;
|
|
|
|
/** @type {Map<string, {labelIds: string[], account: string|undefined}>} */
|
|
const carried = new Map();
|
|
|
|
function factsOf(checkbox) {
|
|
const labelled = checkbox.closest("[data-label-ids]");
|
|
const dragged = checkbox.closest("[data-dnd-thread]");
|
|
|
|
return {
|
|
labelIds: (labelled?.dataset.labelIds ?? "").split(",").filter((id) => "" !== id),
|
|
account: dragged?.dataset.dndAccount,
|
|
};
|
|
}
|
|
|
|
/** The list now on screen. Forgets a selection made in any other. */
|
|
export function enterView(key) {
|
|
if (key !== view) {
|
|
carried.clear();
|
|
view = key;
|
|
}
|
|
}
|
|
|
|
/** Tick the rows on this page that were selected before it was left. */
|
|
export function restoreSelection() {
|
|
for (const checkbox of document.querySelectorAll(ROW_SELECT)) {
|
|
if (true === carried.has(checkbox.value)) {
|
|
checkbox.checked = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Everything selected: the ticked rows on screen, then the ones on other pages.
|
|
*
|
|
* @returns {{id: number, labelIds: string[], account: string|undefined}[]}
|
|
*/
|
|
export function selection() {
|
|
for (const checkbox of document.querySelectorAll(ROW_SELECT)) {
|
|
if (true === checkbox.checked) {
|
|
carried.set(checkbox.value, factsOf(checkbox));
|
|
} else {
|
|
carried.delete(checkbox.value);
|
|
}
|
|
}
|
|
|
|
return [...carried].map(([id, facts]) => ({ id: Number(id), ...facts }));
|
|
}
|
|
|
|
/**
|
|
* Forget the rows that are not on screen. The caller unticks the ones that
|
|
* are — it is the one that knows which list it means.
|
|
*/
|
|
export function clearSelection() {
|
|
carried.clear();
|
|
}
|
|
|
|
/**
|
|
* A label went onto, or came off, the whole selection.
|
|
*
|
|
* The rows on screen are redrawn by the response and say so themselves; the
|
|
* ones on other pages have only what was written down when they were left,
|
|
* and the label menu decides attach-or-detach from it.
|
|
*/
|
|
export function relabelSelection(labelId, attached) {
|
|
const id = String(labelId);
|
|
|
|
for (const facts of carried.values()) {
|
|
facts.labelIds = facts.labelIds.filter((existing) => existing !== id);
|
|
|
|
if (true === attached) {
|
|
facts.labelIds.push(id);
|
|
}
|
|
}
|
|
}
|