All files / src/utils source-click.ts

100% Statements 45/45
100% Branches 21/21
100% Functions 4/4
100% Lines 45/45

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 901x             1x 1x 1x 1x 1x                               9x 9x 9x   9x 9x 5x 5x 5x 5x 5x 5x 4x 4x                                 1x 21x 5x 5x 16x 21x 6x 6x 21x 3x 3x 7x 7x             10x 10x 10x 1x 1x 10x 2x 2x 10x 7x 7x 10x 10x  
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:
            app.workspace.openLinkText(action.path, "", false, { eState: { line: action.line } });
            return;
        case SOURCE_ACTION.VAULT_NOTE:
            app.workspace.openLinkText(action.path, "");
            return;
        case SOURCE_ACTION.PREVIEW:
            new SourcePreviewModal(app, api, action.source).open();
            return;
    }
}