Add organize folders, details sheet, and richer previews.
CI / Gitleaks (pull_request) Successful in 17s
CI / Unit tests (pull_request) Successful in 21s

Map keys 0–9 to destinations, inspect files before deciding, and preview
PDF/RAW/ZIP/video with an OLED dark UI polish plus demo seed and tests.
This commit is contained in:
2026-07-26 21:38:36 -04:00
parent 5552f39707
commit ce43c5de6d
21 changed files with 1905 additions and 121 deletions
+37 -6
View File
@@ -29,7 +29,7 @@ class Adapter {
* {
* key: string,
* label: string,
* type: 'text' | 'password' | 'checkbox' | 'number' | 'select' | 'folder',
* type: 'text' | 'password' | 'checkbox' | 'number' | 'select' | 'folder' | 'folderMap',
* default?: any,
* options?: Array<{ value: string, label: string }>, // for type 'select'
* placeholder?: string,
@@ -37,19 +37,21 @@ class Adapter {
* }
* `type: 'folder'` renders a text field plus a "Browse..." button backed
* by a native folder picker where available (see server.js /api/browse-folder).
* `type: 'folderMap'` renders keys 09, each with an optional label and a
* folder path (for organize-into-buckets flows).
*/
static configSchema = [];
/**
* Actions available on every card. The first two are the swipe defaults
* (right = keep, left = reject); adapters may add more, e.g. a third
* "later" bucket, as long as each has a distinct `key` and `direction`.
* Default actions available on every card. Prefer overriding getActions()
* when the set depends on settings (e.g. numbered destination folders).
* {
* id: string,
* label: string,
* key: string, // KeyboardEvent.key that triggers it
* direction: 'left' | 'right' | 'up' | 'down',
* direction?: 'left' | 'right' | 'up' | 'down',
* isDestructive?: boolean,
* group?: 'primary' | 'organize', // UI layout hint
* }
*/
static actions = [
@@ -61,6 +63,14 @@ class Adapter {
this.settings = settings;
}
/**
* Actions for the current session. Defaults to the static `actions` list;
* override when actions depend on settings (folder destinations, etc.).
*/
getActions() {
return this.constructor.actions;
}
/**
* Optional async setup: validate settings, open a mailbox, connect to a
* database, create a trash directory, etc. Throw a descriptive Error to
@@ -75,7 +85,7 @@ class Adapter {
* id: string,
* title: string,
* subtitle?: string,
* previewType?: 'image' | 'audio' | 'video' | 'text' | 'none',
* previewType?: 'image' | 'audio' | 'video' | 'pdf' | 'archive' | 'text' | 'none',
* meta?: Record<string, string | number>,
* }>>}
*/
@@ -116,6 +126,22 @@ class Adapter {
return false;
}
/**
* Optional: richer details for the inspect-before-deciding panel.
* @returns {Promise<{ fields: Array<{ label: string, value: string }>, path?: string, actions?: Array<{ id: string, label: string }> } | null>}
*/
async getDetails(itemId) {
return null;
}
/**
* Optional: reveal / open the item in the native file manager.
* Return true if handled.
*/
async reveal(itemId) {
return false;
}
/**
* Optional: describes a reversible "trash" this adapter maintains, so
* the UI can offer an explicit, confirm-guarded "Empty trash" action.
@@ -135,6 +161,11 @@ class Adapter {
describeSource() {
return '';
}
/** Optional UI hints (e.g. showDetailsByDefault) merged into /api/queue. */
uiHints() {
return {};
}
}
module.exports = { Adapter };