Add Area layout

This commit is contained in:
kitelev 2025-01-04 00:38:31 +05:00
parent d34166eac3
commit 01196da3b7
22 changed files with 223 additions and 34 deletions

View File

@ -1,6 +1,6 @@
import {FuzzySuggestModal, Notice} from "obsidian"; import {FuzzySuggestModal, Notice} from "obsidian";
import ExoCommand from "./adapters/input/ExoCommand"; import ExoCommand from "./adapters/input/commands/ExoCommand";
import ExoCommands from "./adapters/input/ExoCommands"; import ExoCommands from "./adapters/input/commands/ExoCommands";
import ExoContext from "../../common/ExoContext"; import ExoContext from "../../common/ExoContext";
export class ExoMainModal extends FuzzySuggestModal<ExoCommand> { export class ExoMainModal extends FuzzySuggestModal<ExoCommand> {
@ -18,17 +18,11 @@ export class ExoMainModal extends FuzzySuggestModal<ExoCommand> {
} }
async onChooseItem(cmd: ExoCommand) { async onChooseItem(cmd: ExoCommand) {
const startTime = performance.now();
try { try {
// console.log(`Executing command ${cmd.name}`);
await cmd.execute(this.ctx); await cmd.execute(this.ctx);
// console.log(`Command ${cmd.name} executed`);
} catch (e) { } catch (e) {
console.error(e); console.error(e);
new Notice(`Error: ${e.message}`); new Notice(`Error: ${e.message}`);
} finally {
const endTime = performance.now();
// console.log(`Execution time for command ${cmd.name}: ${endTime - startTime} ms`);
} }
} }
} }

View File

