Remove old suggesters

This commit is contained in:
Evan Fiordeliso 2025-06-30 10:18:53 -04:00
parent ad0af3d369
commit 76de66ca80
8 changed files with 45 additions and 360 deletions

View File

@ -0,0 +1,43 @@
<script lang="ts">
import type { App, TFile } from "obsidian";
import TextInputSuggest, { type Item } from "./TextInputSuggest.svelte";
type Props = {
app: App;
id: string;
asString?: boolean;
value?: TFile | string;
onSelected?: (fileOrPath: TFile | string) => void;
};
let {
app,
id,
asString,
value = $bindable(),
onSelected,
}: Props = $props();
let items: Item<TFile | string>[] = $state([]);
function handleChange(query: string) {
items = app.vault
.getMarkdownFiles()
.filter((f) =>
f.basename.toLowerCase().includes(query.toLowerCase()),
)
.map((f) => ({
text: f.basename,
value: asString ? f.basename : f,
}));
}
</script>
<TextInputSuggest
{app}
{id}
{items}
bind:value
onChange={handleChange}
{onSelected}
/>

View File

@ -1,7 +1,7 @@
<script lang="ts">
import type { ReadingLogEntry } from "@src/types";
import BookSuggest from "@ui/components/suggesters/BookSuggest.svelte";
import type { App } from "obsidian";
import { BookSuggest } from "@ui/suggesters";
interface Props {
app: App;
entry?: ReadingLogEntry;
@ -54,10 +54,6 @@
createdAt: new Date(createdAt),
});
}
function bookSuggest(el: HTMLInputElement) {
new BookSuggest(app, el);
}
</script>
<div class="obt-reading-log-entry-editor">
@ -65,14 +61,7 @@
<form {onsubmit}>
<div class="fields">
<label for="book">Book</label>
<input
type="text"
name="book"
id="book"
bind:value={book}
disabled={editMode}
use:bookSuggest
/>
<BookSuggest id="book" {app} bind:value={book} />
<label for="pagesRead">Pages Read</label>
<input
type="number"

View File

@ -1,32 +0,0 @@
import { TAbstractFile, TFile } from "obsidian";
import { TextInputSuggest } from "./core";
export class BookSuggest extends TextInputSuggest<TFile> {
getSuggestions(inputStr: string): TFile[] {
const abstractFiles = this.app.vault.getAllLoadedFiles();
const files: TFile[] = [];
const lowerCaseInputStr = inputStr.toLowerCase();
abstractFiles.forEach((file: TAbstractFile) => {
if (
file instanceof TFile &&
file.extension === "md" &&
file.basename.toLowerCase().contains(lowerCaseInputStr)
) {
files.push(file);
}
});
return files;
}
renderSuggestion(file: TFile, el: HTMLElement): void {
el.setText(file.basename);
}
selectSuggestion(file: TFile): void {
this.inputEl.value = file.basename;
this.inputEl.trigger("input");
this.close();
}
}

View File

@ -1,39 +0,0 @@
import { App } from "obsidian";
import { TextInputSuggest } from "./core";
export class FieldSuggest extends TextInputSuggest<string> {
constructor(
app: App,
inputEl: HTMLInputElement,
private readonly accepts?: string[]
) {
super(app, inputEl);
}
async getSuggestions(inputStr: string): Promise<string[]> {
const typesContent = await this.app.vault.adapter.read(
this.app.vault.configDir + "/types.json"
);
const types = JSON.parse(typesContent).types;
return Object.entries(types)
.filter(([field, type]) => {
if (this.accepts && !this.accepts.includes(type as string)) {
return false;
}
return field.toLowerCase().includes(inputStr.toLowerCase());
})
.map(([field, _]) => field);
}
renderSuggestion(field: string, el: HTMLElement): void {
el.setText(field);
}
selectSuggestion(field: string): void {
this.inputEl.value = field;
this.inputEl.trigger("input");
this.close();
}
}

View File

@ -1,34 +0,0 @@
// Credits go to Liam's Periodic Notes Plugin: https://github.com/liamcain/obsidian-periodic-notes
import { TAbstractFile, TFile } from "obsidian";
import { TextInputSuggest } from "./core";
export class FileSuggest extends TextInputSuggest<TFile> {
getSuggestions(inputStr: string): TFile[] {
const abstractFiles = this.app.vault.getAllLoadedFiles();
const files: TFile[] = [];
const lowerCaseInputStr = inputStr.toLowerCase();
abstractFiles.forEach((file: TAbstractFile) => {
if (
file instanceof TFile &&
file.extension === "md" &&
file.path.toLowerCase().contains(lowerCaseInputStr)
) {
files.push(file);
}
});
return files;
}
renderSuggestion(file: TFile, el: HTMLElement): void {
el.setText(file.path);
}
selectSuggestion(file: TFile): void {
this.inputEl.value = file.path;
this.inputEl.trigger("input");
this.close();
}
}

View File

@ -1,33 +0,0 @@
// Credits go to Liam's Periodic Notes Plugin: https://github.com/liamcain/obsidian-periodic-notes
import { TAbstractFile, TFolder } from "obsidian";
import { TextInputSuggest } from "./core";
export class FolderSuggest extends TextInputSuggest<TFolder> {
getSuggestions(inputStr: string): TFolder[] {
const abstractFiles = this.app.vault.getAllLoadedFiles();
const folders: TFolder[] = [];
const lowerCaseInputStr = inputStr.toLowerCase();
abstractFiles.forEach((folder: TAbstractFile) => {
if (
folder instanceof TFolder &&
folder.path.toLowerCase().contains(lowerCaseInputStr)
) {
folders.push(folder);
}
});
return folders;
}
renderSuggestion(file: TFolder, el: HTMLElement): void {
el.setText(file.path);
}
selectSuggestion(file: TFolder): void {
this.inputEl.value = file.path;
this.inputEl.trigger("input");
this.close();
}
}

View File

