a Media section, browsing the site's originals in place

a new section above Administration. it embeds THE PLATFORM'S OWN file browser rather
than drawing a file list — upload, delete, rename, move, thumbnails, the context
menu and every task hanging off it already exist and are maintained in one place. a
plugin's own would be a second to keep in step, arriving without any of that.

`basePath` IS A JAIL, not a starting folder: `useFileBrowserApp` resets currentPath
whenever it leaves, so pointing it at `<root>/media/originals` means the breadcrumb
stops there and `derived/` is not reachable by navigating. that is what was asked
for — people work in originals, derived is made for them.

IT IS A GUARDRAIL AND NOT A PERMISSION BOUNDARY, and the panel says so in its own
docstring because the difference will matter to whoever reads this next. the
file-browser API is unchanged and still addresses the whole home: anyone with file
access reaches derived/ through the Files screen. this prevents an accident, not a
determined person. confining it properly means scoping that API per user, which is a
platform change and a much larger one.

the panel also says, once, that derived/ exists and is made from these — otherwise
somebody who knows about it would think it had been lost.

`rootRelative` is new on the settings API because only the SERVER knows what home
is. the file browser addresses everything relative to a root, and a browser deriving
that prefix from the absolute path would be guessing. null when the root sits
outside home — a legitimate setting the browser cannot address, which the panel
explains rather than rendering an empty folder.

NOTHING HERE IS FOFOS-SPECIFIC, which matters now that this plugin is the template
for other site admins: the root is a setting, media/originals is the layout this
plugin creates, and both are read from the API. another site's plugin changes the
label.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-29 08:39:53 +01:00
co-authored by Claude Opus 5
parent 9da0c31a90
commit 944d3d9c5b
5 changed files with 155 additions and 3 deletions
+23 -1
View File
@@ -29,6 +29,18 @@ import { fofosAdminSettings } from '../db/schema';
export type SettingsView = {
/** Absolute. Never null — an unset root reports its default rather than nothing. */
rootPath: string;
/**
* The same place, relative to the owner's home, with a leading slash.
*
* The file browser addresses everything relative to a root — `home` — so a panel embedding it needs
* this form and cannot work it out: only the server knows what home is, and a browser computing it
* from the absolute path would be guessing at a prefix.
*
* Null when the root is outside home. That is a legitimate configuration — the root may be anywhere
* absolute — and it means the embedded browser cannot show it, which the panel says rather than
* rendering an empty folder.
*/
rootRelative: string | null;
/** False while the default is standing in, so a form can show it as a suggestion, not a choice. */
isSet: boolean;
};
@@ -44,7 +56,17 @@ export const defaultRootPath = (email: string): string => join(getOwnerHomeDir(e
export async function readSettings(email: string): Promise<SettingsView> {
const found = await row();
const stored = found?.rootPath ?? null;
return { rootPath: stored ?? defaultRootPath(email), isSet: stored !== null };
const rootPath = stored ?? defaultRootPath(email);
// `home` or a path beneath it — anything else is outside what the file browser can address.
const home = getOwnerHomeDir(email);
const inside = rootPath === home || rootPath.startsWith(`${home}/`);
return {
rootPath,
rootRelative: inside ? `/${rootPath.slice(home.length).replace(/^\/+/, '')}`.replace(/\/$/, '') || '/' : null,
isSet: stored !== null,
};
}
/**