/**
* Journal and search operations for Logseq.
* @module
*/
import type {
Page,
Block,
JournalPage,
GetRecentJournalsOptions,
BlockBacklinks,
PageLinks,
} from "../types.js";
import { LogseqApiError } from "../errors.js";
import { LogseqOperations } from "./base.js";
import { formatJournalName, parseLogseqDate, isoToJournalDay, getTodayIso } from "./helpers.js";
/**
* Gets or creates today's journal page.
*/
export async function getToday(ops: LogseqOperations): Promise<JournalPage> {
const now = new Date();
const journalName = formatJournalName(now);
const isoDate = getTodayIso();
let pageData = await ops.getPage(journalName);
if (!pageData) {
await ops.createPage(journalName);
pageData = await ops.getPage(journalName);
if (!pageData) {
throw new LogseqApiError("Failed to create today's journal page", "createPage");
}
}
return {
page: pageData.page,
date: isoDate,
content: ops.blocksToText(pageData.blocks),
};
}
/**
* Appends content to today's journal page.
*/
export async function appendToToday(ops: LogseqOperations, content: string): Promise<Block> {
const today = await getToday(ops);
return ops.createBlock({
pageName: today.page.name,
content,
});
}
/**
* Gets recent journal entries.
*/
export async function getRecentJournals(
ops: LogseqOperations,
options?: GetRecentJournalsOptions
): Promise<JournalPage[]> {
const days = options?.days ?? 7;
const includeContent = options?.includeContent ?? false;
const now = new Date();
const startDate = new Date();
startDate.setDate(now.getDate() - days + 1);
const startJournalDay = isoToJournalDay(
`${startDate.getFullYear()}-${String(startDate.getMonth() + 1).padStart(2, "0")}-${String(startDate.getDate()).padStart(2, "0")}`
);
const endJournalDay = isoToJournalDay(getTodayIso());
const query = `
[:find (pull ?p [*])
:where
[?p :block/journal? true]
[?p :block/journal-day ?d]
[(>= ?d ${startJournalDay})]
[(<= ?d ${endJournalDay})]]
`;
const result = await ops.query(query);
const journals: JournalPage[] = [];
for (const [pageData] of result as Array<[Record<string, unknown>]>) {
const journalDay = pageData["journal-day"] as number;
const isoDate = parseLogseqDate(journalDay) ?? "";
const pageName = (pageData.name as string) ?? (pageData.originalName as string) ?? "";
const page: Page = {
id: pageData.id as number,
uuid: pageData.uuid as string,
name: pageName,
originalName: (pageData.originalName as string) ?? pageName,
journal: true,
journalDay,
};
let content = "";
if (includeContent) {
const pageContent = await ops.getPageContent(pageName);
content = pageContent ?? "";
}
journals.push({ page, date: isoDate, content });
}
return journals.sort((a, b) => b.date.localeCompare(a.date));
}
/**
* Finds pages linked to and from a given page.
*/
export async function findRelatedPages(
ops: LogseqOperations,
pageName: string
): Promise<PageLinks> {
const normalizedName = pageName.toLowerCase();
const backlinksQuery = `
[:find ?name
:where
[?p :block/name "${normalizedName}"]
[?b :block/refs ?p]
[?b :block/page ?bp]
[?bp :block/name ?name]
[(not= ?name "${normalizedName}")]]
`;
const backlinksResult = await ops.query(backlinksQuery);
const backlinks = [
...new Set((backlinksResult as string[][]).map((r) => r[0]).filter((n): n is string => !!n)),
];
const forwardLinksQuery = `
[:find ?name
:where
[?p :block/name "${normalizedName}"]
[?b :block/page ?p]
[?b :block/refs ?r]
[?r :block/name ?name]
[(not= ?name "${normalizedName}")]]
`;
const forwardLinksResult = await ops.query(forwardLinksQuery);
const forwardLinks = [
...new Set((forwardLinksResult as string[][]).map((r) => r[0]).filter((n): n is string => !!n)),
];
return {
pageName,
backlinks,
forwardLinks,
};
}
/**
* Finds all blocks that reference a given block.
*/
export async function getBlockBacklinks(
ops: LogseqOperations,
uuid: string
): Promise<BlockBacklinks> {
const query = `
[:find (pull ?b [:block/uuid :block/content]) ?page-name
:where
[?target :block/uuid #uuid "${uuid}"]
[?b :block/refs ?target]
[?b :block/page ?p]
[?p :block/name ?page-name]]
`;
const result = await ops.query(query);
const backlinks: BlockBacklinks["backlinks"] = [];
for (const [blockData, pageName] of result as Array<[Record<string, unknown>, string]>) {
backlinks.push({
uuid: blockData.uuid as string,
content: blockData.content as string,
pageName,
});
}
return { uuid, backlinks };
}