diff --git a/src/components/AvatarView.svelte b/src/components/AvatarView.svelte index 30829cb..f24ae44 100644 --- a/src/components/AvatarView.svelte +++ b/src/components/AvatarView.svelte @@ -1,9 +1,10 @@
hoverOnImage = true} on:mouseleave={() => hoverOnImage = false} > - Avatar + Avatar {#if inSourceMode && hoverOnImage} diff --git a/src/components/SelectImageModal.ts b/src/components/SelectImageModal.ts new file mode 100644 index 0000000..5c4b6ba --- /dev/null +++ b/src/components/SelectImageModal.ts @@ -0,0 +1,87 @@ +import {App, SuggestModal, normalizePath, prepareFuzzySearch} from "obsidian"; +import type {SearchMatchPart} from "obsidian"; + +export interface Image { + title: DocumentFragment; + desc?: string; + path: string; +} + +export class SelectImageModal extends SuggestModal { + constructor( + app: App, + private readonly onSelect: (path: string) => void + ) { + super(app); + this.setPlaceholder("Select image / Paste URL..."); + this.setInstructions([ + { command: "↑↓", purpose: "to navigate" }, + { command: "↵", purpose: "to select" } + ]); + } + + override getSuggestions(query: string): Image[] { + const search = prepareFuzzySearch(query); + const files = this.app.vault.getFiles() + .filter(f => ["jpg", "jpeg", "png", "gif", "svg", "webp"].includes(f.extension)) + .map(f => ({ title: f.basename, desc: f.extension, path: f.path })); + const searchResults = files + .map(f => { + const result = search(f.title); + return { + title: result?.matches ? fuzzyStrToFragment(f.title, result?.matches) : strToFragment(f.title), + desc: f.desc, + path: f.path, + score: result?.score ?? 1 + } + }) + .filter(f => f.score < 1) + .sort((a, b) => Math.abs(a.score) - Math.abs(b.score)); + + if (query.startsWith("http://") || query.startsWith("https://")) { + const shortenedQuery = query.substring(0, 30) + (query.length >= 30 ? "..." : ""); + return [ + ...searchResults, + { title: strToFragment(shortenedQuery), desc: "Use URL", path: query } + ]; + } else { + return searchResults; + } + } + + override renderSuggestion(item: Image, el: HTMLElement) { + el.createEl("span", { text: item.title }); + el.createEl("small", { text: item.desc, cls: "float-right" }); + } + + override onChooseSuggestion(item: Image, evt: MouseEvent | KeyboardEvent): any { + this.onSelect(normalizePath(item.path)); + } +} + +function strToFragment(str: string): DocumentFragment { + const fragment = new DocumentFragment(); + fragment.createEl("span", { text: str }); + return fragment; +} + +function fuzzyStrToFragment(str: string, matches: SearchMatchPart[]) : DocumentFragment { + const fragment = new DocumentFragment(); + + const highlightedIndices: number[] = []; + for(const match of matches) { + for(let i = match[0]; i < match[1]; i++) { + highlightedIndices.push(i); + } + } + + for(let i = 0; i < str.length; i++) { + const char = str[i]; + if(highlightedIndices.includes(i)) { + fragment.createEl("span", { text: char, cls: "suggestion-highlight" }); + } else { + fragment.createEl("span", { text: char }); + } + } + return fragment; +} diff --git a/styles.css b/styles.css index 745b35c..b1d1126 100644 --- a/styles.css +++ b/styles.css @@ -13,3 +13,7 @@ .markdown-reading-view > .show-in-reading-view { display: initial; } + +.float-right { + float: right; +}