mirror of
https://github.com/maradotwebp/obsidian-avatar.git
synced 2026-07-22 07:30:24 +00:00
allow user to pick image
This commit is contained in:
parent
5417009647
commit
be39eb09d4
3 changed files with 111 additions and 5 deletions
|
|
@ -1,9 +1,10 @@
|
|||
<script lang="ts">
|
||||
import {App, MarkdownPostProcessorContext, MarkdownView} from "obsidian";
|
||||
import {App, MarkdownPostProcessorContext, MarkdownView, TFile} from "obsidian";
|
||||
import {AvatarPlugin} from "../plugin";
|
||||
import Fab from "./Fab.svelte";
|
||||
import ObsidianIcon from "./ObsidianIcon.svelte";
|
||||
import type {SetState, State} from "../core/stateProviders";
|
||||
import {SelectImageModal} from "./SelectImageModal";
|
||||
|
||||
interface AvatarViewState {
|
||||
image: string;
|
||||
|
|
@ -11,7 +12,6 @@
|
|||
}
|
||||
|
||||
export let app: App;
|
||||
export let plugin: AvatarPlugin;
|
||||
export let ctx: MarkdownPostProcessorContext;
|
||||
export let state: State<AvatarViewState>;
|
||||
export let setState: SetState<AvatarViewState>;
|
||||
|
|
@ -34,18 +34,33 @@
|
|||
});
|
||||
}
|
||||
|
||||
function selectImage() {
|
||||
function updateImage() {
|
||||
if(inSourceMode) {
|
||||
new SelectImageModal(app, (path) => {
|
||||
setState((fm) => {
|
||||
fm.image = path;
|
||||
});
|
||||
}).open();
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeImgPath(src: string): string {
|
||||
const file = app.vault.getAbstractFileByPath(src);
|
||||
if(file && file instanceof TFile) {
|
||||
return app.vault.getResourcePath(file);
|
||||
}
|
||||
return src;
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex">
|
||||
<div
|
||||
class="avatar relative"
|
||||
on:click={selectImage}
|
||||
on:click={updateImage}
|
||||
on:mouseenter={() => hoverOnImage = true}
|
||||
on:mouseleave={() => hoverOnImage = false}
|
||||
>
|
||||
<img class="avatar" alt="Avatar" src={state?.image ?? fallbackImage} />
|
||||
<img class="avatar" alt="Avatar" src={normalizeImgPath(state?.image) ?? fallbackImage} />
|
||||
{#if inSourceMode && hoverOnImage}
|
||||
<Fab>
|
||||
<ObsidianIcon id="edit"></ObsidianIcon>
|
||||
|
|
|
|||
87
src/components/SelectImageModal.ts
Normal file
87
src/components/SelectImageModal.ts
Normal file
|
|
@ -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<Image> {
|
||||
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<Image & { score: number }>(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;
|
||||
}
|
||||
|
|
@ -13,3 +13,7 @@
|
|||
.markdown-reading-view > .show-in-reading-view {
|
||||
display: initial;
|
||||
}
|
||||
|
||||
.float-right {
|
||||
float: right;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue