Skip to content

Commit cb377ff

Browse files
authored
feat: add rich metadata display to NLP project autosuggestion (#1215)
Enhance the CodeMirror-based NLP autocomplete to display rich project metadata (title, aliases, path, custom frontmatter) when users type the project trigger, bringing feature parity with the manual "Add to Project" button dropdown. Changes: - Use CodeMirror 6's addToOptions API for custom rendering - Display up to 3 configurable metadata rows based on projectAutosuggest.rows settings - Add ProjectMetadataResolver integration for consistent metadata resolution - Add CSS styles for metadata display in autocomplete tooltip This improves UX consistency by ensuring both project selection methods (NLP-triggered and manual) display the same rich context to help users identify the correct project.
1 parent 7abc899 commit cb377ff

File tree

2 files changed

+169
-0
lines changed

2 files changed

+169
-0
lines changed

src/editor/NLPCodeMirrorAutocomplete.ts

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,29 @@ export function createNLPAutocomplete(plugin: TaskNotesPlugin): Extension[] {
129129
closeOnBlur: true,
130130
// Max options to show
131131
maxRenderedOptions: 10,
132+
// Custom rendering for project suggestions with metadata
133+
addToOptions: [
134+
{
135+
render: (completion: any, _state: any, _view: any) => {
136+
// Only render custom content for project suggestions with metadata
137+
if (!completion.projectMetadata) return null;
138+
139+
const container = document.createElement("div");
140+
container.className = "cm-project-suggestion__metadata";
141+
142+
const metadata = completion.projectMetadata;
143+
for (const row of metadata) {
144+
const metaRow = document.createElement("div");
145+
metaRow.className = "cm-project-suggestion__meta";
146+
metaRow.textContent = row;
147+
container.appendChild(metaRow);
148+
}
149+
150+
return container;
151+
},
152+
position: 100, // After label (50) and detail (80)
153+
},
154+
],
132155
});
133156

