diff --git a/src/interface/obsidian/src/search_modal.ts b/src/interface/obsidian/src/search_modal.ts index 7d791204..60b4accb 100644 --- a/src/interface/obsidian/src/search_modal.ts +++ b/src/interface/obsidian/src/search_modal.ts @@ -1,6 +1,6 @@ import { App, SuggestModal, request, MarkdownRenderer, Instruction, Platform } from 'obsidian'; import { KhojSetting } from 'src/settings'; -import { createNoteAndCloseModal, getLinkToEntry } from 'src/utils'; +import { supportedBinaryFileTypes, createNoteAndCloseModal, getFileFromPath, getLinkToEntry, supportedImageFilesTypes } from 'src/utils'; export interface SearchResult { entry: string; @@ -112,28 +112,41 @@ export class KhojSearchModal extends SuggestModal { let os_path_separator = result.file.includes('\\') ? '\\' : '/'; let filename = result.file.split(os_path_separator).pop(); - // Remove YAML frontmatter when rendering string - result.entry = result.entry.replace(/---[\n\r][\s\S]*---[\n\r]/, ''); - - // Truncate search results to lines_to_render - let entry_snipped_indicator = result.entry.split('\n').length > lines_to_render ? ' **...**' : ''; - let snipped_entry = result.entry.split('\n').slice(0, lines_to_render).join('\n'); - // Show filename of each search result for context el.createEl("div",{ cls: 'khoj-result-file' }).setText(filename ?? ""); let result_el = el.createEl("div", { cls: 'khoj-result-entry' }) + let resultToRender = ""; + let fileExtension = filename?.split(".").pop() ?? ""; + if (supportedImageFilesTypes.includes(fileExtension) && filename) { + let linkToEntry: string = filename; + let imageFiles = this.app.vault.getFiles().filter(file => supportedImageFilesTypes.includes(fileExtension)); + // Find vault file of chosen search result + let fileInVault = getFileFromPath(imageFiles, result.file); + if (fileInVault) + linkToEntry = this.app.vault.getResourcePath(fileInVault); + + resultToRender = `![](${linkToEntry})`; + } else { + // Remove YAML frontmatter when rendering string + result.entry = result.entry.replace(/---[\n\r][\s\S]*---[\n\r]/, ''); + + // Truncate search results to lines_to_render + let entry_snipped_indicator = result.entry.split('\n').length > lines_to_render ? ' **...**' : ''; + let snipped_entry = result.entry.split('\n').slice(0, lines_to_render).join('\n'); + resultToRender = `${snipped_entry}${entry_snipped_indicator}`; + } // @ts-ignore - MarkdownRenderer.renderMarkdown(snipped_entry + entry_snipped_indicator, result_el, result.file, null); + MarkdownRenderer.renderMarkdown(resultToRender, result_el, result.file, null); } async onChooseSuggestion(result: SearchResult, _: MouseEvent | KeyboardEvent) { - // Get all markdown and PDF files in vault + // Get all markdown, pdf and image files in vault const mdFiles = this.app.vault.getMarkdownFiles(); - const pdfFiles = this.app.vault.getFiles().filter(file => file.extension === 'pdf'); + const binaryFiles = this.app.vault.getFiles().filter(file => supportedBinaryFileTypes.includes(file.extension)); // Find, Open vault file at heading of chosen search result - let linkToEntry = getLinkToEntry(mdFiles.concat(pdfFiles), result.file, result.entry); + let linkToEntry = getLinkToEntry(mdFiles.concat(binaryFiles), result.file, result.entry); if (linkToEntry) this.app.workspace.openLinkText(linkToEntry, ''); } } diff --git a/src/interface/obsidian/src/settings.ts b/src/interface/obsidian/src/settings.ts index 5e0e3494..85e51187 100644 --- a/src/interface/obsidian/src/settings.ts +++ b/src/interface/obsidian/src/settings.ts @@ -10,7 +10,6 @@ export interface UserInfo { email?: string; } - export interface KhojSetting { resultsCount: number; khojUrl: string; diff --git a/src/interface/obsidian/src/utils.ts b/src/interface/obsidian/src/utils.ts index 4a969793..14825543 100644 --- a/src/interface/obsidian/src/utils.ts +++ b/src/interface/obsidian/src/utils.ts @@ -48,11 +48,14 @@ function filenameToMimeType (filename: TFile): string { } } +export const supportedImageFilesTypes = ['png', 'jpg', 'jpeg']; +export const supportedBinaryFileTypes = ['pdf'].concat(supportedImageFilesTypes); +export const supportedFileTypes = ['md', 'markdown'].concat(supportedBinaryFileTypes); + export async function updateContentIndex(vault: Vault, setting: KhojSetting, lastSync: Map, regenerate: boolean = false): Promise> { // Get all markdown, pdf files in the vault console.log(`Khoj: Updating Khoj content index...`) - const files = vault.getFiles().filter(file => file.extension === 'md' || file.extension === 'markdown' || file.extension === 'pdf'); - const binaryFileTypes = ['pdf'] + const files = vault.getFiles().filter(file => supportedFileTypes.includes(file.extension)); let countOfFilesToIndex = 0; let countOfFilesToDelete = 0; lastSync = lastSync.size > 0 ? lastSync : new Map(); @@ -66,7 +69,7 @@ export async function updateContentIndex(vault: Vault, setting: KhojSetting, las } countOfFilesToIndex++; - const encoding = binaryFileTypes.includes(file.extension) ? "binary" : "utf8"; + const encoding = supportedBinaryFileTypes.includes(file.extension) ? "binary" : "utf8"; const mimeType = fileExtensionToMimeType(file.extension) + (encoding === "utf8" ? "; charset=UTF-8" : ""); const fileContent = encoding == 'binary' ? await vault.readBinary(file) : await vault.read(file); fileData.push({blob: new Blob([fileContent], { type: mimeType }), path: file.path}); @@ -353,7 +356,7 @@ export function pasteTextAtCursor(text: string | undefined) { } } -export function getLinkToEntry(sourceFiles: TFile[], chosenFile: string, chosenEntry: string): string | undefined { +export function getFileFromPath(sourceFiles: TFile[], chosenFile: string): TFile | undefined { // Find the vault file matching file of chosen file, entry let fileMatch = sourceFiles // Sort by descending length of path @@ -362,6 +365,12 @@ export function getLinkToEntry(sourceFiles: TFile[], chosenFile: string, chosenE // The first match is the best file match across OS // e.g. Khoj server on Linux, Obsidian vault on Android .find(file => chosenFile.replace(/\\/g, "/").endsWith(file.path)) + return fileMatch; +} + +export function getLinkToEntry(sourceFiles: TFile[], chosenFile: string, chosenEntry: string): string | undefined { + // Find the vault file matching file of chosen file, entry + let fileMatch = getFileFromPath(sourceFiles, chosenFile); // Return link to vault file at heading of chosen search result if (fileMatch) { diff --git a/tests/test_client.py b/tests/test_client.py index d3c18030..24d2dff6 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -61,7 +61,7 @@ def test_search_with_invalid_content_type(client): @pytest.mark.django_db(transaction=True) def test_search_with_valid_content_type(client): headers = {"Authorization": "Bearer kk-secret"} - for content_type in ["all", "org", "markdown", "image", "pdf", "github", "notion", "plaintext", "docx"]: + for content_type in ["all", "org", "markdown", "image", "pdf", "github", "notion", "plaintext", "image", "docx"]: # Act response = client.get(f"/api/search?q=random&t={content_type}", headers=headers) # Assert