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
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@
"@sqlite.org/sqlite-wasm": "3.53.4-build2",
"@tanstack/react-virtual": "3.14.11",
"@tasknotes/calendar": "0.1.10",
"@tasknotes/model": "0.3.0-rc.20",
"@tasknotes/model": "0.3.0-rc.21",
"codemirror": "^6.0.2",
"dexie": "4.4.4",
"firebase": "12.16.0",
Expand Down
10 changes: 5 additions & 5 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 7 additions & 7 deletions src/components/task-model-draft.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ import { useCallback, useSyncExternalStore } from "react";

import type { TaskCollectionConfiguration } from "../domain/task-configuration";
import type {
TaskCustomFieldEdit,
TaskPriorityEdit,
TaskStatusEdit,
} from "../domain/task-type-vocabulary";
TaskNotesPriorityEdit,
TaskNotesStatusEdit,
TaskNotesUserFieldEdit,
} from "@tasknotes/model/mdbase";

/**
* `id` names a row while it is edited. A saved entry's id is its stored
Expand All @@ -32,10 +32,10 @@ export interface TaskModelSettingsDraft {
string,
{ autoArchive: boolean; autoArchiveDelay: number }
>;
statuses: DraftEntry<TaskStatusEdit>[];
statuses: DraftEntry<TaskNotesStatusEdit>[];
/** Lowest first; a priority's weight is its position. */
priorities: DraftEntry<TaskPriorityEdit>[];
customFields: DraftEntry<TaskCustomFieldEdit>[];
priorities: DraftEntry<TaskNotesPriorityEdit>[];
customFields: DraftEntry<TaskNotesUserFieldEdit>[];
}

