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.
|
||||
|
||||
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 {App, Notice} from "obsidian";
|
||||
import {Notice} from "obsidian";
|
||||
import ExoContext from "../../../../common/ExoContext";
|
||||
|
||||
export default class OpenRandomNoteExoCommand implements ExoCommand {
|
||||
name: string = "Рандомная заметка из прошлого";
|
||||
slug: string = "open-random-note";
|
||||
name = "Рандомная заметка из прошлого";
|
||||
slug = "open-random-note";
|
||||
|
||||
async execute(app: App): Promise<void> {
|
||||
const files = app.vault.getFiles();
|
||||
async execute(ctx: ExoContext): Promise<void> {
|
||||
const files = ctx.vaultAdapter.getAllMdFiles();
|
||||
const today = new Date();
|
||||
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) {
|
||||
// Выбираем случайную заметку
|
||||
const randomNote = oldNotes[Math.floor(Math.random() * oldNotes.length)];
|
||||
|
||||
// Открываем её в активной панели
|
||||
const leaf = app.workspace.getLeaf(false);
|
||||
await leaf.openFile(randomNote);
|
||||
await ctx.appUtils.openFile(randomNote);
|
||||
} else {
|
||||
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 {ExoCommandsModal} from "./Commands/ExoCommandsModal";
|
||||
import {ExoMainModal} from "./app/src/ExoMainModal";
|
||||
import "localforage";
|
||||
import ExoApi from "./ExoApi";
|
||||
import ExoApi from "./core/src/ExoApi";
|
||||
import ExoContext from "./common/ExoContext";
|
||||
|
||||
export default class ExoPlugin extends Plugin {
|
||||
private api: ExoApi;
|
||||
private ctx: ExoContext;
|
||||
|
||||
async onload() {
|
||||
this.addRibbonIcon('star', 'Exocortex Commands List', () => {
|
||||
new ExoCommandsModal(this.app).open();
|
||||
this.ctx = new ExoContext(this.app);
|
||||
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",
|
||||
"scripts": {
|
||||
"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"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"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/node": "^16.11.6",
|
||||
"@types/parsimmon": "^1.10.9",
|
||||
"@typescript-eslint/eslint-plugin": "5.29.0",
|
||||
"@typescript-eslint/parser": "5.29.0",
|
||||
"babel-jest": "^29.7.0",
|
||||
"builtin-modules": "3.3.0",
|
||||
"esbuild": "0.17.3",
|
||||
"obsidian": "latest",
|
||||
|
@ -26,6 +32,7 @@
|
|||
"typescript": "4.7.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"compare-versions": "^6.1.1"
|
||||
"compare-versions": "^6.1.1",
|
||||
"jest": "^29.7.0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
"./types",
|
||||
"./node_modules/obsidian-dataview"
|
||||
],
|
||||
"types": ["jest"],
|
||||
"paths": {
|
||||
"*": [
|
||||
"./node_modules/obsidian-dataview/lib/*"
|
||||
|
|
Loading…
Reference in New Issue