Add Domain
This commit is contained in:
parent
5e01bbf24a
commit
ff4362fe14
|
@ -1,17 +0,0 @@
|
||||||
import ExoCommand from "./ExoCommand";
|
|
||||||
import {App, Notice} from "obsidian";
|
|
||||||
import NoteRepository from "../Domain/NoteRepository";
|
|
||||||
|
|
||||||
export default class CountNotesExoCommand implements ExoCommand {
|
|
||||||
name: string = "Количество заметок";
|
|
||||||
slug: string = "count-notes";
|
|
||||||
|
|
||||||
constructor(private app: App) {
|
|
||||||
}
|
|
||||||
|
|
||||||
async execute(): Promise<void> {
|
|
||||||
const noteRepository = new NoteRepository(this.app);
|
|
||||||
const count = noteRepository.all().length;
|
|
||||||
new Notice(`Vault has ${count} notes`);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,8 +0,0 @@
|
||||||
import {App} from "obsidian";
|
|
||||||
|
|
||||||
export default interface ExoCommand {
|
|
||||||
name: string;
|
|
||||||
slug: string;
|
|
||||||
|
|
||||||
execute(app: App): Promise<void>;
|
|
||||||
}
|
|
|
@ -1,17 +0,0 @@
|
||||||
import OpenRandomNoteExoCommand from "./OpenRandomNoteExoCommand";
|
|
||||||
import ExoCommand from "./ExoCommand";
|
|
||||||
import CountNotesExoCommand from "./CountNotesExoCommand";
|
|
||||||
import {App} from "obsidian";
|
|
||||||
|
|
||||||
export default class ExoCommands {
|
|
||||||
static all(app: App): ExoCommand[] {
|
|
||||||
return [
|
|
||||||
new OpenRandomNoteExoCommand(),
|
|
||||||
new CountNotesExoCommand(app)
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
static bySlug(app: App, slug: string): ExoCommand | undefined {
|
|
||||||
return ExoCommands.all(app).find(c => c.slug === slug);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,18 +0,0 @@
|
||||||
import {FuzzySuggestModal} from "obsidian";
|
|
||||||
import ExoCommand from "./ExoCommand";
|
|
||||||
import ExoCommands from "./ExoCommands";
|
|
||||||
|
|
||||||
export class ExoCommandsModal extends FuzzySuggestModal<ExoCommand> {
|
|
||||||
|
|
||||||
getItems(): ExoCommand[] {
|
|
||||||
return ExoCommands.all(this.app);
|
|
||||||
}
|
|
||||||
|
|
||||||
getItemText(cmd: ExoCommand): string {
|
|
||||||
return cmd.name;
|
|
||||||
}
|
|
||||||
|
|
||||||
async onChooseItem(cmd: ExoCommand, evt: MouseEvent | KeyboardEvent) {
|
|
||||||
await cmd.execute(this.app);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,10 +0,0 @@
|
||||||
import {TFile} from "obsidian";
|
|
||||||
|
|
||||||
export default class Note {
|
|
||||||
constructor(private tfile: TFile) {
|
|
||||||
}
|
|
||||||
|
|
||||||
name(): string {
|
|
||||||
return this.tfile.basename;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,15 +0,0 @@
|
||||||
import Note from "./Note";
|
|
||||||
import {App} from "obsidian";
|
|
||||||
|
|
||||||
export default class NoteRepository {
|
|
||||||
private app: App;
|
|
||||||
|
|
||||||
constructor(app: App) {
|
|
||||||
this.app = app;
|
|
||||||
}
|
|
||||||
|
|
||||||
all(): Note[] {
|
|
||||||
const files = this.app.vault.getFiles();
|
|
||||||
return files.map(f => new Note(f));
|
|
||||||
}
|
|
||||||
}
|
|
20
ExoApi.ts
20
ExoApi.ts
|
@ -1,20 +0,0 @@
|
||||||
import {App, Notice} from "obsidian";
|
|
||||||
import ExoCommands from "./Commands/ExoCommands";
|
|
||||||
|
|
||||||
export default class ExoApi {
|
|
||||||
|
|
||||||
constructor(private app: App) {
|
|
||||||
}
|
|
||||||
|
|
||||||
showNotice() {
|
|
||||||
new Notice("Hello from the API!");
|
|
||||||
}
|
|
||||||
|
|
||||||
commands(): ExoCommands[] {
|
|
||||||
return ExoCommands.all(this.app);
|
|
||||||
}
|
|
||||||
|
|
||||||
commandBySlug(slug: string) {
|
|
||||||
return ExoCommands.bySlug(this.app, slug);
|
|
||||||
}
|
|
||||||
}
|
|
13
README.md
13
README.md
|
@ -2,4 +2,15 @@
|
||||||
|
|
||||||
A prototype plugin implementing the Exocortex concept.
|
A prototype plugin implementing the Exocortex concept.
|
||||||
|
|
||||||
Designed to enhance cognitive processes and decision-making within Obsidian.
|
Designed to enhance cognitive processes and decision-making within Obsidian.
|
||||||
|
|
||||||
|
## Expected data model
|
||||||
|
|
||||||
|
- Note - just a `.md` file
|
||||||
|
- Knowledge Object (KO) - Note with class (KOC)
|
||||||
|
- Knowledge Object Class (KOC) - Classification of a note
|
||||||
|
|
||||||
|
Every KO should have properties:
|
||||||
|
|
||||||
|
- uid
|
||||||
|
- tags (with class)
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
import {FuzzySuggestModal, Notice} from "obsidian";
|
||||||
|
import ExoCommand from "./adapters/input/ExoCommand";
|
||||||
|
import ExoCommands from "./adapters/input/ExoCommands";
|
||||||
|
import ExoContext from "../../common/ExoContext";
|
||||||
|
|
||||||
|
export class ExoMainModal extends FuzzySuggestModal<ExoCommand> {
|
||||||
|
|
||||||
|
constructor(private ctx: ExoContext) {
|
||||||
|
super(ctx.app);
|
||||||
|
}
|
||||||
|
|
||||||
|
getItems(): ExoCommand[] {
|
||||||
|
return ExoCommands.all(this.ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
getItemText(cmd: ExoCommand): string {
|
||||||
|
return cmd.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
async onChooseItem(cmd: ExoCommand) {
|
||||||
|
const startTime = performance.now();
|
||||||
|
try {
|
||||||
|
// console.log(`Executing command ${cmd.name}`);
|
||||||
|
await cmd.execute(this.ctx);
|
||||||
|
// console.log(`Command ${cmd.name} executed`);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
new Notice(`Error: ${e.message}`);
|
||||||
|
} finally {
|
||||||
|
const endTime = performance.now();
|
||||||
|
// console.log(`Execution time for command ${cmd.name}: ${endTime - startTime} ms`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
import {App, CachedMetadata, TFile} from "obsidian";
|
||||||
|
import KObject from "../../../core/src/domain/KObject";
|
||||||
|
import AppUtils from "../utils/AppUtils";
|
||||||
|
|
||||||
|
export default class VaultAdapter {
|
||||||
|
constructor(private app: App, private appUtils: AppUtils) {
|
||||||
|
}
|
||||||
|
|
||||||
|
getAllMdFiles() {
|
||||||
|
return this.app.vault.getMarkdownFiles();
|
||||||
|
}
|
||||||
|
|
||||||
|
getFileCache(file: TFile): CachedMetadata | null {
|
||||||
|
return this.app.metadataCache.getFileCache(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
findMdWith(filter: (f: TFile) => boolean) {
|
||||||
|
return this.getAllMdFiles().filter(filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
getObjectFileOrThrow(ko: KObject): TFile {
|
||||||
|
let res = this.getObjectFile(ko);
|
||||||
|
if (!res) {
|
||||||
|
throw new Error("Object file not found for " + ko);
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
getObjectFile(ko: KObject): TFile | null {
|
||||||
|
const a = this.findMdWith(f => {
|
||||||
|
const frontmatter = this.appUtils.getFrontmatterOrNull(f);
|
||||||
|
if (!frontmatter) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const id: string = frontmatter["uid"];
|
||||||
|
return id === ko.id;
|
||||||
|
});
|
||||||
|
return a[0];
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
import ExoCommand from "./ExoCommand";
|
||||||
|
import {Notice} from "obsidian";
|
||||||
|
import CountNotesUseCase from "../../../../core/src/ports/input/CountNotesUseCase";
|
||||||
|
|
||||||
|
export default class CountNotesExoCommand implements ExoCommand {
|
||||||
|
name = "Notes Count";
|
||||||
|
slug = "count-notes";
|
||||||
|
|
||||||
|
constructor(private useCase: CountNotesUseCase) {
|
||||||
|
}
|
||||||
|
|
||||||
|
async execute(): Promise<void> {
|
||||||
|
const result = this.useCase.count();
|
||||||
|
|
||||||
|
new Notice(`Vault has ${result} notes`);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
import ExoCommand from "./ExoCommand";
|
||||||
|
import ExoContext from "../../../../common/ExoContext";
|
||||||
|
import Area from "../../../../core/src/domain/Area";
|
||||||
|
|
||||||
|
export default class CreateEffortUnderAreaExoCommand implements ExoCommand {
|
||||||
|
name = "Create Effort Under Area";
|
||||||
|
slug = "create-effort-under-area";
|
||||||
|
|
||||||
|
constructor(private ctx: ExoContext) {
|
||||||
|
}
|
||||||
|
|
||||||
|
async execute() {
|
||||||
|
const activeFile = this.ctx.appUtils.getActiveFileOrThrow();
|
||||||
|
const activeKo = this.ctx.kObjectCreator.createFromTFileTyped(activeFile);
|
||||||
|
|
||||||
|
if (!(activeKo instanceof Area)) {
|
||||||
|
throw new Error("Active file is not an Area");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.ctx.createEffortUseCase.taskUnderArea(activeKo);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
import ExoCommand from "./ExoCommand";
|
||||||
|
import ExoContext from "../../../../common/ExoContext";
|
||||||
|
|
||||||
|
export default class CreateEmptyNoteWithinInboxExoCommand implements ExoCommand {
|
||||||
|
name = "Create Effort Within Inbox";
|
||||||
|
slug = "create-effort-within-inbox";
|
||||||
|
|
||||||
|
constructor(private ctx: ExoContext) {
|
||||||
|
}
|
||||||
|
|
||||||
|
async execute() {
|
||||||
|
const uid = this.ctx.utils.generateUid();
|
||||||
|
const path = `/0 Inbox/${uid}.md`;
|
||||||
|
await this.ctx.appUtils.createFile(path, uid);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
import ExoContext from "../../../../common/ExoContext";
|
||||||
|
|
||||||
|
export default interface ExoCommand {
|
||||||
|
name: string;
|
||||||
|
slug: string;
|
||||||
|
|
||||||
|
execute(ctx: ExoContext): Promise<void>;
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
import OpenRandomNoteExoCommand from "./OpenRandomNoteExoCommand";
|
||||||
|
import ExoCommand from "./ExoCommand";
|
||||||
|
import CountNotesExoCommand from "./CountNotesExoCommand";
|
||||||
|
import CreateEmptyNoteWithinInboxExoCommand from "./CreateEmptyNoteWithinInboxExoCommand";
|
||||||
|
import GetActiveFileTagsExoCommand from "./GetActiveFileTagsExoCommand";
|
||||||
|
import GetCurrentKOCExoCommand from "./GetCurrentKOCExoCommand";
|
||||||
|
import OpenCurrentDailyNoteExoCommand from "./OpenCurrentDailyNoteExoCommand";
|
||||||
|
import ExoContext from "../../../../common/ExoContext";
|
||||||
|
import CreateEffortUnderAreaExoCommand from "./CreateEffortUnderAreaExoCommand";
|
||||||
|
|
||||||
|
export default class ExoCommands {
|
||||||
|
static all(ctx: ExoContext): ExoCommand[] {
|
||||||
|
return [
|
||||||
|
new OpenRandomNoteExoCommand(),
|
||||||
|
new CountNotesExoCommand(ctx.countNotesUseCase),
|
||||||
|
new CreateEmptyNoteWithinInboxExoCommand(ctx),
|
||||||
|
new GetActiveFileTagsExoCommand(ctx),
|
||||||
|
new GetCurrentKOCExoCommand(ctx),
|
||||||
|
new OpenCurrentDailyNoteExoCommand(ctx, ctx.getCurrentDNUseCase),
|
||||||
|
new CreateEffortUnderAreaExoCommand(ctx)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
static bySlug(ctx: ExoContext, slug: string): ExoCommand | undefined {
|
||||||
|
return ExoCommands.all(ctx).find(c => c.slug === slug);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
import ExoCommand from "./ExoCommand";
|
||||||
|
import {Notice} from "obsidian";
|
||||||
|
import ExoContext from "../../../../common/ExoContext";
|
||||||
|
|
||||||
|
export default class GetActiveFileTagsExoCommand implements ExoCommand {
|
||||||
|
name = "Get Active File Tags";
|
||||||
|
slug = "get-active-file-tags";
|
||||||
|
|
||||||
|
constructor(private ctx: ExoContext) {
|
||||||
|
}
|
||||||
|
|
||||||
|
async execute(): Promise<void> {
|
||||||
|
const activeFile = this.ctx.appUtils.getActiveFileOrThrow();
|
||||||
|
const tags = this.ctx.appUtils.getTagsFromFile(activeFile);
|
||||||
|
new Notice(`The current opened note has tags: ${tags.join(", ")}`);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
import ExoCommand from "./ExoCommand";
|
||||||
|
import {Notice} from "obsidian";
|
||||||
|
import ExoContext from "../../../../common/ExoContext";
|
||||||
|
|
||||||
|
export default class GetCurrentKOCExoCommand implements ExoCommand {
|
||||||
|
name = "Get Current KOC";
|
||||||
|
slug = "get-current-koc";
|
||||||
|
|
||||||
|
constructor(private ctx: ExoContext) {
|
||||||
|
}
|
||||||
|
|
||||||
|
async execute(): Promise<void> {
|
||||||
|
const file = this.ctx.appUtils.getActiveFileOrThrow();
|
||||||
|
const currentKO = this.ctx.kObjectCreator.createFromTFile(file);
|
||||||
|
new Notice(`The current object's KOC is ${currentKO.koc}`);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
import ExoCommand from "./ExoCommand";
|
||||||
|
import ExoContext from "../../../../common/ExoContext";
|
||||||
|
import GetCurrentDailyNoteUseCase from "../../../../core/src/ports/input/GetCurrentDailyNoteUseCase";
|
||||||
|
|
||||||
|
export default class OpenCurrentDailyNoteExoCommand implements ExoCommand {
|
||||||
|
name = "Open Current Daily Note";
|
||||||
|
slug = "open-current-daily-note";
|
||||||
|
|
||||||
|
constructor(private ctx: ExoContext,
|
||||||
|
private getCurrentDailyNoteUseCase: GetCurrentDailyNoteUseCase) {
|
||||||
|
}
|
||||||
|
|
||||||
|
async execute() {
|
||||||
|
const currentDN = await this.getCurrentDailyNoteUseCase.get();
|
||||||
|
|
||||||
|
if (!currentDN) {
|
||||||
|
throw new Error("No current daily note found");
|
||||||
|
}
|
||||||
|
|
||||||
|
let file = this.ctx.vaultAdapter.getObjectFile(currentDN);
|
||||||
|
if (!file) {
|
||||||
|
throw new Error("Daily note file not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.ctx.appUtils.openFile(file);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,12 +1,13 @@
|
||||||
import ExoCommand from "./ExoCommand";
|
import ExoCommand from "./ExoCommand";
|
||||||
import {App, Notice} from "obsidian";
|
import {Notice} from "obsidian";
|
||||||
|
import ExoContext from "../../../../common/ExoContext";
|
||||||
|
|
||||||
export default class OpenRandomNoteExoCommand implements ExoCommand {
|
export default class OpenRandomNoteExoCommand implements ExoCommand {
|
||||||
name: string = "Рандомная заметка из прошлого";
|
name = "Рандомная заметка из прошлого";
|
||||||
slug: string = "open-random-note";
|
slug = "open-random-note";
|
||||||
|
|
||||||
async execute(app: App): Promise<void> {
|
async execute(ctx: ExoContext): Promise<void> {
|
||||||
const files = app.vault.getFiles();
|
const files = ctx.vaultAdapter.getAllMdFiles();
|
||||||
const today = new Date();
|
const today = new Date();
|
||||||
const lastMonth = new Date(today.getFullYear(), today.getMonth() - 1, today.getDate()).setHours(0, 0, 0, 0); // Дата месяц назад без времени
|
const lastMonth = new Date(today.getFullYear(), today.getMonth() - 1, today.getDate()).setHours(0, 0, 0, 0); // Дата месяц назад без времени
|
||||||
|
|
||||||
|
@ -16,9 +17,9 @@ export default class OpenRandomNoteExoCommand implements ExoCommand {
|
||||||
if (oldNotes.length > 0) {
|
if (oldNotes.length > 0) {
|
||||||
// Выбираем случайную заметку
|
// Выбираем случайную заметку
|
||||||
const randomNote = oldNotes[Math.floor(Math.random() * oldNotes.length)];
|
const randomNote = oldNotes[Math.floor(Math.random() * oldNotes.length)];
|
||||||
|
|
||||||
// Открываем её в активной панели
|
// Открываем её в активной панели
|
||||||
const leaf = app.workspace.getLeaf(false);
|
await ctx.appUtils.openFile(randomNote);
|
||||||
await leaf.openFile(randomNote);
|
|
||||||
} else {
|
} else {
|
||||||
new Notice("No old notes found.");
|
new Notice("No old notes found.");
|
||||||
}
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
import DailyNote from "../../../../core/src/domain/DailyNote";
|
||||||
|
import {TFile} from "obsidian";
|
||||||
|
import VaultAdapter from "../VaultAdapter";
|
||||||
|
import DailyNoteCreator from "../../utils/DailyNoteCreator";
|
||||||
|
import AppUtils from "../../utils/AppUtils";
|
||||||
|
import DailyNoteRepository from "../../../../core/src/ports/output/DailyNoteRepository";
|
||||||
|
|
||||||
|
export default class DailyNotePersistenceAdapter implements DailyNoteRepository {
|
||||||
|
constructor(private appUtils: AppUtils,
|
||||||
|
private vaultAdapter: VaultAdapter,
|
||||||
|
private dailyNoteCreator: DailyNoteCreator) {
|
||||||
|
}
|
||||||
|
|
||||||
|
async findCurrent(): Promise<DailyNote | null> {
|
||||||
|
const allDNs = await this.findAll();
|
||||||
|
let currentDailyNotes = allDNs.filter(dn => dn.date.toDateString() === new Date().toDateString());
|
||||||
|
return currentDailyNotes.length > 0 ? currentDailyNotes[0] : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async findAll(): Promise<DailyNote[]> {
|
||||||
|
const rawDailyNotes: TFile[] = this.vaultAdapter.findMdWith((f: TFile) => {
|
||||||
|
return this.appUtils.getTagsFromFile(f).includes("TMS/DailyNote");
|
||||||
|
});
|
||||||
|
|
||||||
|
return rawDailyNotes.map(f => this.dailyNoteCreator.createFromTFile(f));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
import EffortRepository from "../../../../core/src/ports/output/EffortRepository";
|
||||||
|
import Effort from "../../../../core/src/domain/effort/Effort";
|
||||||
|
import ExoContext from "../../../../common/ExoContext";
|
||||||
|
|
||||||
|
export default class EffortPersistenceAdapter implements EffortRepository {
|
||||||
|
constructor(private ctx: ExoContext) {
|
||||||
|
}
|
||||||
|
|
||||||
|
async save(effort: Effort): Promise<void> {
|
||||||
|
const folderPath: string = this.getPathForCreate(effort)
|
||||||
|
const filePath = folderPath + "/" + effort.title + ".md";
|
||||||
|
const data: string = "---\ntags:\n - EMS/Effort\n---\n\nThis is effort created with Exo!";
|
||||||
|
await this.ctx.app.vault.create(filePath, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO should be in EffortPathRulesHelper in app module
|
||||||
|
getPathForCreate(effort: Effort): string {
|
||||||
|
if (effort.area !== null) {
|
||||||
|
const areaFile = this.ctx.vaultAdapter.getObjectFileOrThrow(effort.area);
|
||||||
|
const areaFolder = areaFile.parent;
|
||||||
|
if (!areaFolder) {
|
||||||
|
throw new Error("Area file has no parent folder");
|
||||||
|
}
|
||||||
|
|
||||||
|
return areaFolder.path;
|
||||||
|
}
|
||||||
|
|
||||||
|
return "/0 Inbox/";
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
import {TFile} from "obsidian";
|
||||||
|
|
||||||
|
export default class PersistentObject {
|
||||||
|
constructor(private file: TFile, private props: any) {
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,68 @@
|
||||||
|
import {App, FrontMatterCache, TFile} from "obsidian";
|
||||||
|
|
||||||
|
export default class AppUtils {
|
||||||
|
constructor(private app: App) {
|
||||||
|
}
|
||||||
|
|
||||||
|
async createFile(path: string, textContent: string) {
|
||||||
|
await this.app.vault.create(path, textContent);
|
||||||
|
}
|
||||||
|
|
||||||
|
async openFile(file: TFile) {
|
||||||
|
const leaf = this.app.workspace.getLeaf(false);
|
||||||
|
await leaf.openFile(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
getActiveFileOrThrow(): TFile {
|
||||||
|
const file = this.getActiveFileOrNull();
|
||||||
|
if (!file) {
|
||||||
|
throw new Error('No note opened.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
|
||||||
|
getActiveFileOrNull(): TFile | null {
|
||||||
|
return this.app.workspace.getActiveFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
getFrontmatterOrNull(file: TFile): FrontMatterCache | null {
|
||||||
|
try {
|
||||||
|
return this.getFrontmatterOrThrow(file)
|
||||||
|
} catch (e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getFrontmatterOrThrow(file: TFile): FrontMatterCache {
|
||||||
|
let fileCache = this.app.metadataCache.getFileCache(file);
|
||||||
|
if (!fileCache) {
|
||||||
|
throw new Error(`File cache not found for ${file.path}`);
|
||||||
|
}
|
||||||
|
if (!fileCache.frontmatter) {
|
||||||
|
throw new Error(`Frontmatter not found for ${file.path}`);
|
||||||
|
}
|
||||||
|
return fileCache.frontmatter;
|
||||||
|
}
|
||||||
|
|
||||||
|
getTagsFromFile(file: TFile): string[] {
|
||||||
|
const frontmatter = this.app.metadataCache.getFileCache(file)?.frontmatter;
|
||||||
|
|
||||||
|
if (frontmatter && frontmatter.tags) {
|
||||||
|
if (typeof frontmatter.tags === "string") {
|
||||||
|
return frontmatter.tags.split(",").map(tag => tag.trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(frontmatter.tags)) {
|
||||||
|
return frontmatter.tags;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
getFileByName(parentFileName: string): TFile {
|
||||||
|
return this.app.vault.getMarkdownFiles().filter(f => f.name == parentFileName)[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
import {TFile} from "obsidian";
|
||||||
|
import DailyNote from "../../../core/src/domain/DailyNote";
|
||||||
|
import AppUtils from "./AppUtils";
|
||||||
|
import {UUID} from "node:crypto";
|
||||||
|
|
||||||
|
export default class DailyNoteCreator {
|
||||||
|
constructor(private appUtils: AppUtils) {
|
||||||
|
}
|
||||||
|
|
||||||
|
createFromTFile(file: TFile) {
|
||||||
|
const frontmatter = this.appUtils.getFrontmatterOrThrow(file);
|
||||||
|
const id = frontmatter["uid"] as UUID;
|
||||||
|
const dateStr = frontmatter["dn-date"];
|
||||||
|
const date = new Date(dateStr);
|
||||||
|
return new DailyNote(id, date);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,60 @@
|
||||||
|
import {TFile} from "obsidian";
|
||||||
|
import KObject from "../../../core/src/domain/KObject";
|
||||||
|
import {KOC} from "../../../core/src/domain/KOC";
|
||||||
|
import AppUtils from "./AppUtils";
|
||||||
|
import Area from "../../../core/src/domain/Area";
|
||||||
|
import {UUID} from "node:crypto";
|
||||||
|
|
||||||
|
export default class KObjectCreator {
|
||||||
|
constructor(private appUtils: AppUtils) {
|
||||||
|
}
|
||||||
|
|
||||||
|
createFromTFile(file: TFile) {
|
||||||
|
const frontmatter = this.appUtils.getFrontmatterOrThrow(file);
|
||||||
|
const id = frontmatter["uid"] as UUID;
|
||||||
|
const koc = this.getFileKoc(file);
|
||||||
|
return new KObject(id, koc);
|
||||||
|
}
|
||||||
|
|
||||||
|
createFromTFileTyped(file: TFile) {
|
||||||
|
const koc = this.getFileKoc(file);
|
||||||
|
switch (koc) {
|
||||||
|
case KOC.EMS_AREA:
|
||||||
|
return this.createArea(file);
|
||||||
|
default:
|
||||||
|
throw new Error("Not implemented createFromTFileTyped")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
createArea(file: TFile): Area {
|
||||||
|
const koProperties = this.appUtils.getFrontmatterOrThrow(file);
|
||||||
|
|
||||||
|
const id: UUID = koProperties["uid"] as UUID;
|
||||||
|
|
||||||
|
let parentArea: Area | null = null;
|
||||||
|
const parentStr: string = koProperties["a-parent"];
|
||||||
|
if (parentStr) {
|
||||||
|
const parentFileName = parentStr.replace("[[", "").replace("]]", "");
|
||||||
|
const parentAreaFile: TFile = this.appUtils.getFileByName(parentFileName + ".md");
|
||||||
|
parentArea = this.createArea(parentAreaFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Area(id, file.name.replace(".md", ""), parentArea)
|
||||||
|
}
|
||||||
|
|
||||||
|
getFileKoc(file: TFile): KOC {
|
||||||
|
const tags = this.appUtils.getTagsFromFile(file);
|
||||||
|
|
||||||
|
if (tags.includes("IMS/MOC")) {
|
||||||
|
return KOC.IMS_MOC
|
||||||
|
} else if (tags.includes("EMS/Area")) {
|
||||||
|
return KOC.EMS_AREA;
|
||||||
|
} else if (tags.includes("EMS/Effort")) {
|
||||||
|
return KOC.EMS_EFFORT;
|
||||||
|
} else if (tags.includes("TMS/DailyNote")) {
|
||||||
|
return KOC.TMS_DN;
|
||||||
|
} else {
|
||||||
|
return KOC.UNKNOWN;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
import ExoContext from "../../../common/ExoContext";
|
||||||
|
|
||||||
|
export default class KObjectUtility {
|
||||||
|
constructor(private ctx: ExoContext) {
|
||||||
|
}
|
||||||
|
|
||||||
|
async addMissingId(): Promise<void> {
|
||||||
|
let allMdFiles = this.ctx.vaultAdapter.getAllMdFiles();
|
||||||
|
|
||||||
|
const KOs = allMdFiles.filter(f => {
|
||||||
|
const tags = this.ctx.appUtils.getTagsFromFile(f);
|
||||||
|
let isKo = tags.some(tag => tag.startsWith("TMS/") || tag.startsWith("IMS/") || tag.startsWith("EMS/") || tag.startsWith("KMS/"));
|
||||||
|
return isKo && !f.path.startsWith("9 Meta/");
|
||||||
|
});
|
||||||
|
|
||||||
|
const withoutId = KOs.filter(f => {
|
||||||
|
let frontmatter = this.ctx.appUtils.getFrontmatterOrThrow(f);
|
||||||
|
if (!frontmatter) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return !frontmatter["uid"];
|
||||||
|
})
|
||||||
|
|
||||||
|
for (let f of withoutId) {
|
||||||
|
await this.ctx.app.fileManager.processFrontMatter(f, (frontmatter) => {
|
||||||
|
frontmatter['uid'] = crypto.randomUUID();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
module.exports = {
|
||||||
|
presets: [
|
||||||
|
['@babel/preset-env', {targets: {node: 'current'}}],
|
||||||
|
'@babel/preset-typescript',
|
||||||
|
],
|
||||||
|
};
|
|
@ -0,0 +1,48 @@
|
||||||
|
import {App} from "obsidian";
|
||||||
|
import DailyNoteRepository from "../core/src/ports/output/DailyNoteRepository";
|
||||||
|
import Utils from "../core/src/utils/Utils";
|
||||||
|
import DailyNoteCreator from "../app/src/utils/DailyNoteCreator";
|
||||||
|
import VaultAdapter from "../app/src/adapters/VaultAdapter";
|
||||||
|
import KObjectCreator from "../app/src/utils/KObjectCreator";
|
||||||
|
import AppUtils from "../app/src/utils/AppUtils";
|
||||||
|
import CountNotesUseCase from "../core/src/ports/input/CountNotesUseCase";
|
||||||
|
import CountNotesService from "../core/src/service/CountNotesService";
|
||||||
|
import DailyNotePersistenceAdapter from "../app/src/adapters/output/DailyNotePersistenceAdapter";
|
||||||
|
import GetCurrentDailyNoteUseCase from "../core/src/ports/input/GetCurrentDailyNoteUseCase";
|
||||||
|
import GetCurrentDailyNoteService from "../core/src/service/GetCurrentDailyNoteService";
|
||||||
|
import CreateEffortUseCase from "../core/src/ports/input/CreateEffortUseCase";
|
||||||
|
import CreateEffortService from "../core/src/service/CreateEffortService";
|
||||||
|
import EffortRepository from "../core/src/ports/output/EffortRepository";
|
||||||
|
import EffortPersistenceAdapter from "../app/src/adapters/output/EffortPersistenceAdapter";
|
||||||
|
import KObjectUtility from "../app/src/utils/KObjectUtility";
|
||||||
|
|
||||||
|
export default class ExoContext {
|
||||||
|
public readonly utils: Utils;
|
||||||
|
public readonly kObjectCreator: KObjectCreator
|
||||||
|
public readonly dailyNoteCreator: DailyNoteCreator;
|
||||||
|
public readonly dailyNoteRepository: DailyNoteRepository;
|
||||||
|
public readonly kObjectUtility: KObjectUtility;
|
||||||
|
|
||||||
|
public readonly vaultAdapter: VaultAdapter;
|
||||||
|
public readonly appUtils: AppUtils;
|
||||||
|
|
||||||
|
public readonly countNotesUseCase: CountNotesUseCase;
|
||||||
|
public readonly getCurrentDNUseCase: GetCurrentDailyNoteUseCase;
|
||||||
|
public readonly createEffortUseCase: CreateEffortUseCase;
|
||||||
|
public readonly effortRepository: EffortRepository;
|
||||||
|
|
||||||
|
constructor(public app: App) {
|
||||||
|
this.utils = new Utils(this.app);
|
||||||
|
this.appUtils = new AppUtils(this.app);
|
||||||
|
this.vaultAdapter = new VaultAdapter(this.app, this.appUtils);
|
||||||
|
this.kObjectCreator = new KObjectCreator(this.appUtils);
|
||||||
|
this.dailyNoteCreator = new DailyNoteCreator(this.appUtils);
|
||||||
|
this.dailyNoteRepository = new DailyNotePersistenceAdapter(this.appUtils, this.vaultAdapter, this.dailyNoteCreator);
|
||||||
|
this.kObjectUtility = new KObjectUtility(this);
|
||||||
|
|
||||||
|
this.countNotesUseCase = new CountNotesService(this.vaultAdapter);
|
||||||
|
this.getCurrentDNUseCase = new GetCurrentDailyNoteService(this.dailyNoteRepository);
|
||||||
|
this.effortRepository = new EffortPersistenceAdapter(this);
|
||||||
|
this.createEffortUseCase = new CreateEffortService(this.effortRepository);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
import {Notice} from "obsidian";
|
||||||
|
import ExoContext from "../../common/ExoContext";
|
||||||
|
|
||||||
|
export default class ExoApi {
|
||||||
|
|
||||||
|
// noinspection JSUnusedLocalSymbols
|
||||||
|
constructor(private ctx: ExoContext) {
|
||||||
|
}
|
||||||
|
|
||||||
|
// noinspection JSUnusedGlobalSymbols
|
||||||
|
showNotice() {
|
||||||
|
new Notice("Hello from the API!");
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
import KObject from "./KObject";
|
||||||
|
import {KOC} from "./KOC";
|
||||||
|
import {UUID} from "node:crypto";
|
||||||
|
|
||||||
|
export default class Area extends KObject {
|
||||||
|
constructor(public id: UUID,
|
||||||
|
public readonly name: string,
|
||||||
|
public readonly parent: Area | null) {
|
||||||
|
super(id, KOC.EMS_AREA);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
import KObject from "./KObject";
|
||||||
|
import {UUID} from "node:crypto";
|
||||||
|
import {KOC} from "./KOC";
|
||||||
|
|
||||||
|
export default class DailyNote extends KObject {
|
||||||
|
constructor(public id: UUID,
|
||||||
|
public date: Date) {
|
||||||
|
super(id, KOC.TMS_DN);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
export enum KOC {
|
||||||
|
EMS_AREA = "EMS/Area",
|
||||||
|
EMS_EFFORT = "EMS/Effort",
|
||||||
|
IMS_MOC = "IMS/MOC",
|
||||||
|
TMS_DN = "TMS/DailyNote",
|
||||||
|
UNKNOWN = "UNKNOWN"
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
import {KOC} from "./KOC";
|
||||||
|
import {UUID} from "node:crypto";
|
||||||
|
|
||||||
|
export default class KObject {
|
||||||
|
constructor(public readonly id: UUID,
|
||||||
|
public readonly koc: KOC) {
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
import KObject from "../KObject";
|
||||||
|
import {EffortStatus} from "./EffortStatus";
|
||||||
|
import {KOC} from "../KOC";
|
||||||
|
import Area from "../Area";
|
||||||
|
import {UUID} from "node:crypto";
|
||||||
|
|
||||||
|
export default class Effort extends KObject {
|
||||||
|
constructor(public id: UUID,
|
||||||
|
public title: string,
|
||||||
|
public status: EffortStatus,
|
||||||
|
public started: Date | null,
|
||||||
|
public ended: Date | null,
|
||||||
|
public area: Area | null) {
|
||||||
|
super(id, KOC.EMS_EFFORT);
|
||||||
|
}
|
||||||
|
|
||||||
|
start() {
|
||||||
|
this.started = new Date();
|
||||||
|
this.status = EffortStatus.STARTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
end() {
|
||||||
|
this.ended = new Date();
|
||||||
|
this.status = EffortStatus.ENDED;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
export enum EffortStatus {
|
||||||
|
DRAFT,
|
||||||
|
BACKLOG,
|
||||||
|
READY,
|
||||||
|
STARTED,
|
||||||
|
ENDED,
|
||||||
|
TRASHED
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
export default interface CountNotesUseCase {
|
||||||
|
count(): number;
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
import Effort from "../../domain/effort/Effort";
|
||||||
|
import Area from "../../domain/Area";
|
||||||
|
|
||||||
|
export default interface CreateEffortUseCase {
|
||||||
|
taskUnderArea(area: Area): Effort;
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
import DailyNote from "../../domain/DailyNote";
|
||||||
|
|
||||||
|
export default interface GetCurrentDailyNoteUseCase {
|
||||||
|
get(): Promise<DailyNote | null>;
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
import DailyNote from "../../domain/DailyNote";
|
||||||
|
|
||||||
|
export default interface DailyNoteRepository {
|
||||||
|
findCurrent(): Promise<DailyNote | null>;
|
||||||
|
|
||||||
|
findAll(): Promise<DailyNote[]>;
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
import Effort from "../../domain/effort/Effort";
|
||||||
|
|
||||||
|
export default interface EffortRepository {
|
||||||
|
save(effort: Effort): void;
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
import CountNotesUseCase from "../ports/input/CountNotesUseCase";
|
||||||
|
import VaultAdapter from "../../../app/src/adapters/VaultAdapter";
|
||||||
|
|
||||||
|
export default class CountNotesService implements CountNotesUseCase {
|
||||||
|
|
||||||
|
constructor(private vaultAdapter: VaultAdapter) {
|
||||||
|
}
|
||||||
|
|
||||||
|
count(): number {
|
||||||
|
return this.vaultAdapter.getAllMdFiles().length;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
import CreateEffortUseCase from "../ports/input/CreateEffortUseCase";
|
||||||
|
import Area from "../domain/Area";
|
||||||
|
import {EffortStatus} from "../domain/effort/EffortStatus";
|
||||||
|
import EffortRepository from "../ports/output/EffortRepository";
|
||||||
|
import {UUID} from "node:crypto";
|
||||||
|
import Effort from "../domain/effort/Effort";
|
||||||
|
|
||||||
|
export default class CreateEffortService implements CreateEffortUseCase {
|
||||||
|
constructor(private effortRepository: EffortRepository) {
|
||||||
|
}
|
||||||
|
|
||||||
|
taskUnderArea(area: Area): Effort {
|
||||||
|
const title = "Task under " + area.name;
|
||||||
|
const id = crypto.randomUUID() as UUID;
|
||||||
|
const effort = new Effort(id, title, EffortStatus.DRAFT, null, null, area);
|
||||||
|
|
||||||
|
this.effortRepository.save(effort);
|
||||||
|
return effort;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
import GetCurrentDailyNoteUseCase from "../ports/input/GetCurrentDailyNoteUseCase";
|
||||||
|
import DailyNote from "../domain/DailyNote";
|
||||||
|
import DailyNoteRepository from "../ports/output/DailyNoteRepository";
|
||||||
|
|
||||||
|
export default class GetCurrentDailyNoteService implements GetCurrentDailyNoteUseCase {
|
||||||
|
|
||||||
|
constructor(private repository: DailyNoteRepository) {
|
||||||
|
}
|
||||||
|
|
||||||
|
async get(): Promise<DailyNote | null> {
|
||||||
|
return this.repository.findCurrent();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
import {App} from "obsidian";
|
||||||
|
|
||||||
|
export default class Utils {
|
||||||
|
|
||||||
|
constructor(private app: App) {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
generateUid(): string {
|
||||||
|
return crypto.randomUUID();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
import {describe, expect} from '@jest/globals';
|
||||||
|
|
||||||
|
describe('JEST', () => {
|
||||||
|
it('is working', async () => {
|
||||||
|
expect(true).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
20
main.ts
20
main.ts
|
@ -1,21 +1,19 @@
|
||||||
import {Plugin} from 'obsidian';
|
import {Plugin} from 'obsidian';
|
||||||
import {ExoCommandsModal} from "./Commands/ExoCommandsModal";
|
import {ExoMainModal} from "./app/src/ExoMainModal";
|
||||||
import "localforage";
|
import "localforage";
|
||||||
import ExoApi from "./ExoApi";
|
import ExoApi from "./core/src/ExoApi";
|
||||||
|
import ExoContext from "./common/ExoContext";
|
||||||
|
|
||||||
export default class ExoPlugin extends Plugin {
|
export default class ExoPlugin extends Plugin {
|
||||||
private api: ExoApi;
|
private api: ExoApi;
|
||||||
|
private ctx: ExoContext;
|
||||||
|
|
||||||
async onload() {
|
async onload() {
|
||||||
this.addRibbonIcon('star', 'Exocortex Commands List', () => {
|
this.ctx = new ExoContext(this.app);
|
||||||
new ExoCommandsModal(this.app).open();
|
this.api = new ExoApi(this.ctx);
|
||||||
|
|
||||||
|
this.addRibbonIcon('star', 'Exocortex commands List', () => {
|
||||||
|
new ExoMainModal(this.ctx).open();
|
||||||
});
|
});
|
||||||
|
|
||||||
this.api = new ExoApi(this.app);
|
|
||||||
(this.app as any).plugins.plugins["exo-api"] = this.api;
|
|
||||||
}
|
|
||||||
|
|
||||||
onunload() {
|
|
||||||
delete (this.app as any).plugins.plugins["exo-api"];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
11
package.json
11
package.json
|
@ -5,18 +5,24 @@
|
||||||
"main": "main.js",
|
"main": "main.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "node esbuild.config.mjs",
|
"dev": "node esbuild.config.mjs",
|
||||||
"build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production",
|
"build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production && jest",
|
||||||
"version": "node version-bump.mjs && git add manifest.json versions.json"
|
"version": "node version-bump.mjs && git add manifest.json versions.json"
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"author": "",
|
"author": "",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@babel/core": "^7.26.0",
|
||||||
|
"@babel/preset-env": "^7.26.0",
|
||||||
|
"@babel/preset-typescript": "^7.26.0",
|
||||||
|
"@jest/globals": "^29.7.0",
|
||||||
|
"@types/jest": "^29.5.14",
|
||||||
"@types/luxon": "^3.4.2",
|
"@types/luxon": "^3.4.2",
|
||||||
"@types/node": "^16.11.6",
|
"@types/node": "^16.11.6",
|
||||||
"@types/parsimmon": "^1.10.9",
|
"@types/parsimmon": "^1.10.9",
|
||||||
"@typescript-eslint/eslint-plugin": "5.29.0",
|
"@typescript-eslint/eslint-plugin": "5.29.0",
|
||||||
"@typescript-eslint/parser": "5.29.0",
|
"@typescript-eslint/parser": "5.29.0",
|
||||||
|
"babel-jest": "^29.7.0",
|
||||||
"builtin-modules": "3.3.0",
|
"builtin-modules": "3.3.0",
|
||||||
"esbuild": "0.17.3",
|
"esbuild": "0.17.3",
|
||||||
"obsidian": "latest",
|
"obsidian": "latest",
|
||||||
|
@ -26,6 +32,7 @@
|
||||||
"typescript": "4.7.4"
|
"typescript": "4.7.4"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"compare-versions": "^6.1.1"
|
"compare-versions": "^6.1.1",
|
||||||
|
"jest": "^29.7.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,7 @@
|
||||||
"./types",
|
"./types",
|
||||||
"./node_modules/obsidian-dataview"
|
"./node_modules/obsidian-dataview"
|
||||||
],
|
],
|
||||||
|
"types": ["jest"],
|
||||||
"paths": {
|
"paths": {
|
||||||
"*": [
|
"*": [
|
||||||
"./node_modules/obsidian-dataview/lib/*"
|
"./node_modules/obsidian-dataview/lib/*"
|
||||||
|
|
Loading…
Reference in New Issue