Skip to content

Commit 061f95d

Browse files
authored
docs: fully document the code base (#28)
* refactor: move const definitions into a dedicated .const.ts file * docs: fix broken links * chore: remove recurrence rules until ready to implement * docs: fully document all classes * build: disable JSDoc lint checks in TypeScript files; rely on TSDoc instead * Apply suggestions from code review * chore: remove scheduled times until ready to implement * docs: enforce documentation on .const.ts files * docs: fix links and improve TSDoc contents * Apply suggestions from code review
1 parent 9ef82de commit 061f95d

21 files changed

+219
-159
lines changed

boundaries.config.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ export const ELEMENT_TYPE_RULES = [
3434
{ from: "*", allow: "shared" },
3535
{ from: "main", allow: [["lib", { lib: "obsidian" }]] },
3636
{ from: "(lib|lib:scope)", allow: [["lib", { lib: "${from.lib}" }]] },
37-
{ from: "const", disallow: "*" },
37+
{ from: "const", allow: "./*" },
3838

3939
...fromScopeAllowItself("model"),
4040
...fromScopeElementAllowTargetScopeElements("model", "index", [["model", "collection"]]),

eslint.config.js

-2
Original file line numberDiff line numberDiff line change
@@ -38,14 +38,12 @@ export default [
3838
files: ["src/**/*.{ts,tsx}"],
3939
plugins: {
4040
"@typescript-eslint": typescriptEslintPlugin,
41-
"jsdoc": eslintPluginJsdoc,
4241
"react-hooks": eslintPluginReactHooks,
4342
"tsdoc": eslintPluginTsdoc,
4443
},
4544
rules: {
4645
...typescriptEslintPlugin.configs.recommended.rules,
4746
...typescriptEslintPlugin.configs.strict.rules,
48-
...eslintPluginJsdoc.configs["flat/recommended-typescript-error"].rules,
4947
...eslintPluginReactHooks.configs.recommended.rules,
5048

5149
// https://tsdoc.org/pages/packages/eslint-plugin-tsdoc/

package-lock.json

+11
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@
7171
"typedoc": "^0.27.9",
7272
"typedoc-github-theme": "^0.2.1",
7373
"typedoc-plugin-coverage": "^3.4.1",
74+
"typedoc-plugin-dt-links": "^1.1.14",
7475
"typescript": "^5.8.2",
7576
"utility-types": "^3.11.0",
7677
"vite": "^6.2.0",

src/lib/obsidian-dataview/types.ts

+3
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@ import type { SMarkdownPage, STask } from "obsidian-dataview/lib/data-model/seri
55

66
export { getAPI, isPluginEnabled } from "obsidian-dataview";
77

8+
/** Metadata the obsidian-dataview plugin tracks from notes. */
89
export type DataviewMarkdownPage = SMarkdownPage;
910

11+
/** Metadata the obsidian-dataview plugin tracks from tasks. Extended to improve type information. */
1012
export interface DataviewMarkdownTask extends STask {
1113
created?: DateTime;
1214
due?: DateTime;
@@ -15,6 +17,7 @@ export interface DataviewMarkdownTask extends STask {
1517
scheduled?: DateTime;
1618
}
1719

20+
/** API the obsidian-dataview plugin exposes to plugin authors. Extended to improve type information. */
1821
export interface DataviewApi extends ActualDataviewApi {
1922
pages(query: string): DataArray<SMarkdownPage>;
2023
}

src/main.tsx

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import { Plugin } from "@/lib/obsidian/types";
22

3+
/** Entry point for the plugin. Obsidian depends on this class to define all lifecycle events and rendering behavior. */
34
export class Objo extends Plugin {
5+
/** Called by Obsidian to give the plugin an opportunity to "load" and hook into the ecosystem. */
46
override onload() {
5-
this.app.workspace.onLayoutReady(() => console.log("started"));
7+
this.app.workspace.onLayoutReady(() => console.log("loaded"));
68
}
79
}
810

src/model/collection/periodic-log.ts

+30
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,39 @@ import { parse } from "path";
44

55
import { Collection } from "./schema";
66

7+
/**
8+
* Represents a collection of files, each corresponding to unique {@link Interval} of time, kept inside the same folder.
9+
* Intended to handle Obsidian's built-in "Daily Log" plugin and the more-comprehensive "Periodic log" community plugin.
10+
*/
711
export class PeriodicLog extends Collection {
12+
/**
13+
* The folder containing all of the notes.
14+
* TODO: Is it worth supporting files organized into different folders?
15+
*/
816
public readonly folder: string;
17+
18+
/**
19+
* Luxon format used to parse dates from the file's name.
20+
* @see {@link https://moment.github.io/luxon/#/parsing?id=table-of-tokens}
21+
*/
922
public readonly dateFormat: string;
23+
24+
/**
25+
* Offset between the file's _parsed_ date and the corresponding {@link Interval}'s _start_ date. May be negative.
26+
*
27+
* @example a periodic sprint log using ISO weeks, which start on Monday, as its {@link dateFormat} when sprints
28+
* _actually_ begin on Thursdays.
29+
*/
1030
public readonly dateOffset: Duration<true>;
31+
32+
/**
33+
* The {@link Duration} of each file's corresponding {@link Interval}.
34+
*
35+
* @example a daily log's duration would be `{ days: 1 }`.
36+
*/
1137
public readonly intervalDuration: Duration<true>;
38+
39+
/** Luxon options used when parsing {@link DateTime}s from file names. */
1240
public readonly dateOptions: DateTimeOptions;
1341

1442
public constructor(
@@ -36,10 +64,12 @@ export class PeriodicLog extends Collection {
3664
this.dateOptions = dateOptions;
3765
}
3866

67+
/** {@inheritDoc model/collection/schema.Collection#includes} */
3968
public override includes(filePath: string): boolean {
4069
return this.getIntervalOf(filePath).isValid;
4170
}
4271

72+
/** {@inheritDoc model/collection/schema.Collection#getIntervalOf} */
4373
public override getIntervalOf(filePath: string): IntervalMaybeValid {
4474
const path = parse(filePath);
4575
if (path.dir !== this.folder) {

src/model/collection/schema.ts

+16-1
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,23 @@
11
import { Interval, IntervalMaybeValid } from "luxon";
22

3+
/**
4+
* Interface for Bullet Journal collections.
5+
*
6+
* Provides efficient functions for common operations required by views and other models.
7+
*/
38
export abstract class Collection {
9+
/**
10+
* @param filePath - the path to check.
11+
* @returns whether the file at the given path belongs to this collection.
12+
*/
413
public abstract includes(filePath: string): boolean;
5-
// TODO: Would this make more sense in its own interface?
14+
15+
/**
16+
* @param filePath - the path to check.
17+
* @returns the {@link Interval} corresponding to the file, otherwise an invalid interval.
18+
*
19+
* Used to accurately enable date-based queries on collections and to look up "neighboring" files.
20+
*/
621
public getIntervalOf(filePath: string): IntervalMaybeValid {
722
return Interval.invalid(`interval not implemented`, `"${filePath}" was ignored`);
823
}

src/model/index/schema.ts

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ export interface VaultIndex {
99
* Register a new {@link Collection} into the index.
1010
* All calls to this function will be treated as adding a brand new collection, regardless
1111
* of whether the input has already been registered in an earlier call.
12+
*
1213
* @param collection - the collection to register with this index.
1314
* @throws if the index cannot support the given collection.
1415
* @returns a {@link VaultCollectionIndex} that can be used to interact with the primary index.

src/model/task/constants.ts

-32
This file was deleted.

src/model/task/lib/__tests__/obsidian-tasks.ts

+1-9
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,7 @@ describe("Parsing task emojis", () => {
1313
[{ priority: 2 }, "🔼"],
1414
[{ priority: 4 }, "🔽"],
1515
[{ priority: 5 }, "⏬"],
16-
[{ recurrenceRule: "every day" }, "🔁 every day"],
1716
[{ id: "due3rd", dependsOn: new Set(["due1st", "due2nd"]) }, "🆔 due3rd ⛔ due1st, due2nd"],
18-
[{ times: { start: isoDateTime("09:00") } }, "09:00 do at 9am"],
19-
[{ times: { start: isoDateTime("09:00"), end: isoDateTime("10:00") } }, "09:00/10:00 do between 9am and 10am"],
2017
[{ dates: { cancelled: isoDateTime("2024-10-25") } }, "❌ 2024-10-25"],
2118
[{ dates: { created: isoDateTime("2024-10-26") } }, "➕ 2024-10-26"],
2219
[{ dates: { done: isoDateTime("2024-10-27") } }, "✅ 2024-10-27"],
@@ -35,14 +32,13 @@ describe("Parsing task emojis", () => {
3532
it("parses sequential fields", () => {
3633
expect(
3734
parseTaskEmojiFormat(`
38-
09:00/10:00 TODO! 🔺 🔁 every day 🆔 do3rd ⛔ do1st, do2nd
35+
TODO! 🔺 🆔 do3rd ⛔ do1st, do2nd
3936
❌ 2024-10-25 ➕ 2024-10-26 ✅ 2024-10-27
4037
📅 2024-10-28 ⏳ 2024-10-29 🛫 2024-10-30
4138
`),
4239
).toEqual({
4340
description: "TODO!",
4441
priority: 0,
45-
recurrenceRule: "every day",
4642
id: "do3rd",
4743
dependsOn: new Set(["do1st", "do2nd"]),
4844
dates: {
@@ -53,10 +49,6 @@ describe("Parsing task emojis", () => {
5349
scheduled: isoDateTime("2024-10-29"),
5450
start: isoDateTime("2024-10-30"),
5551
},
56-
times: {
57-
start: isoDateTime("09:00"),
58-
end: isoDateTime("10:00"),
59-
},
6052
});
6153
});
6254
});

src/model/task/lib/obsidian-dataview.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { Task } from "@/model/task/schema";
55

66
/**
77
* @param task - the {@link DataviewMarkdownTask} to extract metadata from.
8-
* @returns a {@link Task} with the extracted metadata.
8+
* @returns a {@link Task} with the extracted metadata and default vaules everywhere else.
99
*/
1010
export function adaptDataviewMarkdownTask(task: DataviewMarkdownTask): DeepPartial<Task> {
1111
return {

src/model/task/lib/obsidian-tasks.ts

+15-33
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,32 @@
11
import { escapeRegExp, has, keysIn, set } from "lodash";
2-
import { DateTime, Interval } from "luxon";
2+
import { DateTime } from "luxon";
33
import { DeepPartial, PickByValue } from "utility-types";
44

55
import { Task } from "@/model/task/schema";
6-
import { PathOf } from "@/util/type-utils";
6+
import { PathsOf } from "@/util/type-utils";
77

88
/**
9-
* Parses {@link Task} metadata from a real markdown task using the Obsidian Task plugin's syntax.
10-
* Specifically, the text is expected to find occurrences from the {@link SYMBOL_PATH_LOOKUP} and write to the
11-
* corresponding field using the proceeding text.
9+
* Parses {@link Task} metadata from a real markdown blob using Obsidian Task's emoji format.
10+
*
11+
* @see {@link https://publish.obsidian.md/tasks/Reference/Task+Formats/Tasks+Emoji+Format}
1212
* @example
13+
*
1314
* ```
14-
* "the text at the front is assumed to be a description. ❌ cancelled date ➕ created date ✅ completed date"
15-
* ( symbol & value )(symbol & value)( symbol & value )
15+
* ( symbol & value )
16+
* "the text at the front is assumed to be a description. ❌ cancelled date ➕ creation date ✅ completed date"
17+
* ( symbol & value ) ( symbol & value )
18+
*
19+
* { cancelled: "cancelled date", created: "creation date", done: "completed date" }
1620
* ```
17-
* @see {@link https://publish.obsidian.md/tasks/Reference/Task+Formats/Tasks+Emoji+Format}
21+
*
1822
* @param text - the text of the task without its' markdown text.
1923
* @returns a {@link Task} with the parsed metadata.
2024
*/
2125
export function parseTaskEmojiFormat(text: string): DeepPartial<Task> {
2226
const matchedSymbols = [...text.matchAll(SYMBOL_REG_EXP), /$/.exec(text) as RegExpExecArray];
2327
const textBeforeAllSymbols = text.slice(0, matchedSymbols[0].index);
2428

25-
const result: DeepPartial<Task> = { ...parseTaskHeader(textBeforeAllSymbols.trim()) };
29+
const result: DeepPartial<Task> = { description: textBeforeAllSymbols.trim() };
2630

2731
for (let i = 0; i <= matchedSymbols.length - 2; ++i) {
2832
const [execArray, nextExecArray] = matchedSymbols.slice(i, i + 2);
@@ -46,27 +50,6 @@ export function parseTaskEmojiFormat(text: string): DeepPartial<Task> {
4650
return result;
4751
}
4852

49-
/**
50-
* Parses {@link Task} metadata from a task's header.
51-
* @param headerText - the text that appears _before_ all of the symbols.
52-
* @returns the {@link Task} metadata fields parsed from the header; unparsed data will become {@link Task.description}.
53-
*/
54-
function parseTaskHeader(headerText: string): DeepPartial<Task> {
55-
const [isoString, isoStringSuffix] = headerText.split(/\s+/, 2);
56-
57-
const interval = Interval.fromISO(isoString);
58-
if (interval.isValid) {
59-
return { times: { start: interval.start, end: interval.end }, description: isoStringSuffix };
60-
}
61-
62-
const time = DateTime.fromISO(isoString);
63-
if (time.isValid) {
64-
return { times: { start: time }, description: isoStringSuffix };
65-
}
66-
67-
return { description: headerText };
68-
}
69-
7053
const SYMBOL_PATH_LOOKUP = {
7154
"❌": "dates.cancelled",
7255
"➕": "dates.created",
@@ -82,15 +65,14 @@ const SYMBOL_PATH_LOOKUP = {
8265
"🔼": "priority",
8366
"🔽": "priority",
8467
"⏬": "priority",
85-
"🔁": "recurrenceRule",
86-
} as const satisfies Record<string, PathOf<Task>>;
68+
} as const satisfies Record<string, PathsOf<Task>>;
8769

8870
const SYMBOL_PRIORITY_LOOKUP = {
8971
"🔺": 0,
9072
"⏫": 1,
9173
"🔼": 2,
9274
"🔽": 4,
9375
"⏬": 5,
94-
} as const satisfies Record<keyof PickByValue<typeof SYMBOL_PATH_LOOKUP, "priority">, number>;
76+
} as const satisfies { [K in keyof PickByValue<typeof SYMBOL_PATH_LOOKUP, "priority">]: number };
9577

9678
const SYMBOL_REG_EXP = new RegExp(keysIn(SYMBOL_PATH_LOOKUP).map(escapeRegExp).join("|"), "g");

src/model/task/schema.const.ts

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { DateTime } from "luxon";
2+
3+
import { Task, TaskSource, TaskStatus } from "./schema";
4+
5+
/** The default date used by {@link Task}s. Valid dates will always take precedence over invalid dates. */
6+
export const DEFAULT_DATETIME_VALUE: DateTime = DateTime.invalid("unspecified");
7+
8+
/** The default priority used by {@link Task}s. Different values will always take precedence over this value. */
9+
export const DEFAULT_PRIORITY_VALUE: Task["priority"] = 3;
10+
11+
/**
12+
* The default type used by {@link TaskSource} and {@link TaskStatus}.
13+
* Different values will always take precedence over this value.
14+
*/
15+
export const DEFAULT_TYPE_VALUE: TaskSource["type"] & TaskStatus["type"] = "UNKNOWN";
16+
17+
/** A strongly-typed {@link Task} with all-default values. */
18+
export const TASK_WITH_DEFAULT_VALUES: Task = {
19+
status: { type: DEFAULT_TYPE_VALUE },
20+
source: { type: DEFAULT_TYPE_VALUE },
21+
dates: {
22+
cancelled: DEFAULT_DATETIME_VALUE,
23+
created: DEFAULT_DATETIME_VALUE,
24+
done: DEFAULT_DATETIME_VALUE,
25+
due: DEFAULT_DATETIME_VALUE,
26+
scheduled: DEFAULT_DATETIME_VALUE,
27+
start: DEFAULT_DATETIME_VALUE,
28+
},
29+
description: "",
30+
priority: DEFAULT_PRIORITY_VALUE,
31+
tags: new Set(),
32+
id: "",
33+
dependsOn: new Set(),
34+
};

0 commit comments

Comments
 (0)