@ -1,6 +1,6 @@
import ExoCommand from "./ExoCommand"; import ExoCommand from "./ExoCommand";
import {Notice} from "obsidian"; import {Notice} from "obsidian";
import CountNotesUseCase from "../../../../core/src/ports/input/CountNotesUseCase"; import CountNotesUseCase from "../../../../../core/src/ports/input/CountNotesUseCase";
export default class CountNotesExoCommand implements ExoCommand { export default class CountNotesExoCommand implements ExoCommand {
name = "Notes Count"; name = "Notes Count";

View File

@ -1,7 +1,7 @@
import ExoCommand from "./ExoCommand"; import ExoCommand from "./ExoCommand";
import ExoContext from "../../../../common/ExoContext"; import ExoContext from "../../../../../common/ExoContext";
import Area from "../../../../core/src/domain/Area"; import Area from "../../../../../core/src/domain/Area";
import Effort from "../../../../core/src/domain/effort/Effort"; import Effort from "../../../../../core/src/domain/effort/Effort";
export default class CreateEffortExoCommand implements ExoCommand { export default class CreateEffortExoCommand implements ExoCommand {
name = "Create Effort"; name = "Create Effort";

View File

@ -1,5 +1,5 @@
import ExoCommand from "./ExoCommand"; import ExoCommand from "./ExoCommand";
import ExoContext from "../../../../common/ExoContext"; import ExoContext from "../../../../../common/ExoContext";
export default class CreateEmptyNoteWithinInboxExoCommand implements ExoCommand { export default class CreateEmptyNoteWithinInboxExoCommand implements ExoCommand {
name = "Create Effort Within Inbox"; name = "Create Effort Within Inbox";

View File

@ -1,4 +1,4 @@
import ExoContext from "../../../../common/ExoContext"; import ExoContext from "../../../../../common/ExoContext";
export default interface ExoCommand { export default interface ExoCommand {
name: string; name: string;

View File

@ -5,7 +5,7 @@ import CreateEmptyNoteWithinInboxExoCommand from "./CreateEmptyNoteWithinInboxEx
import GetActiveFileTagsExoCommand from "./GetActiveFileTagsExoCommand"; import GetActiveFileTagsExoCommand from "./GetActiveFileTagsExoCommand";
import GetCurrentKOCExoCommand from "./GetCurrentKOCExoCommand"; import GetCurrentKOCExoCommand from "./GetCurrentKOCExoCommand";
import OpenCurrentDailyNoteExoCommand from "./OpenCurrentDailyNoteExoCommand"; import OpenCurrentDailyNoteExoCommand from "./OpenCurrentDailyNoteExoCommand";
import ExoContext from "../../../../common/ExoContext"; import ExoContext from "../../../../../common/ExoContext";
import CreateEffortExoCommand from "./CreateEffortExoCommand"; import CreateEffortExoCommand from "./CreateEffortExoCommand";
export default class ExoCommands { export default class ExoCommands {

View File

@ -1,6 +1,6 @@
import ExoCommand from "./ExoCommand"; import ExoCommand from "./ExoCommand";
import {Notice} from "obsidian"; import {Notice} from "obsidian";
import ExoContext from "../../../../common/ExoContext"; import ExoContext from "../../../../../common/ExoContext";
export default class GetActiveFileTagsExoCommand implements ExoCommand { export default class GetActiveFileTagsExoCommand implements ExoCommand {
name = "Get Active File Tags"; name = "Get Active File Tags";

View File

@ -1,6 +1,6 @@
import ExoCommand from "./ExoCommand"; import ExoCommand from "./ExoCommand";
import {Notice} from "obsidian"; import {Notice} from "obsidian";
import ExoContext from "../../../../common/ExoContext"; import ExoContext from "../../../../../common/ExoContext";
export default class GetCurrentKOCExoCommand implements ExoCommand { export default class GetCurrentKOCExoCommand implements ExoCommand {
name = "Get Current KOC"; name = "Get Current KOC";

View File

@ -1,6 +1,6 @@
import ExoCommand from "./ExoCommand"; import ExoCommand from "./ExoCommand";
import ExoContext from "../../../../common/ExoContext"; import ExoContext from "../../../../../common/ExoContext";
import GetCurrentDailyNoteUseCase from "../../../../core/src/ports/input/GetCurrentDailyNoteUseCase"; import GetCurrentDailyNoteUseCase from "../../../../../core/src/ports/input/GetCurrentDailyNoteUseCase";
export default class OpenCurrentDailyNoteExoCommand implements ExoCommand { export default class OpenCurrentDailyNoteExoCommand implements ExoCommand {
name = "Open Current Daily Note"; name = "Open Current Daily Note";

View File

@ -1,6 +1,6 @@
import ExoCommand from "./ExoCommand"; import ExoCommand from "./ExoCommand";
import {Notice} from "obsidian"; import {Notice} from "obsidian";
import ExoContext from "../../../../common/ExoContext"; import ExoContext from "../../../../../common/ExoContext";
export default class OpenRandomNoteExoCommand implements ExoCommand { export default class OpenRandomNoteExoCommand implements ExoCommand {
name = "Рандомная заметка из прошлого"; name = "Рандомная заметка из прошлого";

View File

@ -0,0 +1,46 @@
import Layout from "./Layout";
import ExoContext from "../../../../../common/ExoContext";
import Area from "../../../../../core/src/domain/Area";
export default class AreaLayout implements Layout<Area> {
constructor(private ctx: ExoContext) {
}
async render(ko: Area): Promise<HTMLDivElement> {
const renderText = document.createElement("div");
const unresolvedEfforts = await this.ctx.effortRepository.find(e => {
if (e.area === null) {
return false;
}
const sameArea = e.area.id == ko.id;
const unresolved = e.isUnresolved();
return sameArea && unresolved;
});
if (unresolvedEfforts.length > 0) {
let h1 = document.createElement("h1");
h1.textContent = "Unresolved Efforts";
renderText.appendChild(h1);
let table = document.createElement("table");
renderText.appendChild(table);
const headerRow = document.createElement("tr");
const th = document.createElement("th");
th.innerText = "Name";
headerRow.appendChild(th);
table.appendChild(headerRow);
for (let effort of unresolvedEfforts) {
const tr = document.createElement("tr");
let td = document.createElement("td");
td.textContent = "[[" + effort.title + "]]";
tr.appendChild(td);
table.appendChild(tr);
}
}
return renderText;
}
}

View File

@ -0,0 +1,3 @@
export default interface Layout<KO> {
render(ko: KO): Promise<HTMLElement>;
}

View File

@ -0,0 +1,19 @@
import AreaLayout from "./AreaLayout";
import {KOC} from "../../../../../core/src/domain/KOC";
import ExoContext from "../../../../../common/ExoContext";
import KObject from "../../../../../core/src/domain/KObject";
import Layout from "./Layout";
export default class LayoutFactory {
constructor(private ctx: ExoContext) {
}
create(ko: KObject): Layout<KObject> | null {
switch (ko.koc) {
case KOC.EMS_AREA:
return new AreaLayout(this.ctx);
default:
return null;
}
}
}

View File

@ -2,6 +2,7 @@ import EffortRepository from "../../../../core/src/ports/output/EffortRepository
import Effort from "../../../../core/src/domain/effort/Effort"; import Effort from "../../../../core/src/domain/effort/Effort";
import ExoContext from "../../../../common/ExoContext"; import ExoContext from "../../../../common/ExoContext";
import Area from "../../../../core/src/domain/Area"; import Area from "../../../../core/src/domain/Area";
import {TFile} from "obsidian";
export default class EffortPersistenceAdapter implements EffortRepository { export default class EffortPersistenceAdapter implements EffortRepository {
constructor(private ctx: ExoContext) { constructor(private ctx: ExoContext) {
@ -21,6 +22,20 @@ export default class EffortPersistenceAdapter implements EffortRepository {
await this.ctx.appUtils.updateFile(file, data); await this.ctx.appUtils.updateFile(file, data);
} }
async find(filter: (e: Effort) => boolean): Promise<Effort[]> {
let all = await this.findAll();
return all.filter(filter);
}
async findAll(): Promise<Effort[]> {
const rawEfforts: TFile[] = this.ctx.appUtils.findMdWith((f: TFile) => {
return this.ctx.appUtils.getTagsFromFile(f).includes("EMS/Effort");
});
let promises = rawEfforts.map(async f => await this.ctx.kObjectCreator.createFromTFileTyped(f) as Effort);
return await Promise.all(promises);
}
private serializeData(effort: Effort) { private serializeData(effort: Effort) {
let result = ""; let result = "";
result += "---\n"; result += "---\n";

View File

@ -0,0 +1,24 @@
import AppUtils from "./AppUtils";
import {TFile} from "obsidian";
import Area from "../../../core/src/domain/Area";
import {UUID} from "node:crypto";
export default class AreaCreator {
constructor(private appUtils: AppUtils) {
}
async create(file: TFile): Promise<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 file = this.appUtils.getTFileFromStrLink(parentStr);
parentArea = await this.create(file);
}
return new Area(id, file.name.replace(".md", ""), parentArea)
}
}

View File

@ -0,0 +1,38 @@
import AppUtils from "./AppUtils";
import {TFile} from "obsidian";
import {UUID} from "node:crypto";
import Effort from "../../../core/src/domain/effort/Effort";
import {EffortStatus} from "../../../core/src/domain/effort/EffortStatus";
import Area from "../../../core/src/domain/Area";
import AreaCreator from "./AreaCreator";
export default class EffortCreator {
constructor(private appUtils: AppUtils, private areaCreator: AreaCreator) {
}
async create(file: TFile): Promise<Effort> {
const koProperties = this.appUtils.getFrontmatterOrThrow(file);
const id: UUID = koProperties["uid"] as UUID;
const status: EffortStatus = koProperties["e-status"] as EffortStatus;
const started: Date | null = koProperties["started"] ? koProperties["started"] as Date : null;
const ended: Date | null = koProperties["ended"] ? koProperties["ended"] as Date : null;
let area: Area | null = null;
const areaStr: string = koProperties["area"];
if (areaStr) {
const file = this.appUtils.getTFileFromStrLink(areaStr);
area = await this.areaCreator.create(file);
}
let parent: Effort | null = null;
const parentStr: string = koProperties["e-parent"];
if (parentStr) {
const file = this.appUtils.getTFileFromStrLink(parentStr);
parent = await this.create(file);
}
const body: string = await this.appUtils.getFileBody(file);
return new Effort(id, file.name.replace(".md", ""), status, started, ended, area, parent, body);
}
}

View File

@ -0,0 +1,17 @@
import {KOC} from "../../../core/src/domain/KOC";
export default class KOCFactory {
static create(tags: string[]) {
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;
}
}
}

View File

@ -6,6 +6,7 @@ import Area from "../../../core/src/domain/Area";
import {UUID} from "node:crypto"; import {UUID} from "node:crypto";
import Effort from "../../../core/src/domain/effort/Effort"; import Effort from "../../../core/src/domain/effort/Effort";
import {EffortStatus} from "../../../core/src/domain/effort/EffortStatus"; import {EffortStatus} from "../../../core/src/domain/effort/EffortStatus";
import KOCFactory from "./KOCFactory";
export default class KObjectCreator { export default class KObjectCreator {
constructor(private appUtils: AppUtils) { constructor(private appUtils: AppUtils) {
@ -45,6 +46,9 @@ export default class KObjectCreator {
return new Area(id, file.name.replace(".md", ""), parentArea) return new Area(id, file.name.replace(".md", ""), parentArea)
} }
/**
* @deprecated
*/
async createEffort(file: TFile): Promise<Effort> { async createEffort(file: TFile): Promise<Effort> {
const koProperties = this.appUtils.getFrontmatterOrThrow(file); const koProperties = this.appUtils.getFrontmatterOrThrow(file);
@ -63,7 +67,7 @@ export default class KObjectCreator {
let parent: Effort | null = null; let parent: Effort | null = null;
const parentStr: string = koProperties["e-parent"]; const parentStr: string = koProperties["e-parent"];
if (parentStr) { if (parentStr) {
const file = this.appUtils.getTFileFromStrLink(areaStr); const file = this.appUtils.getTFileFromStrLink(parentStr);
parent = await this.createEffort(file); parent = await this.createEffort(file);
} }
@ -73,17 +77,6 @@ export default class KObjectCreator {
getFileKoc(file: TFile): KOC { getFileKoc(file: TFile): KOC {
const tags = this.appUtils.getTagsFromFile(file); const tags = this.appUtils.getTagsFromFile(file);
return KOCFactory.create(tags);
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;
}
} }
} }

View File

@ -15,15 +15,21 @@ import EffortRepository from "../core/src/ports/output/EffortRepository";
import EffortPersistenceAdapter from "../app/src/adapters/output/EffortPersistenceAdapter"; import EffortPersistenceAdapter from "../app/src/adapters/output/EffortPersistenceAdapter";
import KObjectUtility from "../app/src/utils/KObjectUtility"; import KObjectUtility from "../app/src/utils/KObjectUtility";
import EffortPathRulesHelper from "../app/src/helpers/EffortPathRulesHelper"; import EffortPathRulesHelper from "../app/src/helpers/EffortPathRulesHelper";
import EffortCreator from "../app/src/utils/EffortCreator";
import AreaCreator from "../app/src/utils/AreaCreator";
import LayoutFactory from "../app/src/adapters/input/layouts/LayoutFactory";
export default class ExoContext { export default class ExoContext {
public readonly utils: Utils; public readonly utils: Utils;
public readonly kObjectCreator: KObjectCreator public readonly kObjectCreator: KObjectCreator
public readonly dailyNoteCreator: DailyNoteCreator; public readonly dailyNoteCreator: DailyNoteCreator;
public readonly areaCreator: AreaCreator;
public readonly effortCreator: EffortCreator;
public readonly dailyNoteRepository: DailyNoteRepository; public readonly dailyNoteRepository: DailyNoteRepository;
public readonly kObjectUtility: KObjectUtility; public readonly kObjectUtility: KObjectUtility;
public readonly appUtils: AppUtils; public readonly appUtils: AppUtils;
public readonly layoutFactory: LayoutFactory;
public readonly countNotesUseCase: CountNotesUseCase; public readonly countNotesUseCase: CountNotesUseCase;
public readonly getCurrentDNUseCase: GetCurrentDailyNoteUseCase; public readonly getCurrentDNUseCase: GetCurrentDailyNoteUseCase;
@ -34,8 +40,13 @@ export default class ExoContext {
constructor(public app: App) { constructor(public app: App) {
this.utils = new Utils(this.app); this.utils = new Utils(this.app);
this.appUtils = new AppUtils(this.app); this.appUtils = new AppUtils(this.app);
this.layoutFactory = new LayoutFactory(this);
this.kObjectCreator = new KObjectCreator(this.appUtils); this.kObjectCreator = new KObjectCreator(this.appUtils);
this.dailyNoteCreator = new DailyNoteCreator(this.appUtils); this.dailyNoteCreator = new DailyNoteCreator(this.appUtils);
this.areaCreator = new AreaCreator(this.appUtils);
this.effortCreator = new EffortCreator(this.appUtils, this.areaCreator);
this.dailyNoteRepository = new DailyNotePersistenceAdapter(this.appUtils, this.dailyNoteCreator); this.dailyNoteRepository = new DailyNotePersistenceAdapter(this.appUtils, this.dailyNoteCreator);
this.kObjectUtility = new KObjectUtility(this); this.kObjectUtility = new KObjectUtility(this);

View File

@ -25,4 +25,12 @@ export default class Effort extends KObject {
this.ended = new Date(); this.ended = new Date();
this.status = EffortStatus.ENDED; this.status = EffortStatus.ENDED;
} }
isResolved(): boolean {
return this.status === EffortStatus.ENDED || this.status === EffortStatus.TRASHED;
}
isUnresolved(): boolean {
return !this.isResolved();
}
} }

View File

@ -2,4 +2,6 @@ import Effort from "../../domain/effort/Effort";
export default interface EffortRepository { export default interface EffortRepository {
save(effort: Effort): Promise<void>; save(effort: Effort): Promise<void>;
find(filter: (e: Effort) => boolean): Promise<Effort[]>;
} }

21
main.ts
View File

@ -1,4 +1,4 @@
import {Plugin} from 'obsidian'; import {Plugin, TFile} from 'obsidian';
import {ExoMainModal} from "./app/src/ExoMainModal"; import {ExoMainModal} from "./app/src/ExoMainModal";
import "localforage"; import "localforage";
import ExoApi from "./core/src/ExoApi"; import ExoApi from "./core/src/ExoApi";
@ -15,5 +15,24 @@ export default class ExoPlugin extends Plugin {
this.addRibbonIcon('star', 'Exocortex commands List', () => { this.addRibbonIcon('star', 'Exocortex commands List', () => {
new ExoMainModal(this.ctx).open(); new ExoMainModal(this.ctx).open();
}); });
this.registerMarkdownPostProcessor(async (el, ctx) => {
if (!ctx.frontmatter || !ctx.frontmatter.tags) {
return;
}
if (el.classList.contains("mod-ui")) {
const file: TFile = this.ctx.appUtils.getFileByPathOrThrow(ctx.sourcePath);
console.log(file)
const ko = await this.ctx.kObjectCreator.createFromTFileTyped(file);
const layout = this.ctx.layoutFactory.create(ko);
if (!layout) {
return;
}
const renderText = await layout.render(ko);
el.prepend(renderText);
}
});
} }
} }