Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 67 additions & 10 deletions biome.json
Original file line number Diff line number Diff line change
@@ -1,14 +1,5 @@
{
"$schema": "https://biomejs.dev/schemas/2.1.1/schema.json",
"files": {
"experimentalScannerIgnores": [
"**/node_modules/**",
"**/dist/**",
"**/build/**",
"**/.devenv/**",
"**/.direnv/**"
]
},
"$schema": "https://biomejs.dev/schemas/2.3.8/schema.json",
"formatter": {
"enabled": true,
"indentStyle": "space",
Expand All @@ -24,9 +15,75 @@
},
"a11y": {
"noStaticElementInteractions": "off"
},
"style": {
"noDefaultExport": "error",
"noExportedImports": "error",
"noInferrableTypes": "error",
"noMagicNumbers": "error",
"noNegationElse": "error",
"noNestedTernary": "error",
"noNonNullAssertion": "error",
"noParameterAssign": "error",
"noParameterProperties": "error",
"noShoutyConstants": "error",
"noUselessElse": "error",
"noYodaExpression": "error",
"useArrayLiterals": "error",
"useAsConstAssertion": "error",
"useBlockStatements": "error",
"useCollapsedElseIf": "error",
"useCollapsedIf": "error",
"useConsistentArrayType": {
"level": "error",
"options": {
"syntax": "shorthand"
}
},
"useConsistentCurlyBraces": "error",
"useConsistentObjectDefinitions": {
"level": "error",
"options": {
"syntax": "shorthand"
}
},
"useConst": "error",
"useDefaultParameterLast": "error",
"useDefaultSwitchClause": "error",
"useEnumInitializers": "error",
"useExplicitLengthCheck": "error",
"useForOf": "error",
"useFragmentSyntax": "error",
"useImportType": "error",
"useLiteralEnumMembers": "error",
"useNumberNamespace": "error",
"useNumericSeparators": "error",
"useObjectSpread": "error",
"useReactFunctionComponents": "error",
"useReadonlyClassProperties": "error",
"useSelfClosingElements": "error",
"useShorthandFunctionType": "error",
"useSingleVarDeclarator": "error",
"useTemplate": "error",
"useThrowNewError": "error",
"useThrowOnlyError": "error"
}
}
},
"overrides": [
{
"includes": [
"**/*.test.ts"
],
"linter": {
"rules": {
"style": {
"noMagicNumbers": "off"
}
}
}
}
],
"assist": {
"actions": {
"source": {
Expand Down
11 changes: 9 additions & 2 deletions plugin/src/api/domain/task.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,17 @@ export type Task = {
order: number;
};

export type Priority = 1 | 2 | 3 | 4;
export const Priorities = {
P4: 1,
P3: 2,
P2: 3,
P1: 4,
} as const;

export type Priority = (typeof Priorities)[keyof typeof Priorities];

export type CreateTaskParams = {
priority: number;
priority: Priority;
projectId: ProjectId;
description?: string;
sectionId?: SectionId;
Expand Down
21 changes: 20 additions & 1 deletion plugin/src/api/fetcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,25 @@ export interface WebFetcher {
fetch(params: RequestParams): Promise<WebResponse>;
}

export type StatusCode = number;

export const StatusCode = {
isError(code: StatusCode): boolean {
return code >= StatusCodes.BadRequest;
},
isServerError(code: StatusCode): boolean {
return code >= StatusCodes.InternalServerError;
},
} as const;

export const StatusCodes = {
OK: 200,
BadRequest: 400,
Unauthorized: 401,
Forbidden: 403,
InternalServerError: 500,
} as const;

export type RequestParams = {
url: string;
method: string;
Expand All @@ -12,7 +31,7 @@ export type RequestParams = {
};

export type WebResponse = {
statusCode: number;
statusCode: StatusCode;
body: string;
};

Expand Down
20 changes: 10 additions & 10 deletions plugin/src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,17 @@ import type { Project } from "@/api/domain/project";
import type { Section } from "@/api/domain/section";
import type { CreateTaskParams, Task, TaskId } from "@/api/domain/task";
import type { UserInfo } from "@/api/domain/user";
import type { RequestParams, WebFetcher, WebResponse } from "@/api/fetcher";
import debug from "@/log";
import { type RequestParams, StatusCode, type WebFetcher, type WebResponse } from "@/api/fetcher";
import { debug } from "@/log";

type PaginatedResponse<T> = {
results: T[];
nextCursor: string | null;
};

export class TodoistApiClient {
private token: string;
private fetcher: WebFetcher;
private readonly token: string;
private readonly fetcher: WebFetcher;

constructor(token: string, fetcher: WebFetcher) {
this.token = token;
Expand All @@ -26,14 +26,14 @@ export class TodoistApiClient {
public async getTasks(filter?: string): Promise<Task[]> {
if (filter !== undefined) {
return await this.doPaginated<Task>("/tasks/filter", { query: filter });
} else {
return await this.doPaginated<Task>("/tasks");
}

return await this.doPaginated<Task>("/tasks");
}

public async createTask(content: string, options?: CreateTaskParams): Promise<Task> {
const body = snakify({
content: content,
content,
...(options ?? {}),
});
const response = await this.do("/tasks", "POST", body);
Expand Down Expand Up @@ -87,7 +87,7 @@ export class TodoistApiClient {
private async do(path: string, method: string, json?: object): Promise<WebResponse> {
const params: RequestParams = {
url: `https://api.todoist.com/api/v1${path}`,
method: method,
method,
headers: {
Authorization: `Bearer ${this.token}`,
},
Expand All @@ -110,7 +110,7 @@ export class TodoistApiClient {
context: response,
});

if (response.statusCode >= 400) {
if (StatusCode.isError(response.statusCode)) {
throw new TodoistApiError(params, response);
}

Expand All @@ -119,7 +119,7 @@ export class TodoistApiClient {
}

export class TodoistApiError extends Error {
public statusCode: number;
public statusCode: StatusCode;

constructor(request: RequestParams, response: WebResponse) {
const message = `[${request.method}] ${request.url} returned '${response.statusCode}: ${response.body}`;
Expand Down
2 changes: 1 addition & 1 deletion plugin/src/commands/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
import { t } from "@/i18n";
import type { Translations } from "@/i18n/translation";
import type TodoistPlugin from "@/index";
import debug from "@/log";
import { debug } from "@/log";

export type MakeCommand = (
plugin: TodoistPlugin,
Expand Down
2 changes: 2 additions & 0 deletions plugin/src/data/deadline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ const formatDeadline = (info: DeadlineInfo): string => {
return i18n.lastWeekday(getFormatter("weekday").format(info.raw));
case "nextWeek":
return getFormatter("weekday").format(info.raw);
default:
break;
}

if (!info.isCurrentYear) {
Expand Down
4 changes: 4 additions & 0 deletions plugin/src/data/dueDate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,8 @@ const formatDate = (info: DateInfo): string => {
return i18n.lastWeekday(getFormatter("weekday").format(info.raw));
case "nextWeek":
return getFormatter("weekday").format(info.raw);
default:
break;
}

if (!info.isCurrentYear) {
Expand All @@ -194,6 +196,8 @@ const formatDueDateHeader = (due: DueDate): string => {
case "tomorrow":
parts.push(i18n.tomorrow);
break;
default:
break;
}

return parts.join(" ‧ ");
Expand Down
13 changes: 7 additions & 6 deletions plugin/src/data/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type { Project, ProjectId } from "@/api/domain/project";
import type { Section, SectionId } from "@/api/domain/section";
import type { Task as ApiTask, CreateTaskParams, TaskId } from "@/api/domain/task";
import type { UserInfo } from "@/api/domain/user";
import { StatusCode, StatusCodes } from "@/api/fetcher";
import { Repository, type RepositoryReader } from "@/data/repository";
import { SubscriptionManager, type UnsubscribeCallback } from "@/data/subscriptions";
import type { Task } from "@/data/task";
Expand Down Expand Up @@ -146,10 +147,10 @@ export class TodoistAdapter {
description: apiTask.description,

project: project ?? makeUnknownProject(apiTask.projectId),
section: section,
section,
parentId: apiTask.parentId ?? undefined,

labels: labels,
labels,
priority: apiTask.priority,

due: apiTask.due ?? undefined,
Expand Down Expand Up @@ -253,13 +254,13 @@ class Subscription {
kind: QueryErrorKind.Unknown,
};
if (error instanceof TodoistApiError) {
if (error.statusCode === 400) {
if (error.statusCode === StatusCodes.BadRequest) {
result.kind = QueryErrorKind.BadRequest;
} else if (error.statusCode === 401) {
} else if (error.statusCode === StatusCodes.Unauthorized) {
result.kind = QueryErrorKind.Unauthorized;
} else if (error.statusCode === 403) {
} else if (error.statusCode === StatusCodes.Forbidden) {
result.kind = QueryErrorKind.Forbidden;
} else if (error.statusCode > 500) {
} else if (StatusCode.isServerError(error.statusCode)) {
result.kind = QueryErrorKind.ServerError;
}
}
Expand Down
2 changes: 1 addition & 1 deletion plugin/src/data/subscriptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ export type UnsubscribeCallback = () => void;

export class SubscriptionManager<T> {
private readonly subscriptions: Map<SubscriptionId, T> = new Map();
private generator: Generator<SubscriptionId> = subscriptionIdGenerator();
private readonly generator: Generator<SubscriptionId> = subscriptionIdGenerator();

public subscribe(value: T): UnsubscribeCallback {
const id = this.generator.next().value;
Expand Down
2 changes: 1 addition & 1 deletion plugin/src/data/transformations/grouping.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export function groupBy(tasks: Task[], groupBy: GroupVariant): GroupedTasks[] {
case GroupVariant.Label:
return groupByLabel(tasks);
default:
throw Error(`Cannot group by ${groupBy}`);
throw new Error(`Cannot group by ${groupBy}`);
}
}

Expand Down
4 changes: 2 additions & 2 deletions plugin/src/data/transformations/relationships.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export function buildTaskTree(tasks: Task[]): TaskTree[] {
if (parent !== undefined) {
const child = mapping.get(task.id);
if (child === undefined) {
throw Error("Expected to find task in map");
throw new Error("Expected to find task in map");
}
parent.children.push(child);
}
Expand All @@ -31,7 +31,7 @@ export function buildTaskTree(tasks: Task[]): TaskTree[] {
return roots.map((id) => {
const tree = mapping.get(id);
if (tree === undefined) {
throw Error("Expected to find task in map");
throw new Error("Expected to find task in map");
}
return tree;
});
Expand Down
1 change: 1 addition & 0 deletions plugin/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { makeServices, type Services } from "@/services";
import { type Settings, useSettingsStore } from "@/settings";
import { SettingsTab } from "@/ui/settings";

// biome-ignore lint/style/noDefaultExport: We must use default export for Obsidian plugins
export default class TodoistPlugin extends Plugin {
public readonly services: Services;

Expand Down
5 changes: 5 additions & 0 deletions plugin/src/infra/time.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,8 @@ export const now = (): ZonedDateTime => {
export const timezone = (): string => {
return getLocalTimeZone();
};

const millisInSecond = 1000;
export const secondsToMillis = (seconds: number): number => {
return seconds * millisInSecond;
};
2 changes: 1 addition & 1 deletion plugin/src/log.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useSettingsStore } from "@/settings";

export default function debug(log: string | LogMessage) {
export function debug(log: string | LogMessage) {
if (!useSettingsStore.getState().debugLogging) {
return;
}
Expand Down
22 changes: 13 additions & 9 deletions plugin/src/query/injector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { createRoot, type Root } from "react-dom/client";
import { create, type StoreApi, type UseBoundStore } from "zustand";

import type TodoistPlugin from "@/index";
import debug from "@/log";
import { debug } from "@/log";
import { parseQuery } from "@/query/parser";
import { applyReplacements } from "@/query/replacements";
import {
Expand Down Expand Up @@ -95,15 +95,19 @@ class ReactRenderer<T extends {}> extends MarkdownRenderChild {

for (const mutation of mutations) {
for (const addedNode of mutation.addedNodes) {
if (addedNode instanceof HTMLElement) {
if (addedNode.classList.contains("edit-block-button")) {
addedNode.hide();
this.store.setState({
click: () => addedNode.click(),
});
return;
}
if (!(addedNode instanceof HTMLElement)) {
continue;
}

if (!addedNode.classList.contains("edit-block-button")) {
continue;
}

addedNode.hide();
this.store.setState({
click: () => addedNode.click(),
});
return;
}
}
});
Expand Down
2 changes: 1 addition & 1 deletion plugin/src/services/modals.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ class ReactModal<T extends {}> extends Modal {

const modal: ModalInfo = {
close: () => this.close(),
popoverContainerEl: popoverContainerEl,
popoverContainerEl,
};

if (opts.dontCloseOnExternalClick ?? false) {
Expand Down
Loading