134157
// Add explicit keyboard navigation for autocomplete with high priority
@@ -235,6 +258,8 @@ async function getFileSuggestions(
235258
): Promise<Completion[]> {
236259
try {
237260
const { FileSuggestHelper } = await import("../suggest/FileSuggestHelper");
261+
const { ProjectMetadataResolver } = await import("../utils/projectMetadataResolver");
262+
const { parseDisplayFieldsRow } = await import("../utils/projectAutosuggestDisplayFieldsParser");
238263

239264
// Get autosuggest config - use projectAutosuggest for projects,
240265
// or user field's autosuggestFilter for user fields
@@ -262,6 +287,95 @@ async function getFileSuggestions(
262287
return !excluded.some((ex) => file.path.startsWith(ex));
263288
});
264289

290+
// For projects, add rich metadata rendering
291+
if (propertyId === "projects") {
292+
const resolver = new ProjectMetadataResolver({
293+
getFrontmatter: (entry) => entry.frontmatter,
294+
});
295+
const rowConfigs = (plugin.settings?.projectAutosuggest?.rows ?? []).slice(0, 3);
296+
297+
return filteredList.map((item) => {
298+
const displayText = item.displayText || item.insertText;
299+
const insertText = item.insertText;
300+
301+
// Get file metadata for rendering
302+
const file = plugin.app.vault
303+
.getMarkdownFiles()
304+
.find((f) => f.basename === item.insertText);
305+
306+
// Build metadata rows
307+
const metadataRows: string[] = [];
308+
if (file && rowConfigs.length > 0) {
309+
const cache = plugin.app.metadataCache.getFileCache(file);
310+
const frontmatter: Record<string, any> = cache?.frontmatter || {};
311+
const mapped = plugin.fieldMapper.mapFromFrontmatter(
312+
frontmatter,
313+
file.path,
314+
plugin.settings.storeTitleInFilename
315+
);
316+
317+
const title = typeof mapped.title === "string" ? mapped.title : "";
318+
const aliases = Array.isArray(frontmatter["aliases"])
319+
? (frontmatter["aliases"] as any[]).filter((a: any) => typeof a === "string")
320+
: [];
321+
322+
const fileData = {
323+
basename: file.basename,
324+
name: file.name,
325+
path: file.path,
326+
parent: file.parent?.path || "",
327+
title,
328+
aliases,
329+
frontmatter,
330+
};
331+
332+
// Build each configured row
333+
for (let i = 0; i < Math.min(rowConfigs.length, 3); i++) {
334+
const row = rowConfigs[i];
335+
if (!row) continue;
336+
337+
try {
338+
const tokens = parseDisplayFieldsRow(row);
339+
const parts: string[] = [];
340+
341+
for (const token of tokens) {
342+
if (token.property.startsWith("literal:")) {
343+
const lit = token.property.slice(8);
344+
if (lit) parts.push(lit);
345+
continue;
346+
}
347+
348+
const value = resolver.resolve(token.property, fileData);
349+
if (!value) continue;
350+
351+
if (token.showName) {
352+
parts.push(`${token.displayName ?? token.property}: ${value}`);
353+
} else {
354+
parts.push(value);
355+
}
356+
}
357+
358+
if (parts.length > 0) {
359+
metadataRows.push(parts.join(" "));
360+
}
361+
} catch {
362+
// Ignore row parse errors
363+
}
364+
}
365+
}
366+
367+
return {
368+
label: displayText,
369+
apply: `[[${insertText}]] `,
370+
type: "text",
371+
info: "Project",
372+
// Add metadata as a custom property for the render function
373+
projectMetadata: metadataRows.length > 0 ? metadataRows : undefined,
374+
} as any;
375+
});
376+
}
377+
378+
// For non-project file suggestions, use simple rendering
265379
return filteredList.map((item) => {
266380
const displayText = item.displayText || item.insertText;
267381
const insertText = item.insertText;

styles/task-modal.css

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -870,6 +870,61 @@
870870
color: var(--text-normal);
871871
}
872872

873+
/* NLP Project Suggestion Styles - Rich Metadata Display (Legacy textarea-based) */
874+
.tasknotes-plugin .nlp-suggest-project__filename {
875+
font-weight: var(--font-weight-medium);
876+
color: var(--text-normal);
877+
font-size: var(--font-ui-small);
878+
margin-bottom: 2px;
879+
}
880+
881+
.tasknotes-plugin .nlp-suggest-project__meta {
882+
font-size: var(--font-ui-smaller);
883+
color: var(--text-muted);
884+
margin-top: 2px;
885+
line-height: 1.4;
886+
}
887+
888+
.tasknotes-plugin .nlp-suggest-project__meta-label {
889+
color: var(--text-muted);
890+
font-weight: 500;
891+
margin-right: 4px;
892+
}
893+
894+
.tasknotes-plugin .nlp-suggest-project__meta-value {
895+
color: var(--text-normal);
896+
}
897+
898+
/* CodeMirror Project Suggestion Styles - Rich Metadata Display */
899+
.tasknotes-plugin .cm-tooltip-autocomplete .cm-project-suggestion {
900+
display: flex;
901+
flex-direction: column;
902+
gap: 2px;
903+
padding: var(--size-4-1) 0;
904+
}
905+
906+
.tasknotes-plugin .cm-tooltip-autocomplete .cm-project-suggestion__name {
907+
font-weight: var(--font-weight-medium);
908+
color: var(--text-normal);
909+
font-size: var(--font-ui-small);
910+
}
911+
912+
.tasknotes-plugin .cm-tooltip-autocomplete .cm-project-suggestion__meta {
913+
font-size: var(--font-ui-smaller);
914+
color: var(--text-muted);
915+
line-height: 1.4;
916+
}
917+
918+
.tasknotes-plugin .cm-tooltip-autocomplete .cm-project-suggestion__meta-label {
919+
color: var(--text-muted);
920+
font-weight: 500;
921+
margin-right: 4px;
922+
}
923+
924+
.tasknotes-plugin .cm-tooltip-autocomplete .cm-project-suggestion__meta-value {
925+
color: var(--text-normal);
926+
}
927+
873928
/* =====================================================================
874929
PROJECT SELECTION UI
875930
===================================================================== */

0 commit comments

Comments
 (0)