Plugin architecture for adding custom actions to the file browser.
Extensions are placed in src/app/extensions/ and managed via extensions.config.ts.
interface FileActionExtension {
id: string;
label: string;
icon: string;
command: (ctx: FileActionContext) => void;
parentId?: string;
position?: 'start' | 'end' | number;
visible?: (ctx: FileActionContext) => boolean;
disabled?: (ctx: FileActionContext) => boolean;
}If parentId is set, the extension appears only in that submenu (e.g., 'share').
If not set, the extension appears in both menu and toolbar.
interface FileActionContext {
target: FileModel | FileDetailsModel | FileFolderModel;
location: 'file-list' | 'file-detail';
isViewOnly: boolean;
canWrite: boolean;
}export function copyLinksExtensionFactory(): FileActionExtension[] {
return [
{
id: 'copy-link',
label: 'Copy Link',
icon: 'fas fa-link',
command: (ctx) => {
navigator.clipboard.writeText(ctx.target.links.html);
},
parentId: 'share', // appears in Share submenu only
position: 'end',
visible: (ctx) => ctx.target.kind === 'file',
disabled: (ctx) => ctx.isViewOnly,
},
];
}Note: Context menus honor
positionexactly (for example,'start'inserts before built-in items). Top-level toolbars append extensions after the core OSF buttons but still keep extension-to-extension ordering based onposition. Design UX with that constraint in mind.
export function createFileExtensionFactory(config: Config): FileActionExtension[] {
return [
{
id: 'create-file',
label: 'Create File',
icon: 'fas fa-file-plus',
command: (ctx) => {
window.open(`${config.editorUrl}?path=${ctx.target.path}`, '_blank');
},
// no parentId: appears in both menu and toolbar
position: 'end',
visible: (ctx) =>
ctx.location === 'file-list' &&
ctx.target.kind === 'folder' &&
!ctx.isViewOnly &&
ctx.canWrite,
},
];
}The extension configuration is managed via extensions.config.ts, which is generated at build time.
src/app/
extensions.config.ts # Generated (not in git)
extensions.config.default.ts # Default config (in git)
By default, extensions.config.default.ts is used. To use a custom configuration:
EXTENSIONS_CONFIG=/path/to/my-extensions.config.ts npm run buildThe scripts/setup-extensions.js helper runs before npm run build, npm start, and npm test (see the prebuild / prestart / pretest npm scripts) and handles this:
- If
EXTENSIONS_CONFIGis set, copies that file - Otherwise, copies
extensions.config.default.ts
export const extensionConfig: ExtensionConfig[] = [
{
load: () => import('./extensions/copy-links'),
factory: 'copyLinksExtensionFactory',
enabled: true,
},
{
load: () => import('./extensions/onlyoffice'),
factory: 'editByOnlyOfficeExtensionFactory',
enabled: true,
config: { editorUrl: 'https://...' },
},
{
load: () => import('./extensions/onlyoffice'),
factory: 'createFileExtensionFactory',
enabled: true,
config: { editorUrl: 'https://...' },
},
];