@ -1,205 +0,0 @@
// Credits go to Liam's Periodic Notes Plugin: https://github.com/liamcain/obsidian-periodic-notes
import { App, type ISuggestOwner, Scope } from "obsidian";
import { createPopper, type Instance as PopperInstance } from "@popperjs/core";
const wrapAround = (value: number, size: number): number => {
return ((value % size) + size) % size;
};
class Suggest<T> {
private owner: ISuggestOwner<T>;
private values: T[];
private suggestions: HTMLDivElement[];
private selectedItem: number;
private containerEl: HTMLElement;
constructor(
owner: ISuggestOwner<T>,
containerEl: HTMLElement,
scope: Scope
) {
this.owner = owner;
this.containerEl = containerEl;
containerEl.on(
"click",
".suggestion-item",
this.onSuggestionClick.bind(this)
);
containerEl.on(
"mousemove",
".suggestion-item",
this.onSuggestionMouseover.bind(this)
);
scope.register([], "ArrowUp", (event) => {
if (!event.isComposing) {
this.setSelectedItem(this.selectedItem - 1, true);
return false;
}
});
scope.register([], "ArrowDown", (event) => {
if (!event.isComposing) {
this.setSelectedItem(this.selectedItem + 1, true);
return false;
}
});
scope.register([], "Enter", (event) => {
if (!event.isComposing) {
this.useSelectedItem(event);
return false;
}
});
}
onSuggestionClick(event: MouseEvent, el: HTMLDivElement): void {
event.preventDefault();
const item = this.suggestions.indexOf(el);
this.setSelectedItem(item, false);
this.useSelectedItem(event);
}
onSuggestionMouseover(_event: MouseEvent, el: HTMLDivElement): void {
const item = this.suggestions.indexOf(el);
this.setSelectedItem(item, false);
}
setSuggestions(values: T[]) {
this.containerEl.empty();
const suggestionEls: HTMLDivElement[] = [];
values.forEach((value) => {
const suggestionEl = this.containerEl.createDiv("suggestion-item");
this.owner.renderSuggestion(value, suggestionEl);
suggestionEls.push(suggestionEl);
});
this.values = values;
this.suggestions = suggestionEls;
this.setSelectedItem(0, false);
}
useSelectedItem(event: MouseEvent | KeyboardEvent) {
const currentValue = this.values[this.selectedItem];
if (currentValue) {
this.owner.selectSuggestion(currentValue, event);
}
}
setSelectedItem(selectedIndex: number, scrollIntoView: boolean) {
const normalizedIndex = wrapAround(
selectedIndex,
this.suggestions.length
);
const prevSelectedSuggestion = this.suggestions[this.selectedItem];
const selectedSuggestion = this.suggestions[normalizedIndex];
prevSelectedSuggestion?.removeClass("is-selected");
selectedSuggestion?.addClass("is-selected");
this.selectedItem = normalizedIndex;
if (scrollIntoView) {
selectedSuggestion.scrollIntoView(false);
}
}
}
export abstract class TextInputSuggest<T> implements ISuggestOwner<T> {
private popper: PopperInstance;
private scope: Scope;
private suggestEl: HTMLElement;
private suggest: Suggest<T>;
constructor(
protected app: App,
protected inputEl: HTMLInputElement | HTMLTextAreaElement
) {
this.scope = new Scope();
this.suggestEl = createDiv("suggestion-container");
const suggestion = this.suggestEl.createDiv("suggestion");
this.suggest = new Suggest(this, suggestion, this.scope);
this.scope.register([], "Escape", this.close.bind(this));
this.inputEl.addEventListener("input", this.onInputChanged.bind(this));
this.inputEl.addEventListener("focus", this.onInputChanged.bind(this));
this.inputEl.addEventListener("blur", this.close.bind(this));
this.suggestEl.on(
"mousedown",
".suggestion-container",
(event: MouseEvent) => {
event.preventDefault();
}
);
}
async onInputChanged(): Promise<void> {
const inputStr = this.inputEl.value;
let suggestions = this.getSuggestions(inputStr);
if (suggestions instanceof Promise) {
suggestions = await suggestions;
}
if (!suggestions) {
this.close();
return;
}
if (suggestions.length > 0) {
this.suggest.setSuggestions(suggestions);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
this.open((<any>this.app).dom.appContainerEl, this.inputEl);
} else {
this.close();
}
}
open(container: HTMLElement, inputEl: HTMLElement): void {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(<any>this.app).keymap.pushScope(this.scope);
container.appendChild(this.suggestEl);
this.popper = createPopper(inputEl, this.suggestEl, {
placement: "bottom-start",
modifiers: [
{
name: "sameWidth",
enabled: true,
fn: ({ state, instance }) => {
// Note: positioning needs to be calculated twice -
// first pass - positioning it according to the width of the popper
// second pass - position it with the width bound to the reference element
// we need to early exit to avoid an infinite loop
const targetWidth = `${state.rects.reference.width}px`;
if (state.styles.popper.width === targetWidth) {
return;
}
state.styles.popper.width = targetWidth;
instance.update();
},
phase: "beforeWrite",
requires: ["computeStyles"],
},
],
});
}
close(): void {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(<any>this.app).keymap.popScope(this.scope);
this.suggest.setSuggestions([]);
if (this.popper) this.popper.destroy();
this.suggestEl.detach();
}
abstract getSuggestions(inputStr: string): T[] | Promise<T[]>;
abstract renderSuggestion(item: T, el: HTMLElement): void;
abstract selectSuggestion(item: T): void;
}

View File

@ -1,4 +0,0 @@
export { BookSuggest } from "./BookSuggest";
export { FieldSuggest } from "./FieldSuggest";
export { FileSuggest } from "./FileSuggest";
export { FolderSuggest } from "./FolderSuggest";