type DraftPatch = Partial<TaskModelSettingsDraft>;
Expand Down
52 changes: 32 additions & 20 deletions src/components/task-model-settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,13 @@ import {
StatusListEditor,
} from "./task-vocabulary-editor";
import {
TaskVocabularyError,
TaskVocabularyInUseError,
vocabularyProblem,
type TaskVocabularyPatch,
type TaskVocabularyProblem,
} from "../domain/task-type-vocabulary";
isTaskNotesVocabularyError,
isTaskNotesVocabularyInUseError,
taskNotesVocabularyProblem,
type TaskNotesVocabularyInUseError,
type TaskNotesVocabularyPatch,
type TaskNotesVocabularyProblem,
} from "@tasknotes/model/mdbase";
import {
useTaskModelDraft,
type TaskModelSettingsDraft,
Expand Down Expand Up @@ -157,19 +158,20 @@ export function TaskModelSettingsEditor() {
if (!writable || !dirty || saveState === "saving") return;
const vocabulary = vocabularyChanges(draft, original);
const invalid =
vocabularyProblem(vocabulary, {
taskNotesVocabularyProblem(vocabulary, {
status: draft.defaultStatus,
priority: draft.defaultPriority,
}) ??
(vocabulary.customFields?.add?.some(
(vocabulary.userFields?.add?.some(
({ displayName }) => !displayName.trim(),
)
? ({ kind: "field-label", key: "" } as const)
: null);
if (invalid) {
const invalidMessage = invalid && vocabularyProblemMessage(invalid);
if (invalidMessage) {
setSaveState("error");
setError("");
setProblem(vocabularyProblemMessage(invalid));
setProblem(invalidMessage);
// A missing name is the usual reason; put the keyboard on it.
(event.currentTarget as HTMLElement)
.querySelector<HTMLInputElement>(
Expand All @@ -183,7 +185,7 @@ export function TaskModelSettingsEditor() {
setProblem("");
try {
await updateTaskModelSettings({
...(Object.keys(vocabulary).length ? { vocabulary } : {}),
...vocabulary,
defaultStatus: draft.defaultStatus,
defaultPriority: draft.defaultPriority,
recurrence: {
Expand Down Expand Up @@ -217,10 +219,12 @@ export function TaskModelSettingsEditor() {
setSaveState("saved");
} catch (reason) {
setSaveState("error");
if (reason instanceof TaskVocabularyInUseError)
setProblem(vocabularyInUseMessage(reason, original));
else if (reason instanceof TaskVocabularyError)
setProblem(vocabularyProblemMessage(reason.problem));
const message = isTaskNotesVocabularyInUseError(reason)
? vocabularyInUseMessage(reason, original)
: isTaskNotesVocabularyError(reason)
? vocabularyProblemMessage(reason.problem)
: null;
if (message) setProblem(message);
else setError(reason instanceof Error ? reason.message : String(reason));
}
}
Expand Down Expand Up @@ -625,10 +629,10 @@ function SettingsGroup({
function vocabularyChanges(
draft: TaskModelSettingsDraft,
original: TaskModelSettingsDraft,
): TaskVocabularyPatch {
): TaskNotesVocabularyPatch {
const changed = (key: "statuses" | "priorities" | "customFields") =>
JSON.stringify(draft[key]) !== JSON.stringify(original[key]);
const patch: TaskVocabularyPatch = {};
const patch: TaskNotesVocabularyPatch = {};
if (changed("statuses"))
patch.statuses = draft.statuses.map(
({ value, label, color, isCompleted }) => ({
Expand All @@ -649,7 +653,7 @@ function vocabularyChanges(
original.customFields.map((field) => [field.id, field]),
);
const kept = new Set(draft.customFields.map(({ id }) => id));
patch.customFields = {
patch.userFields = {
add: draft.customFields
.filter(({ id }) => !before.has(id))
.map(({ key, displayName, type }) => ({ key, displayName, type })),
Expand All @@ -668,7 +672,13 @@ function vocabularyChanges(
return patch;
}

function vocabularyProblemMessage(problem: TaskVocabularyProblem): string {
/**
* What to tell someone about a problem they can fix in this form, or null for
* one this form cannot cause.
*/
function vocabularyProblemMessage(
problem: TaskNotesVocabularyProblem,
): string | null {
switch (problem.kind) {
case "empty-label":
return problem.list === "status"
Expand All @@ -689,11 +699,13 @@ function vocabularyProblemMessage(problem: TaskVocabularyProblem): string {
case "field-name":
case "field-taken":
return translate`“${problem.key}” can’t be used for a custom field. Try another name.`;
case "completed-and-skipped":
return null;
}
}

function vocabularyInUseMessage(
problem: TaskVocabularyInUseError,
problem: TaskNotesVocabularyInUseError,
original: TaskModelSettingsDraft,
): string {
const entries =
Expand Down
39 changes: 21 additions & 18 deletions src/components/task-vocabulary-editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ import { ArrowDown, ArrowUp, Plus, Trash2 } from "lucide-react";
import { useRef, type ReactNode } from "react";

import {
customFieldKey,
vocabularyValue,
type TaskCustomFieldEdit,
type TaskPriorityEdit,
type TaskStatusEdit,
} from "../domain/task-type-vocabulary";
taskNotesUserFieldKey,
taskNotesVocabularyValue,
type TaskNotesPriorityEdit,
type TaskNotesStatusEdit,
type TaskNotesUserFieldEdit,
} from "@tasknotes/model/mdbase";
import { TaskNotesSelect } from "./tasknotes-controls";

import type { DraftEntry } from "./task-model-draft";
Expand Down Expand Up @@ -110,7 +110,7 @@ function OptionRows<Entry extends Named>({
return;
}
// A new entry's stored value follows its name until saved.
const value = vocabularyValue(
const value = taskNotesVocabularyValue(
label,
entries
.filter((candidate) => candidate.id !== entry.id)
Expand Down Expand Up @@ -214,9 +214,9 @@ export function StatusListEditor({
onChange,
onDefault,
}: {
statuses: DraftEntry<TaskStatusEdit>[];
statuses: DraftEntry<TaskNotesStatusEdit>[];
defaultStatus: string;
onChange(statuses: DraftEntry<TaskStatusEdit>[]): void;
onChange(statuses: DraftEntry<TaskNotesStatusEdit>[]): void;
onDefault(value: string): void;
}) {
const { t } = useLingui();
Expand Down Expand Up @@ -256,7 +256,7 @@ export function StatusListEditor({
...statuses,
{
id: newId(),
value: vocabularyValue(
value: taskNotesVocabularyValue(
"",
statuses.map(({ value }) => value),
),
Expand All @@ -280,9 +280,9 @@ export function PriorityListEditor({
onChange,
onDefault,
}: {
priorities: DraftEntry<TaskPriorityEdit>[];
priorities: DraftEntry<TaskNotesPriorityEdit>[];
defaultPriority: string;
onChange(priorities: DraftEntry<TaskPriorityEdit>[]): void;
onChange(priorities: DraftEntry<TaskNotesPriorityEdit>[]): void;
onDefault(value: string): void;
}) {
const { t } = useLingui();
Expand All @@ -305,7 +305,7 @@ export function PriorityListEditor({
...priorities,
{
id: newId(),
value: vocabularyValue(
value: taskNotesVocabularyValue(
"",
priorities.map(({ value }) => value),
),
Expand All @@ -327,10 +327,10 @@ export function CustomFieldListEditor({
reservedKeys,
onChange,
}: {
fields: DraftEntry<TaskCustomFieldEdit>[];
fields: DraftEntry<TaskNotesUserFieldEdit>[];
/** Property names a new field may not take. */
reservedKeys: readonly string[];
onChange(fields: DraftEntry<TaskCustomFieldEdit>[]): void;
onChange(fields: DraftEntry<TaskNotesUserFieldEdit>[]): void;
}) {
const { t } = useLingui();
const root = useRef<HTMLDivElement>(null);
Expand All @@ -357,7 +357,7 @@ export function CustomFieldListEditor({
<ul className="vocabulary-list">
{fields.map((field) => {
const name = field.displayName.trim() || t`Field name`;
const change = (patch: Partial<TaskCustomFieldEdit>) =>
const change = (patch: Partial<TaskNotesUserFieldEdit>) =>
onChange(
fields.map((candidate) =>
candidate.id === field.id
Expand All @@ -378,7 +378,10 @@ export function CustomFieldListEditor({
isNew(field)
? {
displayName,
key: customFieldKey(displayName, taken(field.id)),
key: taskNotesUserFieldKey(
displayName,
taken(field.id),
),
}
: { displayName },
);
Expand Down Expand Up @@ -427,7 +430,7 @@ export function CustomFieldListEditor({
...fields,
{
id,
key: customFieldKey("", taken(id)),
key: taskNotesUserFieldKey("", taken(id)),
displayName: "",
type: "text",
},
Expand Down
19 changes: 5 additions & 14 deletions src/demo/demo-task-repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { parseFrontmatter } from "@tasknotes/model/frontmatter";
import {
buildTaskNotesMdbaseResources,
patchTaskNotesMdbaseTypeSettings,
taskNotesVocabularyInUse,
} from "@tasknotes/model/mdbase";
import {
buildTaskNotesSettingsPatch,
Expand All @@ -22,10 +23,6 @@ import { appendViewPage } from "../application/view-query-session";
import { materializeRollingWindow } from "../application/rolling-occurrences";
import { taskCompletion } from "../domain/task-completion";
import { runningTimers } from "../domain/running-timers";
import {
patchTaskTypeVocabulary,
vocabularyInUse,
} from "../domain/task-type-vocabulary";
import {
findOccurrenceParent,
occurrenceRecordId,
Expand Down Expand Up @@ -932,26 +929,20 @@ export class DemoTaskRepository implements TaskRepository {
patch,
this.model.configuration(),
);
const vocabulary = patch.vocabulary;
if (vocabulary) {
if (split.type.statuses || split.type.priorities) {
const current = this.model.configuration();
const inUse = vocabularyInUse(
const inUse = taskNotesVocabularyInUse(
this.tasks.values(),
{
statuses: current.statuses.map(({ value }) => value),
priorities: current.priorities.map(({ value }) => value),
},
vocabulary,
split.type,
);
if (inUse) throw inUse;
}
const modelDefinition = patchTaskNotesMdbaseTypeSettings(
vocabulary
? patchTaskTypeVocabulary(this.modelDefinition, vocabulary, {
status: split.type.defaultStatus,
priority: split.type.defaultPriority,
})
: this.modelDefinition,
this.modelDefinition,
split.type,
);
if (Object.keys(split.app).length) {
Expand Down
Loading
Loading