Skip to content

Commit

Permalink
auto-set tags and default journal
Browse files Browse the repository at this point in the history
When creating new documents, automatically set the tags to whatever the current search tags are, if any. Also when setting the journal, if no journal is selected (i.e. viewing all documents, the default), set the default journal. Add concept of default journal (stored in preferences) to the application, with some guardrails to ensure it always exists. Clean-up the journals page slightly (will be replacing it with a context menu in a future release, so very minor adjustments here).
  • Loading branch information
cloverich committed Jun 30, 2024
1 parent eb41e99 commit 0045fa7
Show file tree
Hide file tree
Showing 9 changed files with 193 additions and 115 deletions.
4 changes: 4 additions & 0 deletions src/components/icons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
AlignJustify,
AlignLeft,
AlignRight,
Archive,
Baseline,
Bug,
Bold,
Expand Down Expand Up @@ -56,6 +57,7 @@ import {
Search,
Settings,
Smile,
Star,
Strikethrough,
Subscript,
SunMedium,
Expand Down Expand Up @@ -170,6 +172,7 @@ export const Icons = {
alignJustify: AlignJustify,
alignLeft: AlignLeft,
alignRight: AlignRight,
archive: Archive,
arrowDown: ChevronDown,
bg: PaintBucket,
blockquote: Quote,
Expand Down Expand Up @@ -222,6 +225,7 @@ export const Icons = {
row: RectangleHorizontal,
search: Search,
settings: Settings,
star: Star,
strikethrough: Strikethrough,
subscript: Subscript,
superscript: Superscript,
Expand Down
91 changes: 55 additions & 36 deletions src/hooks/stores/journals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { observable, computed, toJS } from "mobx";
import { JournalResponse, IClient } from "../useClient";

export class JournalsStore {
private isLoaded: boolean = false;
@observable loading: boolean = true;
@observable saving: boolean = false;
@observable error: Error | null = null;
Expand All @@ -16,16 +15,38 @@ export class JournalsStore {
return this.journals.filter((j) => !!j.archivedAt);
}

@observable defaultJournalId: string;

constructor(
private client: IClient,
journals: JournalResponse[],
defaultJournalId: string,
) {
this.client = client;
this.journals = journals;
this.defaultJournalId = defaultJournalId;
}

static async create(client: IClient) {
const journals = await client.journals.list();
return new JournalsStore(client, journals);

// todo: This is more like application setup state; should probably have a
// global setup routine to track all this in one place.
// ensure at least one journal exists
if (journals.length === 0) {
journals.push(await client.journals.create({ name: "My Journal" }));
}

// ensure a default journal is set
let { DEFAULT_JOURNAL_ID: default_journal } =
await client.preferences.get();

if (!default_journal) {
await client.preferences.setDefaultJournal(journals[0].id);
default_journal = journals[0].id;
}

return new JournalsStore(client, journals, default_journal);
}

idForName = (name: string) => {
Expand All @@ -34,29 +55,29 @@ export class JournalsStore {
if (match) return match.id;
};

load = async () => {
if (this.isLoaded) return;
private async assertNotDefault(journalId: string) {
const { DEFAULT_JOURNAL_ID: default_journal } =
await this.client.preferences.get();

try {
this.journals = await this.client.journals.list();
} catch (err: any) {
this.error = err;
if (journalId === default_journal) {
throw new Error(
"Cannot archive / delete the default journal; set a different journal as default first.",
);
}

this.isLoaded = true;
this.loading = false;
};
}

remove = async (journalId: string) => {
this.saving = true;
try {
// todo: update this.journals
await this.assertNotDefault(journalId);
await this.client.journals.remove({ id: journalId });
this.journals = this.journals.filter((j) => j.id !== journalId);
} catch (err: any) {
this.error = err;
console.error("Error removing journal:", err);
throw err;
} finally {
this.saving = false;
}
this.saving = false;
};

create = async ({ name }: { name: string }) => {
Expand All @@ -69,9 +90,10 @@ export class JournalsStore {
this.journals.push(newJournal);
} catch (err: any) {
console.error(err);
this.error = err;
throw err;
} finally {
this.saving = false;
}
this.saving = false;
};

toggleArchive = async (journal: JournalResponse) => {
Expand All @@ -84,41 +106,38 @@ export class JournalsStore {
journal = toJS(journal);

try {
await this.assertNotDefault(journal.id);

if (journal.archivedAt) {
this.journals = await this.client.journals.unarchive(journal);
} else {
this.journals = await this.client.journals.archive(journal);
}
} catch (err: any) {
console.error(`Error toggling archive for journal ${journal.name}:`, err);
this.saving = false;

// NOTE: Otherwise this returns success, I'm unsure why the
// other calls are storing the error?
throw err;
} finally {
this.saving = false;
}

this.saving = false;
};

/**
* Determines the default journal to use when creating a new document.
*
* todo(test): When one or multiple journals are selected, returns the first
* todo(test): When no journals are selected, returns the first active journal
* todo(test): When archived journal selected, returns the selected (archived) journal
* Set the default journal; this is the journal that will be selected by
* default when creating a new document, if no journal is selected.
*/
defaultJournal = (selectedJournals: string[]) => {
const selectedId = this.journals.find((j) =>
selectedJournals.includes(j.name),
)?.id;

if (selectedId) {
return selectedId;
} else {
// todo: defaulting to first journal, but could use logic such as the last selected
// journal, etc, once that is in place
return this.active[0].id;
setDefault = async (journalId: string) => {
this.saving = true;
try {
await this.client.preferences.setDefaultJournal(journalId);
this.defaultJournalId = journalId;
} catch (err: any) {
this.error = err;
throw err;
} finally {
this.saving = false;
}
};
}
Expand Down
5 changes: 2 additions & 3 deletions src/hooks/useJournalsLoader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export const JournalsStoreContext = React.createContext<JournalsStore | null>(
);

/**
* Loads this journal store. After loading it should be passed down in context
* Loads the journal store. After loading it should be passed down in context
*/
export function useJournalsLoader() {
const [journals, setJournals] = React.useState<JournalResponse[]>();
Expand All @@ -24,10 +24,9 @@ export function useJournalsLoader() {
async function load() {
try {
const journalStore = await JournalsStore.create(client);
const journals = await client.journals.list();
if (!isEffectMounted) return;

setJournals(journals);
setJournals(journalStore.journals);
setJournalsStore(journalStore);
setLoading(false);
} catch (err: any) {
Expand Down
1 change: 1 addition & 0 deletions src/preload/client/documents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,7 @@ export class DocumentsClient {
tags,
}: SaveRequest): string => {
const id = uuidv7();

this.db
.prepare(
`INSERT INTO documents (id, journalId, content, title, createdAt, updatedAt) VALUES (:id, :journalId, :content, :title, :createdAt, :updatedAt)`,
Expand Down
12 changes: 0 additions & 12 deletions src/preload/client/journals.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,6 @@
import { uuidv7 } from "uuidv7";
import { Database } from "better-sqlite3";

interface IJournal {
// path to root folder
url: string;
// display name
name: string;

/**
* The duration of a single document in a journal.
*/
period: "day" | "week" | "month" | "year";
}

export interface JournalResponse {
id: string;
name: string;
Expand Down
5 changes: 5 additions & 0 deletions src/preload/client/preferences.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { ipcRenderer } from "electron";
export interface Preferences {
DATABASE_URL: string;
PREFERENCES_FILE: string;
DEFAULT_JOURNAL_ID: string;
}

export type IPreferencesClient = PreferencesClient;
Expand All @@ -25,4 +26,8 @@ export class PreferencesClient {
openDialogUserFiles = () => {
ipcRenderer.send("select-user-files-dir");
};

setDefaultJournal = async (journalId: string) => {
this.settings.set("DEFAULT_JOURNAL_ID", journalId);
};
}
29 changes: 27 additions & 2 deletions src/views/create/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,40 @@ function useCreateDocument() {
const isMounted = useIsMounted();
const [error, setError] = useState<Error | null>(null);

/**
* Determines the default journal to use when creating a new document.
*
* todo(test): When one or multiple journals are selected, returns the first
* todo(test): When no journals are selected, returns the first active journal
* todo(test): When archived journal selected, returns the selected (archived) journal
*/
function defaultJournal(selectedJournals: string[]) {
const selectedId = journalsStore.journals.find((j) =>
selectedJournals.includes(j.name),
)?.id;

if (selectedId) {
return selectedId;
} else {
const defaultId = journalsStore.defaultJournalId;
if (defaultId) return defaultId;

console.error(
"No default journal set, using first active journal; set a default journal in preferences",
);
return journalsStore.active[0].id;
}
}

useEffect(() => {
(async function () {
if (!isMounted) return;

try {
const document = await client.documents.save({
content: "",
journalId: journalsStore.defaultJournal(searchStore.selectedJournals),
tags: [], // todo: tagsStore.defaultTags;
journalId: defaultJournal(searchStore.selectedJournals),
tags: searchStore.selectedTags,
});

if (!isMounted) return;
Expand Down
21 changes: 17 additions & 4 deletions src/views/documents/SearchStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ export class SearchStore {
};

/**
* Execute a search with the current tokens.
* Execute a documents search with the current tokens.
*
* @param resetPagination - By default execute a fresh search. When paginating,
* we don't want to reset the pagination state.
Expand Down Expand Up @@ -239,11 +239,24 @@ export class SearchStore {
}
};

/**
* Return the selected journals from the search tokens, if any
*/
@computed get selectedJournals(): string[] {
// Grab the journal names from the tokens
// todo: Typescript doesn't know when I filter to type === 'in' its InTokens
return (
this._tokens
.filter((t) => t.type === "in")
// todo: Typescript doesn't know when I filter to type === 'in' its InTokens
.map((t) => t.value) as string[]
);
}

/**
* Return the selected tags from the search tokens, if any
*/
@computed get selectedTags(): string[] {
return this._tokens
.filter((t) => t.type === "in")
.filter((t) => t.type === "tag")
.map((t) => t.value) as string[];
}

Expand Down
Loading

0 comments on commit 0045fa7

Please sign in to comment.