Extending Exocortex with custom functionality.
cd /your/vault/.obsidian/plugins/exocortex
npm install
npm run dev # Watch modepackages/
├── core/ # Business logic (storage-agnostic)
├── obsidian-plugin/ # Obsidian UI integration
└── cli/ # Command-line tools
src/application/commands/MyCustomCommand.ts:
import { ICommand } from '../interfaces/ICommand';
import type ExocortexPlugin from '../../main';
export class MyCustomCommand implements ICommand {
id = 'my-custom-command';
name = 'My Custom Command';
constructor(private plugin: ExocortexPlugin) {}
callback = async (): Promise<void> => {
const { workspace } = this.plugin.app;
const activeFile = workspace.getActiveFile();
if (!activeFile) {
return;
}
// Your logic here
};
}src/application/commands/CommandRegistry.ts:
import { MyCustomCommand } from './MyCustomCommand';
export class CommandRegistry {
constructor(private plugin: ExocortexPlugin) {
// Add to commands array
this.commands = [
// ... existing commands
new MyCustomCommand(plugin),
];
}
}packages/exocortex/src/domain/commands/CommandVisibility.ts:
export function canExecuteMyCustomCommand(metadata: Record<string, any>): boolean {
return metadata.exo__Instance_class === 'ems__Task';
}src/presentation/renderers/layout/MyCustomRenderer.ts:
import { BaseRenderer } from './BaseRenderer';
export class MyCustomRenderer extends BaseRenderer {
async render(
container: HTMLElement,
metadata: Record<string, any>
): Promise<void> {
const section = container.createDiv({ cls: 'my-custom-section' });
section.createEl('h3', { text: 'My Custom Section' });
// Render logic
}
}src/presentation/renderers/layout/UniversalLayoutRenderer.ts:
private renderCustomSection(container: HTMLElement): void {
const renderer = new MyCustomRenderer(this.app, this.plugin);
await renderer.render(container, this.metadata);
}import { App, Modal } from 'obsidian';
export class MyCustomModal extends Modal {
private onSubmit: (result: MyResult) => void;
constructor(app: App, onSubmit: (result: MyResult) => void) {
super(app);
this.onSubmit = onSubmit;
}
onOpen(): void {
const { contentEl } = this;
contentEl.createEl('h2', { text: 'My custom modal' });
// Form elements
const input = contentEl.createEl('input', { type: 'text' });
// Buttons
const buttonContainer = contentEl.createDiv({ cls: 'modal-button-container' });
const okButton = buttonContainer.createEl('button', { text: 'OK', cls: 'mod-cta' });
okButton.addEventListener('click', () => this.submit());
}
private submit(): void {
this.onSubmit({ /* result */ });
this.close();
}
}callback = async (): Promise<void> => {
const modal = new MyCustomModal(
this.plugin.app,
async (result) => {
// Handle result
}
);
modal.open();
};tests/unit/MyCustomCommand.test.ts:
import { MyCustomCommand } from '../../src/application/commands/MyCustomCommand';
describe('MyCustomCommand', () => {
let command: MyCustomCommand;
let mockPlugin: any;
beforeEach(() => {
mockPlugin = {
app: { workspace: { getActiveFile: jest.fn() } }
};
command = new MyCustomCommand(mockPlugin);
});
it('should execute successfully', async () => {
await command.callback();
// Assertions
});
});npm run test:all # All tests
npm run test:unit # Unit tests onlynpm run build # Production build
npm run dev # Development watch modeSee also: