@ncukondo/reference-manager 0.13.4 → 0.13.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunks/{action-menu-De64T89o.js → action-menu-svBJmZdi.js} +2 -2
- package/dist/chunks/{action-menu-De64T89o.js.map → action-menu-svBJmZdi.js.map} +1 -1
- package/dist/chunks/{index-DNGailHu.js → index-CaAOawzv.js} +4 -4
- package/dist/chunks/{index-DNGailHu.js.map → index-CaAOawzv.js.map} +1 -1
- package/dist/chunks/{search-prompt-RtHDJFgL.js → search-prompt-Bg9usf8f.js} +32 -8
- package/dist/chunks/search-prompt-Bg9usf8f.js.map +1 -0
- package/dist/cli.js +1 -1
- package/dist/features/interactive/search-prompt.d.ts +11 -0
- package/dist/features/interactive/search-prompt.d.ts.map +1 -1
- package/package.json +1 -1
- package/dist/chunks/search-prompt-RtHDJFgL.js.map +0 -1
|
@@ -114,6 +114,17 @@ function parseSelectedValues(values) {
|
|
|
114
114
|
function getTerminalWidth() {
|
|
115
115
|
return process.stdout.columns ?? 80;
|
|
116
116
|
}
|
|
117
|
+
function getTerminalHeight() {
|
|
118
|
+
return process.stdout.rows ?? 24;
|
|
119
|
+
}
|
|
120
|
+
function calculateEffectiveLimit(configLimit) {
|
|
121
|
+
const terminalHeight = getTerminalHeight();
|
|
122
|
+
const reservedLines = 5;
|
|
123
|
+
const linesPerItem = 3;
|
|
124
|
+
const availableLines = terminalHeight - reservedLines;
|
|
125
|
+
const maxVisibleChoices = Math.max(1, Math.floor(availableLines / linesPerItem));
|
|
126
|
+
return configLimit > 0 ? Math.min(configLimit, maxVisibleChoices) : maxVisibleChoices;
|
|
127
|
+
}
|
|
117
128
|
function collectEnabledIds(choices) {
|
|
118
129
|
const enabledIds = /* @__PURE__ */ new Set();
|
|
119
130
|
for (const choice of choices) {
|
|
@@ -140,9 +151,10 @@ function restoreEnabledState(choices, enabledIds) {
|
|
|
140
151
|
}
|
|
141
152
|
async function runSearchPrompt(allReferences, searchFn, config, initialQuery = "") {
|
|
142
153
|
const enquirer = await import("enquirer");
|
|
143
|
-
const
|
|
154
|
+
const BaseAutoComplete = enquirer.default.AutoComplete;
|
|
144
155
|
const terminalWidth = getTerminalWidth();
|
|
145
|
-
const
|
|
156
|
+
const effectiveLimit = calculateEffectiveLimit(config.limit);
|
|
157
|
+
const initialResults = initialQuery ? searchFn(initialQuery).slice(0, effectiveLimit) : allReferences.slice(0, effectiveLimit).map((ref) => ({
|
|
146
158
|
reference: ref,
|
|
147
159
|
overallStrength: "exact",
|
|
148
160
|
tokenMatches: [],
|
|
@@ -156,15 +168,16 @@ async function runSearchPrompt(allReferences, searchFn, config, initialQuery = "
|
|
|
156
168
|
initial: initialQuery,
|
|
157
169
|
choices: initialChoices,
|
|
158
170
|
multiple: true,
|
|
159
|
-
limit:
|
|
171
|
+
limit: effectiveLimit,
|
|
172
|
+
footer: "(Tab: select, Enter: confirm)",
|
|
160
173
|
suggest: (input, choices) => {
|
|
161
174
|
if (input === lastQuery) {
|
|
162
175
|
return choices;
|
|
163
176
|
}
|
|
164
177
|
lastQuery = input;
|
|
165
178
|
const enabledIds = collectEnabledIds(choices);
|
|
166
|
-
const newChoices = input.trim() ? createChoices(searchFn(input).slice(0,
|
|
167
|
-
allReferences.slice(0,
|
|
179
|
+
const newChoices = input.trim() ? createChoices(searchFn(input).slice(0, effectiveLimit), terminalWidth) : createChoices(
|
|
180
|
+
allReferences.slice(0, effectiveLimit).map((ref) => ({
|
|
168
181
|
reference: ref,
|
|
169
182
|
overallStrength: "exact",
|
|
170
183
|
tokenMatches: [],
|
|
@@ -177,8 +190,17 @@ async function runSearchPrompt(allReferences, searchFn, config, initialQuery = "
|
|
|
177
190
|
}
|
|
178
191
|
};
|
|
179
192
|
try {
|
|
180
|
-
const prompt = new
|
|
181
|
-
const
|
|
193
|
+
const prompt = new BaseAutoComplete(promptOptions);
|
|
194
|
+
const promptInstance = prompt;
|
|
195
|
+
promptInstance.space = function() {
|
|
196
|
+
return this.append(" ");
|
|
197
|
+
};
|
|
198
|
+
const promptWithRender = promptInstance;
|
|
199
|
+
promptWithRender.next = function() {
|
|
200
|
+
promptInstance.toggle(promptInstance.focused);
|
|
201
|
+
this.render();
|
|
202
|
+
};
|
|
203
|
+
const result = await promptInstance.run();
|
|
182
204
|
const promptAny = prompt;
|
|
183
205
|
let valuesToParse = result;
|
|
184
206
|
if (Array.isArray(result) && result.length === 0 && promptAny.focused?.enabled && promptAny.focused?.name) {
|
|
@@ -200,9 +222,11 @@ async function runSearchPrompt(allReferences, searchFn, config, initialQuery = "
|
|
|
200
222
|
}
|
|
201
223
|
}
|
|
202
224
|
export {
|
|
225
|
+
calculateEffectiveLimit,
|
|
203
226
|
createChoices,
|
|
227
|
+
getTerminalHeight,
|
|
204
228
|
getTerminalWidth,
|
|
205
229
|
parseSelectedValues,
|
|
206
230
|
runSearchPrompt
|
|
207
231
|
};
|
|
208
|
-
//# sourceMappingURL=search-prompt-
|
|
232
|
+
//# sourceMappingURL=search-prompt-Bg9usf8f.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"search-prompt-Bg9usf8f.js","sources":["../../src/features/interactive/format.ts","../../src/features/interactive/search-prompt.ts"],"sourcesContent":["/**\n * Display format functions for interactive search\n */\n\nimport type { CslItem } from \"../../core/csl-json/types.js\";\n\n/**\n * CSL name type (author structure)\n */\ntype CslName = NonNullable<CslItem[\"author\"]>[number];\n\n/**\n * Format a single author name\n * - Personal: \"Smith, J.\" (family + initial of given)\n * - Institutional: \"World Health Organization\" (literal)\n */\nfunction formatSingleAuthor(author: CslName): string {\n if (author.literal) {\n return author.literal;\n }\n if (author.family) {\n if (author.given) {\n const initial = author.given.charAt(0).toUpperCase();\n return `${author.family}, ${initial}.`;\n }\n return author.family;\n }\n return \"\";\n}\n\n/**\n * Format author list for display\n * - Single author: \"Smith, J.\"\n * - Two authors: \"Smith, J., & Doe, A.\"\n * - Three authors: \"Smith, J., Doe, A., & Johnson, B.\"\n * - More than three: \"Smith, J., et al.\"\n *\n * @param authors - Array of CSL author objects\n * @returns Formatted author string\n */\nexport function formatAuthors(authors: CslName[] | undefined): string {\n if (!authors || authors.length === 0) {\n return \"\";\n }\n\n if (authors.length > 3) {\n const first = authors[0];\n if (!first) {\n return \"\";\n }\n return `${formatSingleAuthor(first)}, et al.`;\n }\n\n const formatted = authors.map(formatSingleAuthor);\n if (formatted.length === 1) {\n return formatted[0] ?? \"\";\n }\n\n // Join all but last with \", \" and append \"& \" + last\n const allButLast = formatted.slice(0, -1).join(\", \");\n const last = formatted[formatted.length - 1] ?? \"\";\n return `${allButLast}, & ${last}`;\n}\n\n/**\n * Truncate title to fit terminal width\n *\n * @param title - Title string\n * @param maxWidth - Maximum display width\n * @returns Truncated title with ellipsis if needed\n */\nexport function formatTitle(title: string | undefined, maxWidth: number): string {\n if (!title) {\n return \"\";\n }\n\n if (title.length <= maxWidth) {\n return title;\n }\n\n // Truncate and add ellipsis, keeping total length at maxWidth\n return `${title.slice(0, maxWidth - 3)}...`;\n}\n\n/**\n * Format identifiers (DOI, PMID, PMCID, ISBN) for display\n *\n * @param item - CSL item\n * @returns Formatted identifier string (e.g., \"DOI: 10.1000/example | PMID: 12345678\")\n */\nexport function formatIdentifiers(item: CslItem): string {\n const identifiers: string[] = [];\n\n if (item.DOI) {\n identifiers.push(`DOI: ${item.DOI}`);\n }\n if (item.PMID) {\n identifiers.push(`PMID: ${item.PMID}`);\n }\n if (item.PMCID) {\n identifiers.push(`PMCID: ${item.PMCID}`);\n }\n if (item.ISBN) {\n identifiers.push(`ISBN: ${item.ISBN}`);\n }\n\n return identifiers.join(\" | \");\n}\n\n/**\n * Extract year from CSL item\n */\nfunction extractYear(item: CslItem): number | undefined {\n const dateParts = item.issued?.[\"date-parts\"];\n if (!dateParts || dateParts.length === 0) {\n return undefined;\n }\n const firstDatePart = dateParts[0];\n if (!firstDatePart || firstDatePart.length === 0) {\n return undefined;\n }\n return firstDatePart[0];\n}\n\n/**\n * Compose a complete search result line\n *\n * Format:\n * ```\n * [1] Smith, J., & Doe, A. (2020)\n * Machine learning in medicine: A comprehensive review\n * DOI: 10.1000/example | PMID: 12345678\n * ```\n *\n * @param item - CSL item\n * @param index - Display index (1-based)\n * @param terminalWidth - Terminal width for title truncation\n * @returns Multi-line formatted string\n */\nexport function formatSearchResult(item: CslItem, index: number, terminalWidth: number): string {\n const lines: string[] = [];\n\n // Line 1: [index] Authors (year)\n const authors = formatAuthors(item.author);\n const year = extractYear(item);\n const yearPart = year !== undefined ? ` (${year})` : \"\";\n const line1 = `[${index}] ${authors}${yearPart}`;\n lines.push(line1);\n\n // Line 2: Title (indented, truncated)\n const indent = \" \";\n const titleMaxWidth = terminalWidth - indent.length;\n const title = formatTitle(item.title, titleMaxWidth);\n if (title) {\n lines.push(`${indent}${title}`);\n }\n\n // Line 3: Identifiers (indented)\n const identifiers = formatIdentifiers(item);\n if (identifiers) {\n lines.push(`${indent}${identifiers}`);\n }\n\n return lines.join(\"\\n\");\n}\n","/**\n * Interactive search prompt using Enquirer's AutoComplete\n *\n * Provides real-time incremental search with multiple selection support.\n */\n\nimport type { CslItem } from \"../../core/csl-json/types.js\";\nimport type { SearchResult } from \"../search/types.js\";\nimport type { AutoCompleteChoice } from \"./enquirer.js\";\nimport { formatSearchResult } from \"./format.js\";\n\n/**\n * Configuration for the search prompt\n */\nexport interface SearchPromptConfig {\n /** Maximum number of results to display */\n limit: number;\n /** Debounce delay in milliseconds */\n debounceMs: number;\n}\n\n/**\n * Search function type for filtering references\n */\nexport type SearchFunction = (query: string) => SearchResult[];\n\n/**\n * Result from the search prompt\n */\nexport interface SearchPromptResult {\n /** Selected references */\n selected: CslItem[];\n /** Whether the prompt was cancelled */\n cancelled: boolean;\n}\n\n/**\n * Maps internal choice value to CslItem\n */\ninterface ChoiceData {\n index: number;\n item: CslItem;\n}\n\n/**\n * Creates choices from search results\n */\nexport function createChoices(\n results: SearchResult[],\n terminalWidth: number\n): AutoCompleteChoice[] {\n return results.map((result, index) => {\n const displayIndex = index + 1;\n const formattedText = formatSearchResult(result.reference, displayIndex, terminalWidth);\n\n // Enquirer returns the 'name' property on selection when 'value' is not defined\n // So we store the JSON data in 'name' and use 'message' for display\n return {\n name: JSON.stringify({\n index,\n item: result.reference,\n } satisfies ChoiceData),\n message: formattedText,\n };\n });\n}\n\n/**\n * Parses selected values back to CslItems\n */\nexport function parseSelectedValues(values: string | string[]): CslItem[] {\n const valueArray = Array.isArray(values) ? values : [values];\n const items: CslItem[] = [];\n\n for (const value of valueArray) {\n if (!value) continue;\n try {\n const data = JSON.parse(value) as ChoiceData;\n items.push(data.item);\n } catch {\n // If parsing fails, the value might be just the id (name)\n // In this case, we can't recover the full item\n // This shouldn't happen in normal operation\n }\n }\n\n return items;\n}\n\n/**\n * Gets terminal width, falling back to 80 if not available\n */\nexport function getTerminalWidth(): number {\n return process.stdout.columns ?? 80;\n}\n\n/**\n * Gets terminal height, falling back to 24 if not available\n */\nexport function getTerminalHeight(): number {\n return process.stdout.rows ?? 24;\n}\n\n/**\n * Calculates the effective limit for the autocomplete list\n * based on terminal height to prevent input field from being hidden.\n * Reserves space for: prompt header (1), input line (1), footer hint (1), and padding (2)\n * Each item displays up to 3 lines (author/year, title, identifiers)\n */\nexport function calculateEffectiveLimit(configLimit: number): number {\n const terminalHeight = getTerminalHeight();\n const reservedLines = 5; // header + input + footer hint + padding\n const linesPerItem = 3; // each search result shows up to 3 lines\n const availableLines = terminalHeight - reservedLines;\n const maxVisibleChoices = Math.max(1, Math.floor(availableLines / linesPerItem));\n return configLimit > 0 ? Math.min(configLimit, maxVisibleChoices) : maxVisibleChoices;\n}\n\n/**\n * Collects enabled (selected) item IDs from choices\n */\nfunction collectEnabledIds(choices: AutoCompleteChoice[]): Set<string> {\n const enabledIds = new Set<string>();\n for (const choice of choices) {\n if ((choice as { enabled?: boolean }).enabled) {\n try {\n const data = JSON.parse(choice.name) as ChoiceData;\n enabledIds.add(data.item.id);\n } catch {\n // Ignore parse errors\n }\n }\n }\n return enabledIds;\n}\n\n/**\n * Restores enabled state for choices matching the given IDs\n */\nfunction restoreEnabledState(choices: AutoCompleteChoice[], enabledIds: Set<string>): void {\n for (const choice of choices) {\n try {\n const data = JSON.parse(choice.name) as ChoiceData;\n if (enabledIds.has(data.item.id)) {\n (choice as { enabled?: boolean }).enabled = true;\n }\n } catch {\n // Ignore parse errors\n }\n }\n}\n\n/**\n * Creates and runs an interactive search prompt\n */\nexport async function runSearchPrompt(\n allReferences: CslItem[],\n searchFn: SearchFunction,\n config: SearchPromptConfig,\n initialQuery = \"\"\n): Promise<SearchPromptResult> {\n // Dynamic import to allow mocking in tests\n // enquirer is a CommonJS module, so we must use default import\n const enquirer = await import(\"enquirer\");\n const BaseAutoComplete = (enquirer.default as unknown as Record<string, unknown>)\n .AutoComplete as new (\n options: Record<string, unknown>\n ) => {\n run(): Promise<string | string[]>;\n };\n\n const terminalWidth = getTerminalWidth();\n // Calculate effective limit based on terminal height\n const effectiveLimit = calculateEffectiveLimit(config.limit);\n\n // Create initial choices from all references (limited)\n const initialResults: SearchResult[] = initialQuery\n ? searchFn(initialQuery).slice(0, effectiveLimit)\n : allReferences.slice(0, effectiveLimit).map((ref) => ({\n reference: ref,\n overallStrength: \"exact\" as const,\n tokenMatches: [],\n score: 0,\n }));\n\n const initialChoices = createChoices(initialResults, terminalWidth);\n\n // Track last search query to avoid redundant searches\n let lastQuery = initialQuery;\n\n const promptOptions = {\n name: \"references\",\n message: \"Search references\",\n initial: initialQuery,\n choices: initialChoices,\n multiple: true,\n limit: effectiveLimit,\n footer: \"(Tab: select, Enter: confirm)\",\n suggest: (input: string, choices: AutoCompleteChoice[]) => {\n // If input hasn't changed, return current choices\n if (input === lastQuery) {\n return choices;\n }\n lastQuery = input;\n\n // Collect enabled (selected) item IDs from current choices\n const enabledIds = collectEnabledIds(choices);\n\n // Create new choices based on input\n const newChoices = input.trim()\n ? createChoices(searchFn(input).slice(0, effectiveLimit), terminalWidth)\n : createChoices(\n allReferences.slice(0, effectiveLimit).map((ref) => ({\n reference: ref,\n overallStrength: \"exact\" as const,\n tokenMatches: [],\n score: 0,\n })),\n terminalWidth\n );\n\n // Restore enabled state for matching items\n restoreEnabledState(newChoices, enabledIds);\n\n return newChoices;\n },\n };\n\n try {\n const prompt = new BaseAutoComplete(promptOptions);\n\n // Override key handlers:\n // - Space: append character instead of toggling selection\n // - Tab: toggle selection (like space does in default multiple mode)\n const promptInstance = prompt as unknown as {\n space: () => void;\n dispatch: (s: string, key: { name: string }) => Promise<void>;\n append: (ch: string) => void;\n toggle: (item: unknown) => void;\n focused: unknown;\n run(): Promise<string | string[]>;\n };\n\n promptInstance.space = function () {\n return this.append(\" \");\n };\n\n // Override next() which is called on Tab key press\n // Make it toggle selection only (no movement, use arrow keys to move)\n const promptWithRender = promptInstance as unknown as {\n next: () => void;\n render: () => void;\n };\n promptWithRender.next = function () {\n promptInstance.toggle(promptInstance.focused);\n this.render();\n };\n\n const result = await promptInstance.run();\n\n // Workaround for Enquirer bug: focused item with enabled=true may not be in result\n const promptAny = prompt as unknown as {\n focused?: { name: string; enabled?: boolean };\n selected?: Array<{ name: string }>;\n };\n\n let valuesToParse: string | string[] = result;\n\n // If result is empty but focused item is enabled, use focused item\n if (\n Array.isArray(result) &&\n result.length === 0 &&\n promptAny.focused?.enabled &&\n promptAny.focused?.name\n ) {\n valuesToParse = [promptAny.focused.name];\n }\n\n // Handle result\n const selected = parseSelectedValues(valuesToParse);\n\n return {\n selected,\n cancelled: false,\n };\n } catch (error) {\n // Enquirer throws an empty string when cancelled\n if (error === \"\" || (error instanceof Error && error.message === \"\")) {\n return {\n selected: [],\n cancelled: true,\n };\n }\n throw error;\n }\n}\n"],"names":[],"mappings":"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"}
|
package/dist/cli.js
CHANGED
|
@@ -40,6 +40,17 @@ export declare function parseSelectedValues(values: string | string[]): CslItem[
|
|
|
40
40
|
* Gets terminal width, falling back to 80 if not available
|
|
41
41
|
*/
|
|
42
42
|
export declare function getTerminalWidth(): number;
|
|
43
|
+
/**
|
|
44
|
+
* Gets terminal height, falling back to 24 if not available
|
|
45
|
+
*/
|
|
46
|
+
export declare function getTerminalHeight(): number;
|
|
47
|
+
/**
|
|
48
|
+
* Calculates the effective limit for the autocomplete list
|
|
49
|
+
* based on terminal height to prevent input field from being hidden.
|
|
50
|
+
* Reserves space for: prompt header (1), input line (1), footer hint (1), and padding (2)
|
|
51
|
+
* Each item displays up to 3 lines (author/year, title, identifiers)
|
|
52
|
+
*/
|
|
53
|
+
export declare function calculateEffectiveLimit(configLimit: number): number;
|
|
43
54
|
/**
|
|
44
55
|
* Creates and runs an interactive search prompt
|
|
45
56
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"search-prompt.d.ts","sourceRoot":"","sources":["../../../src/features/interactive/search-prompt.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,8BAA8B,CAAC;AAC5D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAGxD;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,2CAA2C;IAC3C,KAAK,EAAE,MAAM,CAAC;IACd,qCAAqC;IACrC,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,KAAK,EAAE,MAAM,KAAK,YAAY,EAAE,CAAC;AAE/D;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,0BAA0B;IAC1B,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,uCAAuC;IACvC,SAAS,EAAE,OAAO,CAAC;CACpB;AAUD;;GAEG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,YAAY,EAAE,EACvB,aAAa,EAAE,MAAM,GACpB,kBAAkB,EAAE,CAetB;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,OAAO,EAAE,CAiBxE;AAED;;GAEG;AACH,wBAAgB,gBAAgB,IAAI,MAAM,CAEzC;AAoCD;;GAEG;AACH,wBAAsB,eAAe,CACnC,aAAa,EAAE,OAAO,EAAE,EACxB,QAAQ,EAAE,cAAc,EACxB,MAAM,EAAE,kBAAkB,EAC1B,YAAY,SAAK,GAChB,OAAO,CAAC,kBAAkB,CAAC,
|
|
1
|
+
{"version":3,"file":"search-prompt.d.ts","sourceRoot":"","sources":["../../../src/features/interactive/search-prompt.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,8BAA8B,CAAC;AAC5D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAGxD;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,2CAA2C;IAC3C,KAAK,EAAE,MAAM,CAAC;IACd,qCAAqC;IACrC,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,KAAK,EAAE,MAAM,KAAK,YAAY,EAAE,CAAC;AAE/D;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,0BAA0B;IAC1B,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,uCAAuC;IACvC,SAAS,EAAE,OAAO,CAAC;CACpB;AAUD;;GAEG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,YAAY,EAAE,EACvB,aAAa,EAAE,MAAM,GACpB,kBAAkB,EAAE,CAetB;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,OAAO,EAAE,CAiBxE;AAED;;GAEG;AACH,wBAAgB,gBAAgB,IAAI,MAAM,CAEzC;AAED;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,CAE1C;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAOnE;AAoCD;;GAEG;AACH,wBAAsB,eAAe,CACnC,aAAa,EAAE,OAAO,EAAE,EACxB,QAAQ,EAAE,cAAc,EACxB,MAAM,EAAE,kBAAkB,EAC1B,YAAY,SAAK,GAChB,OAAO,CAAC,kBAAkB,CAAC,CAuI7B"}
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"search-prompt-RtHDJFgL.js","sources":["../../src/features/interactive/format.ts","../../src/features/interactive/search-prompt.ts"],"sourcesContent":["/**\n * Display format functions for interactive search\n */\n\nimport type { CslItem } from \"../../core/csl-json/types.js\";\n\n/**\n * CSL name type (author structure)\n */\ntype CslName = NonNullable<CslItem[\"author\"]>[number];\n\n/**\n * Format a single author name\n * - Personal: \"Smith, J.\" (family + initial of given)\n * - Institutional: \"World Health Organization\" (literal)\n */\nfunction formatSingleAuthor(author: CslName): string {\n if (author.literal) {\n return author.literal;\n }\n if (author.family) {\n if (author.given) {\n const initial = author.given.charAt(0).toUpperCase();\n return `${author.family}, ${initial}.`;\n }\n return author.family;\n }\n return \"\";\n}\n\n/**\n * Format author list for display\n * - Single author: \"Smith, J.\"\n * - Two authors: \"Smith, J., & Doe, A.\"\n * - Three authors: \"Smith, J., Doe, A., & Johnson, B.\"\n * - More than three: \"Smith, J., et al.\"\n *\n * @param authors - Array of CSL author objects\n * @returns Formatted author string\n */\nexport function formatAuthors(authors: CslName[] | undefined): string {\n if (!authors || authors.length === 0) {\n return \"\";\n }\n\n if (authors.length > 3) {\n const first = authors[0];\n if (!first) {\n return \"\";\n }\n return `${formatSingleAuthor(first)}, et al.`;\n }\n\n const formatted = authors.map(formatSingleAuthor);\n if (formatted.length === 1) {\n return formatted[0] ?? \"\";\n }\n\n // Join all but last with \", \" and append \"& \" + last\n const allButLast = formatted.slice(0, -1).join(\", \");\n const last = formatted[formatted.length - 1] ?? \"\";\n return `${allButLast}, & ${last}`;\n}\n\n/**\n * Truncate title to fit terminal width\n *\n * @param title - Title string\n * @param maxWidth - Maximum display width\n * @returns Truncated title with ellipsis if needed\n */\nexport function formatTitle(title: string | undefined, maxWidth: number): string {\n if (!title) {\n return \"\";\n }\n\n if (title.length <= maxWidth) {\n return title;\n }\n\n // Truncate and add ellipsis, keeping total length at maxWidth\n return `${title.slice(0, maxWidth - 3)}...`;\n}\n\n/**\n * Format identifiers (DOI, PMID, PMCID, ISBN) for display\n *\n * @param item - CSL item\n * @returns Formatted identifier string (e.g., \"DOI: 10.1000/example | PMID: 12345678\")\n */\nexport function formatIdentifiers(item: CslItem): string {\n const identifiers: string[] = [];\n\n if (item.DOI) {\n identifiers.push(`DOI: ${item.DOI}`);\n }\n if (item.PMID) {\n identifiers.push(`PMID: ${item.PMID}`);\n }\n if (item.PMCID) {\n identifiers.push(`PMCID: ${item.PMCID}`);\n }\n if (item.ISBN) {\n identifiers.push(`ISBN: ${item.ISBN}`);\n }\n\n return identifiers.join(\" | \");\n}\n\n/**\n * Extract year from CSL item\n */\nfunction extractYear(item: CslItem): number | undefined {\n const dateParts = item.issued?.[\"date-parts\"];\n if (!dateParts || dateParts.length === 0) {\n return undefined;\n }\n const firstDatePart = dateParts[0];\n if (!firstDatePart || firstDatePart.length === 0) {\n return undefined;\n }\n return firstDatePart[0];\n}\n\n/**\n * Compose a complete search result line\n *\n * Format:\n * ```\n * [1] Smith, J., & Doe, A. (2020)\n * Machine learning in medicine: A comprehensive review\n * DOI: 10.1000/example | PMID: 12345678\n * ```\n *\n * @param item - CSL item\n * @param index - Display index (1-based)\n * @param terminalWidth - Terminal width for title truncation\n * @returns Multi-line formatted string\n */\nexport function formatSearchResult(item: CslItem, index: number, terminalWidth: number): string {\n const lines: string[] = [];\n\n // Line 1: [index] Authors (year)\n const authors = formatAuthors(item.author);\n const year = extractYear(item);\n const yearPart = year !== undefined ? ` (${year})` : \"\";\n const line1 = `[${index}] ${authors}${yearPart}`;\n lines.push(line1);\n\n // Line 2: Title (indented, truncated)\n const indent = \" \";\n const titleMaxWidth = terminalWidth - indent.length;\n const title = formatTitle(item.title, titleMaxWidth);\n if (title) {\n lines.push(`${indent}${title}`);\n }\n\n // Line 3: Identifiers (indented)\n const identifiers = formatIdentifiers(item);\n if (identifiers) {\n lines.push(`${indent}${identifiers}`);\n }\n\n return lines.join(\"\\n\");\n}\n","/**\n * Interactive search prompt using Enquirer's AutoComplete\n *\n * Provides real-time incremental search with multiple selection support.\n */\n\nimport type { CslItem } from \"../../core/csl-json/types.js\";\nimport type { SearchResult } from \"../search/types.js\";\nimport type { AutoCompleteChoice } from \"./enquirer.js\";\nimport { formatSearchResult } from \"./format.js\";\n\n/**\n * Configuration for the search prompt\n */\nexport interface SearchPromptConfig {\n /** Maximum number of results to display */\n limit: number;\n /** Debounce delay in milliseconds */\n debounceMs: number;\n}\n\n/**\n * Search function type for filtering references\n */\nexport type SearchFunction = (query: string) => SearchResult[];\n\n/**\n * Result from the search prompt\n */\nexport interface SearchPromptResult {\n /** Selected references */\n selected: CslItem[];\n /** Whether the prompt was cancelled */\n cancelled: boolean;\n}\n\n/**\n * Maps internal choice value to CslItem\n */\ninterface ChoiceData {\n index: number;\n item: CslItem;\n}\n\n/**\n * Creates choices from search results\n */\nexport function createChoices(\n results: SearchResult[],\n terminalWidth: number\n): AutoCompleteChoice[] {\n return results.map((result, index) => {\n const displayIndex = index + 1;\n const formattedText = formatSearchResult(result.reference, displayIndex, terminalWidth);\n\n // Enquirer returns the 'name' property on selection when 'value' is not defined\n // So we store the JSON data in 'name' and use 'message' for display\n return {\n name: JSON.stringify({\n index,\n item: result.reference,\n } satisfies ChoiceData),\n message: formattedText,\n };\n });\n}\n\n/**\n * Parses selected values back to CslItems\n */\nexport function parseSelectedValues(values: string | string[]): CslItem[] {\n const valueArray = Array.isArray(values) ? values : [values];\n const items: CslItem[] = [];\n\n for (const value of valueArray) {\n if (!value) continue;\n try {\n const data = JSON.parse(value) as ChoiceData;\n items.push(data.item);\n } catch {\n // If parsing fails, the value might be just the id (name)\n // In this case, we can't recover the full item\n // This shouldn't happen in normal operation\n }\n }\n\n return items;\n}\n\n/**\n * Gets terminal width, falling back to 80 if not available\n */\nexport function getTerminalWidth(): number {\n return process.stdout.columns ?? 80;\n}\n\n/**\n * Collects enabled (selected) item IDs from choices\n */\nfunction collectEnabledIds(choices: AutoCompleteChoice[]): Set<string> {\n const enabledIds = new Set<string>();\n for (const choice of choices) {\n if ((choice as { enabled?: boolean }).enabled) {\n try {\n const data = JSON.parse(choice.name) as ChoiceData;\n enabledIds.add(data.item.id);\n } catch {\n // Ignore parse errors\n }\n }\n }\n return enabledIds;\n}\n\n/**\n * Restores enabled state for choices matching the given IDs\n */\nfunction restoreEnabledState(choices: AutoCompleteChoice[], enabledIds: Set<string>): void {\n for (const choice of choices) {\n try {\n const data = JSON.parse(choice.name) as ChoiceData;\n if (enabledIds.has(data.item.id)) {\n (choice as { enabled?: boolean }).enabled = true;\n }\n } catch {\n // Ignore parse errors\n }\n }\n}\n\n/**\n * Creates and runs an interactive search prompt\n */\nexport async function runSearchPrompt(\n allReferences: CslItem[],\n searchFn: SearchFunction,\n config: SearchPromptConfig,\n initialQuery = \"\"\n): Promise<SearchPromptResult> {\n // Dynamic import to allow mocking in tests\n // enquirer is a CommonJS module, so we must use default import\n const enquirer = await import(\"enquirer\");\n const AutoComplete = (enquirer.default as unknown as Record<string, unknown>)\n .AutoComplete as new (\n options: Record<string, unknown>\n ) => {\n run(): Promise<string | string[]>;\n };\n\n const terminalWidth = getTerminalWidth();\n\n // Create initial choices from all references (limited)\n const initialResults: SearchResult[] = initialQuery\n ? searchFn(initialQuery).slice(0, config.limit)\n : allReferences.slice(0, config.limit).map((ref) => ({\n reference: ref,\n overallStrength: \"exact\" as const,\n tokenMatches: [],\n score: 0,\n }));\n\n const initialChoices = createChoices(initialResults, terminalWidth);\n\n // Track last search query to avoid redundant searches\n let lastQuery = initialQuery;\n\n const promptOptions = {\n name: \"references\",\n message: \"Search references\",\n initial: initialQuery,\n choices: initialChoices,\n multiple: true,\n limit: config.limit,\n suggest: (input: string, choices: AutoCompleteChoice[]) => {\n // If input hasn't changed, return current choices\n if (input === lastQuery) {\n return choices;\n }\n lastQuery = input;\n\n // Collect enabled (selected) item IDs from current choices\n const enabledIds = collectEnabledIds(choices);\n\n // Create new choices based on input\n const newChoices = input.trim()\n ? createChoices(searchFn(input).slice(0, config.limit), terminalWidth)\n : createChoices(\n allReferences.slice(0, config.limit).map((ref) => ({\n reference: ref,\n overallStrength: \"exact\" as const,\n tokenMatches: [],\n score: 0,\n })),\n terminalWidth\n );\n\n // Restore enabled state for matching items\n restoreEnabledState(newChoices, enabledIds);\n\n return newChoices;\n },\n };\n\n try {\n const prompt = new AutoComplete(promptOptions);\n const result = await prompt.run();\n\n // Workaround for Enquirer bug: focused item with enabled=true may not be in result\n const promptAny = prompt as unknown as {\n focused?: { name: string; enabled?: boolean };\n selected?: Array<{ name: string }>;\n };\n\n let valuesToParse: string | string[] = result;\n\n // If result is empty but focused item is enabled, use focused item\n if (\n Array.isArray(result) &&\n result.length === 0 &&\n promptAny.focused?.enabled &&\n promptAny.focused?.name\n ) {\n valuesToParse = [promptAny.focused.name];\n }\n\n // Handle result\n const selected = parseSelectedValues(valuesToParse);\n\n return {\n selected,\n cancelled: false,\n };\n } catch (error) {\n // Enquirer throws an empty string when cancelled\n if (error === \"\" || (error instanceof Error && error.message === \"\")) {\n return {\n selected: [],\n cancelled: true,\n };\n }\n throw error;\n }\n}\n"],"names":[],"mappings":"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"}
|