All files / src/utils source-click.ts

100% Statements 20/20
100% Branches 19/19
100% Functions 4/4
100% Lines 20/20

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90              6x                                         9x       9x 5x           4x                                     21x 5x   16x 16x 6x   10x 3x   7x                 10x   1x 1x   2x 2x   7x 7x      
import type { App, Vault } from "obsidian";
import type { LilbeeClient } from "../api";
import type { Source } from "../types";
import { CONTENT_TYPE, isPdfContentType } from "../types";
import { SourcePreviewModal } from "../views/source-preview-modal";
 
/** Discriminator tags for `SourceClickAction`. */
export const SOURCE_ACTION = {
    VAULT_MARKDOWN: "vault-markdown",
    VAULT_NOTE: "vault-note",
    PREVIEW: "preview",
} as const;
 
/**
 * The resolved intent of clicking a Source chip or card. One of:
 * - `vault-markdown`: open the markdown file and scroll to a specific line.
 * - `vault-note`: open the vault file without deep-linking.
 * - `preview`: file is not in the vault, OR is a PDF — fetch via `/api/source`
 *   and show the source-preview modal so we can deep-link to a page via
 *   `<object data="...?page=N">` (Obsidian's PDF viewer doesn't honour the
 *   `#page=N` fragment in `openLinkText`).
 */
export type SourceClickAction =
    | { kind: typeof SOURCE_ACTION.VAULT_MARKDOWN; path: string; line: number }
    | { kind: typeof SOURCE_ACTION.VAULT_NOTE; path: string }
    | { kind: typeof SOURCE_ACTION.PREVIEW; source: Source };
 
function isMarkdownish(contentType: string): boolean {
    return contentType === CONTENT_TYPE.MARKDOWN || contentType === CONTENT_TYPE.HTML;
}
 
function vaultAction(source: Source, path: string): SourceClickAction {
    if (isMarkdownish(source.content_type) && source.line_start !== null) {
        return {
            kind: SOURCE_ACTION.VAULT_MARKDOWN,
            path,
            line: source.line_start,
        };
    }
    return { kind: SOURCE_ACTION.VAULT_NOTE, path };
}
 
/**
 * Resolve what should happen when the user clicks a source reference.
 *
 * PDFs always route to the preview modal — Obsidian's built-in PDF viewer
 * does not honour `#page=N` fragments passed through `openLinkText`, so we
 * use the modal's `<object data="…?page=N">` path that does. The modal
 * exposes an "Open in vault" button for users who want the file opened in
 * the main pane.
 *
 * For non-PDFs, prefer the server-supplied `vault_path`. If absent (older
 * server builds, or external-server mode), fall back to `source.source` —
 * sources ingested via a vault-native flow often match a vault-relative
 * path directly. Only fall through to the preview modal when no vault file
 * can be resolved.
 */
export function sourceClickAction(source: Source, vault: Vault): SourceClickAction {
    if (isPdfContentType(source.content_type)) {
        return { kind: SOURCE_ACTION.PREVIEW, source };
    }
    const vaultPath = source.vault_path;
    if (vaultPath && vault.getAbstractFileByPath(vaultPath)) {
        return vaultAction(source, vaultPath);
    }
    if (source.source && vault.getAbstractFileByPath(source.source)) {
        return vaultAction(source, source.source);
    }
    return { kind: SOURCE_ACTION.PREVIEW, source };
}
 
/**
 * Dispatch a resolved `SourceClickAction`. Opens the vault file with the
 * appropriate deep-link for markdown, or opens the preview modal for PDFs
 * and sources that only live on the server.
 */
export async function executeSourceClick(app: App, api: LilbeeClient, action: SourceClickAction): Promise<void> {
    switch (action.kind) {
        case SOURCE_ACTION.VAULT_MARKDOWN:
            void app.workspace.openLinkText(action.path, "", false, { eState: { line: action.line } });
            return;
        case SOURCE_ACTION.VAULT_NOTE:
            void app.workspace.openLinkText(action.path, "");
            return;
        case SOURCE_ACTION.PREVIEW:
            new SourcePreviewModal(app, api, action.source).open();
            return;
    }
}