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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
# Changelog

## Unreleased

- Show unsaved pages and blocks resolved from a Roam block reference in Manage search results.
2 changes: 1 addition & 1 deletion src/utils/quickSwitcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ import type {
QuickSwitcherCommandPaletteSettings,
QuickSwitcherTargetType,
} from "~/types/quickSwitcher";
import { BLOCK_REF_REGEX } from "roamjs-components/dom/constants";

const isRecord = (value: unknown): value is Record<string, unknown> =>
typeof value === "object" && value !== null;

const BLOCK_REF_REGEX = /\(\(([A-Za-z0-9_-]+)\)\)/;
export const DEFAULT_COMMAND_PALETTE_PREFIX = "QS: ";
const LEGACY_COMMAND_PALETTE_PREFIX = "Q S - ";

Expand Down
27 changes: 26 additions & 1 deletion src/utils/quickSwitcherEntries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,11 +90,17 @@ const getUidFromEntryInput = ({ value }: { value: string }): string => {
extractBlockRefUid({ value: normalizedValue }) ||
(isPageUrlInput({ entry: normalizedValue })
? parsePageUidFromUrl({ url: normalizedValue })
: normalizedValue) ||
: "") ||
Comment thread
mdroidian marked this conversation as resolved.
""
);
};

const getUidFromExactBlockRef = ({ value }: { value: string }): string => {
const normalizedValue = value.trim();
const uid = extractBlockRefUid({ value: normalizedValue });
return uid && normalizedValue === `((${uid}))` ? uid : "";
};

export const getSuggestionTargetKey = ({
suggestion,
}: {
Expand Down Expand Up @@ -245,6 +251,25 @@ export const searchEntries = async ({
savedTargetKeys: Set<string>;
searchApi?: RoamSearchApi;
}): Promise<QuickSwitcherEntrySuggestion[]> => {
const referencedUid = getUidFromExactBlockRef({ value: query });
if (referencedUid) {
try {
const referencedSuggestion = await resolveUidToSuggestion({
uid: referencedUid,
});
if (
referencedSuggestion &&
!savedTargetKeys.has(
getSuggestionTargetKey({ suggestion: referencedSuggestion }),
)
) {
return [referencedSuggestion];
}
} catch {
// Fall through to the existing text search when direct resolution fails.
}
}

const options: RoamSearchOptions = {
"hide-code-blocks": false,
limit: MAX_SEARCH_RESULTS,
Expand Down
217 changes: 217 additions & 0 deletions tests/quickSwitcher.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
createBookmarkFromSuggestion,
getBlockBreadcrumbsFromPull,
getSavedTargetKeys,
resolveEntryInput,
searchEntries,
} from "../src/utils/quickSwitcherEntries";

Expand Down Expand Up @@ -250,6 +251,222 @@ test("searches a bounded frontend result set and filters saved targets", async (
}
});

test("resolves an exact block reference to an unsaved Manage search result", async () => {
const originalWindow = globalThis.window;
Object.defineProperty(globalThis, "window", {
configurable: true,
value: {
location: {
href: "https://roamresearch.com/#/app/test-graph/daily-notes",
origin: "https://roamresearch.com",
},
roamAlphaAPI: {
data: {
backend: {
q: async (query: string): Promise<[string][]> =>
query.includes("?page :node/title ?title")
? [["Resolved project page"]]
: [],
},
pull: (): null => null,
},
},
},
writable: true,
});

try {
let textSearchCalled = false;
const suggestions = await searchEntries({
query: " ((acW-i9uMD)) ",
savedTargetKeys: new Set(),
searchApi: async () => {
textSearchCalled = true;
return [];
},
});

expect(textSearchCalled).toBe(false);
expect(suggestions).toEqual([
{
uid: "acW-i9uMD",
title: "Resolved project page",
targetType: "page",
url: "https://roamresearch.com/#/app/test-graph/page/acW-i9uMD",
},
]);
} finally {
Object.defineProperty(globalThis, "window", {
configurable: true,
value: originalWindow,
writable: true,
});
}
});

test("resolves an exact block reference to an unsaved block result", async () => {
const originalWindow = globalThis.window;
Object.defineProperty(globalThis, "window", {
configurable: true,
value: {
location: {
href: "https://roamresearch.com/#/app/test-graph/daily-notes",
origin: "https://roamresearch.com",
},
roamAlphaAPI: {
data: {
backend: {
q: async (query: string): Promise<[string][]> =>
query.includes("?block :block/string ?text")
? [["Follow up with the project team tomorrow"]]
: [],
},
pull: (): Record<string, unknown> => ({
":block/page": { ":node/title": "Projects" },
}),
},
},
},
writable: true,
});

try {
const suggestions = await searchEntries({
query: "((acW-i9uMD))",
savedTargetKeys: new Set(),
searchApi: async () => {
throw new Error("Text search should not run for a resolved block ref");
},
});

expect(suggestions).toEqual([
{
breadcrumbs: ["Projects"],
uid: "acW-i9uMD",
title: "Follow up with the project team tomorrow",
targetType: "block",
url: "https://roamresearch.com/#/app/test-graph/page/acW-i9uMD",
},
]);
} finally {
Object.defineProperty(globalThis, "window", {
configurable: true,
value: originalWindow,
writable: true,
});
}
});

test("preserves Manage text search for saved, unresolved, invalid, and bare UID inputs", async () => {
const originalWindow = globalThis.window;
let uidResolutionQueries = 0;
Object.defineProperty(globalThis, "window", {
configurable: true,
value: {
location: {
href: "https://roamresearch.com/#/app/test-graph/daily-notes",
origin: "https://roamresearch.com",
},
roamAlphaAPI: {
data: {
backend: {
q: async (query: string): Promise<[string][]> => {
uidResolutionQueries += 1;
if (
query.includes('"savedUid1"') &&
query.includes("?page :node/title ?title")
) {
return [["Saved page"]];
}
return [];
},
},
pull: (): null => null,
},
},
},
writable: true,
});

try {
let textSearches = 0;
const searchApi = async (): Promise<Record<string, unknown>[]> => {
textSearches += 1;
return [];
};
const savedSuggestions = await searchEntries({
query: "((savedUid1))",
savedTargetKeys: new Set(["page:savedUid1"]),
searchApi,
});
expect(savedSuggestions).toEqual([]);

await searchEntries({
query: "((missing01))",
savedTargetKeys: new Set(),
searchApi,
});
const queriesAfterUnresolvedRef = uidResolutionQueries;
await searchEntries({
query: "((short))",
savedTargetKeys: new Set(),
searchApi,
});
await searchEntries({
query: "acW-i9uMD",
savedTargetKeys: new Set(),
searchApi,
});

expect(textSearches).toBe(4);
expect(uidResolutionQueries).toBe(queriesAfterUnresolvedRef);
} finally {
Object.defineProperty(globalThis, "window", {
configurable: true,
value: originalWindow,
writable: true,
});
}
});

test("does not resolve a bare UID through the entry input UID path", async () => {
const originalWindow = globalThis.window;
const queries: string[] = [];
Object.defineProperty(globalThis, "window", {
configurable: true,
value: {
location: {
href: "https://roamresearch.com/#/app/test-graph/daily-notes",
origin: "https://roamresearch.com",
},
roamAlphaAPI: {
data: {
backend: {
q: async (query: string): Promise<[string][]> => {
queries.push(query);
return [];
},
},
pull: (): null => null,
},
},
},
writable: true,
});

try {
expect(await resolveEntryInput({ value: "acW-i9uMD" })).toBeNull();
expect(queries).toHaveLength(1);
expect(queries[0]).toContain('?page :node/title "acW-i9uMD"');
} finally {
Object.defineProperty(globalThis, "window", {
configurable: true,
value: originalWindow,
writable: true,
});
}
});

test("extracts block breadcrumbs from pulled block parents", () => {
expect(
getBlockBreadcrumbsFromPull({
Expand Down
Loading