Support syncing, searching images from Obsidian plugin

This commit is contained in:
Debanjum Singh Solanky 2024-07-09 17:29:13 +05:30
parent 260aa61818
commit 21fe1a917b
4 changed files with 39 additions and 18 deletions

View file

@ -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<SearchResult> {
let os_path_separator = result.file.includes('\\') ? '\\' : '/';
let filename = result.file.split(os_path_separator).pop();
// 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');
// 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' })
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, '');
}
}

View file

@ -10,7 +10,6 @@ export interface UserInfo {
email?: string;
}
export interface KhojSetting {
resultsCount: number;
khojUrl: string;

View file

@ -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<TFile, number>, regenerate: boolean = false): Promise<Map<TFile, number>> {
// 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<TFile, number>();
@ -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) {

View file

@ -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