Skip to content

Commit

Permalink
fix: #67
Browse files Browse the repository at this point in the history
  • Loading branch information
lazyloong committed Nov 13, 2024
1 parent 216dadd commit b3508aa
Show file tree
Hide file tree
Showing 6 changed files with 213 additions and 11 deletions.
2 changes: 1 addition & 1 deletion manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@
"author": "lazyloong",

"minAppVersion": "1.0.0",
"version": "2.27.8"
"version": "2.27.9"
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@
"builtin-modules": "^3.3.0",
"obsidian": "github:obsidianmd/obsidian-api",
"obsidian-plugin-cli": "^0.9.0",
"templater": "github:SilentVoid13/Templater",
"typescript": "^5.4.3"
},
"dependencies": {
"@popperjs/core": "^2.11.8",
"lodash": "^4.17.21"
}
}
32 changes: 25 additions & 7 deletions src/modal/fileModal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ export default class FileModal extends FuzzyModal<Item> {
});
}
this.setInstructions(prompt);
this.addTagInput();
}
addTagInput(): void {
let inputContainerEl = this.modalEl.querySelector(
Expand All @@ -130,16 +131,28 @@ export default class FileModal extends FuzzyModal<Item> {
else this.tags = value.split(",").map((t) => t.trim());
this.onInput();
});
if (this.plugin.settings.file.searchWithTag) this.tagInput.show();
}
onOpen(): void {
super.onOpen();
if (!this.tagInput) this.addTagInput();

this.tags = [];
this.tagInput.setValue("");
let inputContainerEl = this.modalEl.querySelector(
".prompt-input-container"
) as HTMLInputElement;
let clearButton = inputContainerEl.querySelector(
".search-input-clear-button"
) as HTMLDivElement;
if (this.plugin.settings.file.searchWithTag) {
this.tagInput.show();
clearButton.style.marginRight = "25%";
} else {
this.tagInput.hide();
clearButton.style.marginRight = "0";
}
}
onClose(): void {
super.onClose();
this.tags = [];
this.tagInput.setValue("");
}
getEmptyInputSuggestions(): MatchData[] {
if (this.tags.length == 0) {
Expand All @@ -165,7 +178,7 @@ export default class FileModal extends FuzzyModal<Item> {
return (
tagArray &&
tagArray.length != 0 &&
tagArray.some((tag) => this.tags.some((t) => tag.startsWith(t)))
this.tags.every((t) => tagArray.some((tt) => tt.startsWith(t)))
);
})
.map((p) => ({
Expand Down Expand Up @@ -276,7 +289,11 @@ export default class FileModal extends FuzzyModal<Item> {
result = result.filter((matchData) => {
if (!matchData.item.file) return;
let tagArray = getFileTagArray(matchData.item.file);
return tagArray?.some((tag) => this.tags.some((t) => tag.startsWith(t)));
return (
tagArray &&
tagArray.length != 0 &&
this.tags.every((t) => tagArray.some((tt) => tt.startsWith(t)))
);
});
}
return result;
Expand Down Expand Up @@ -614,7 +631,8 @@ class TagInput extends TextComponent {
super(inputEl);
this.hide();
this.setPlaceholder("标签");
this.inputEl.classList.add("prompt-input");
this.inputEl.addClasses(["prompt-input", "fz-tag-input"]);

this.inputEl.style.width = "30%";
this.inputEl.style.borderLeft = "2px solid var(--background-primary)";
let tagSuggest = new PinyinSuggest(this.inputEl, plugin);
Expand Down
1 change: 1 addition & 0 deletions src/modal/modal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export default abstract class FuzzyModal<T extends Item> extends SuggestModal<Ma
this.useInput = false;
this.plugin = plugin;
this.historyMatchData = new HistoryMatchDataNode("\0");
this.containerEl.addClass("fz-modal");

this.scope.register([], "Backspace", async (e) => {
if (this.plugin.settings.global.closeWithBackspace && this.inputEl.value === "") {
Expand Down
4 changes: 2 additions & 2 deletions src/utils/pinyinSuggest.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import ThePlugin from "@/main";
import { TextInputSuggest } from "templater/src/settings/suggesters/suggest";
import { TextInputSuggest } from "./suggest";
import { SuggestionRenderer } from "./suggestionRenderer";
import { MatchData, Item } from "./type";

export class PinyinSuggest extends TextInputSuggest<MatchData<Item>> {
getItemFunction: (query: string) => MatchData<Item>[];
plugin: ThePlugin;
constructor(inputEl: HTMLInputElement | HTMLTextAreaElement, plugin: ThePlugin) {
super(inputEl);
super(inputEl, plugin.app);
this.plugin = plugin;
}
getSuggestions(inputStr: string): MatchData<Item>[] {
Expand Down
183 changes: 183 additions & 0 deletions src/utils/suggest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
// Credits go to Liam's Periodic Notes Plugin: https://github.com/liamcain/obsidian-periodic-notes

import { App, ISuggestOwner, Scope } from "obsidian";
import { createPopper, 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> {
protected inputEl: HTMLInputElement | HTMLTextAreaElement;

private popper: PopperInstance;
private scope: Scope;
private suggestEl: HTMLElement;
private suggest: Suggest<T>;
private app: App;

constructor(inputEl: HTMLInputElement | HTMLTextAreaElement, app: App) {
this.inputEl = inputEl;
this.app = app;
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();
});
}

onInputChanged(): void {
const inputStr = this.inputEl.value;
const suggestions = this.getSuggestions(inputStr);

if (!suggestions) {
this.close();
return;
}

if (suggestions.length > 0) {
this.suggest.setSuggestions(suggestions);
this.open(app.dom.appContainerEl, this.inputEl);
} else {
this.close();
}
}

open(container: HTMLElement, inputEl: HTMLElement): void {
// eslint-disable-next-line @typescript-eslint/no-explicit-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 {
this.app.keymap.popScope(this.scope);

this.suggest.setSuggestions([]);
if (this.popper) this.popper.destroy();
this.suggestEl.detach();
}

abstract getSuggestions(inputStr: string): T[];
abstract renderSuggestion(item: T, el: HTMLElement): void;
abstract selectSuggestion(item: T): void;
}

0 comments on commit b3508aa

Please sign in to comment.