Skip to content

Commit

Permalink
4.1.5
Browse files Browse the repository at this point in the history
- Improved icon selection experience
  • Loading branch information
valentine195 committed May 11, 2021
1 parent cc6ab7e commit c7ed951
Show file tree
Hide file tree
Showing 9 changed files with 346 additions and 20 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,10 @@ Adds a "copy content" button to every admonition block.

# Version History

## 4.1.5

- Improved Admonition Icon selection experience

## 4.1.4

- Trimmed whitespace from content when copying to clipboard.
Expand Down
2 changes: 1 addition & 1 deletion manifest.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"id": "obsidian-admonition",
"name": "Admonition",
"version": "4.1.4",
"version": "4.1.5",
"minAppVersion": "0.11.0",
"description": "Admonition block-styled content for Obsidian.md",
"author": "Jeremy Valentine",
Expand Down
12 changes: 6 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "obsidian-admonition",
"version": "4.1.4",
"version": "4.1.5",
"description": "Admonition block-styled content for Obsidian.md",
"main": "main.js",
"scripts": {
Expand All @@ -11,9 +11,10 @@
"author": "Jeremy Valentine",
"license": "MIT",
"devDependencies": {
"@fortawesome/fontawesome-svg-core": "^1.2.35",
"@fortawesome/free-solid-svg-icons": "^5.15.1",
"@fortawesome/fontawesome-svg-core": "^1.2.32",
"@fortawesome/free-regular-svg-icons": "^5.15.3",
"@fortawesome/free-solid-svg-icons": "^5.15.1",
"@popperjs/core": "^2.9.2",
"@rollup/plugin-commonjs": "^15.1.0",
"@rollup/plugin-node-resolve": "^9.0.0",
"@rollup/plugin-typescript": "^6.0.0",
Expand All @@ -25,6 +26,5 @@
"rollup-plugin-css-only": "^3.1.0",
"tslib": "^2.0.3",
"typescript": "^4.0.3"
},
"dependencies": {}
}
}
}
10 changes: 8 additions & 2 deletions src/icons.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
import { fas } from "@fortawesome/free-solid-svg-icons";
import { faCopy } from "@fortawesome/free-regular-svg-icons";
import { fas } from "@fortawesome/free-solid-svg-icons";
import {
IconDefinition,
IconName,
findIconDefinition,
icon,
library
} from "@fortawesome/fontawesome-svg-core";

library.add(fas, faCopy);

export { icon, findIconDefinition };
export const iconNames = Object.values(fas).map(
(i: IconDefinition) => i.iconName
);

export { icon, findIconDefinition, IconName };
2 changes: 1 addition & 1 deletion src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ Object.fromEntries =

import "./main.css";
import AdmonitionSetting from "./settings";
import { findIconDefinition, icon } from "@fortawesome/fontawesome-svg-core";
import { findIconDefinition, icon } from "./icons";

const DEFAULT_APP_SETTINGS: ISettingsData = {
userAdmonitions: {},
Expand Down
306 changes: 306 additions & 0 deletions src/modals.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,306 @@
import {
App,
FuzzyMatch,
FuzzySuggestModal,
Scope,
SuggestModal,
TextComponent
} from "obsidian";
import { createPopper, Instance as PopperInstance } from "@popperjs/core";
import { findIconDefinition, icon, IconName } from "./icons";

class Suggester<T> {
owner: SuggestModal<T>;
items: T[];
suggestions: HTMLDivElement[];
selectedItem: number;
containerEl: HTMLElement;
constructor(
owner: SuggestModal<T>,
containerEl: HTMLElement,
scope: Scope
) {
this.containerEl = containerEl;
this.owner = owner;
containerEl.on(
"click",
".suggestion-item",
this.onSuggestionClick.bind(this)
);
containerEl.on(
"mousemove",
".suggestion-item",
this.onSuggestionMouseover.bind(this)
);

scope.register([], "ArrowUp", () => {
this.setSelectedItem(this.selectedItem - 1, true);
return false;
});

scope.register([], "ArrowDown", () => {
this.setSelectedItem(this.selectedItem + 1, true);
return false;
});

scope.register([], "Enter", (evt) => {
this.useSelectedItem(evt);
return false;
});

scope.register([], "Tab", (evt) => {
this.chooseSuggestion(evt);
return false;
});
}
chooseSuggestion(evt: KeyboardEvent) {
if (!this.items || !this.items.length) return;
const currentValue = this.items[this.selectedItem];
if (currentValue) {
this.owner.onChooseSuggestion(currentValue, evt);
}
}
onSuggestionClick(event: MouseEvent, el: HTMLDivElement): void {
event.preventDefault();
if (!this.suggestions || !this.suggestions.length) return;

const item = this.suggestions.indexOf(el);
this.setSelectedItem(item, false);
this.useSelectedItem(event);
}

onSuggestionMouseover(event: MouseEvent, el: HTMLDivElement): void {
if (!this.suggestions || !this.suggestions.length) return;
const item = this.suggestions.indexOf(el);
this.setSelectedItem(item, false);
}
empty() {
this.containerEl.empty();
}
setSuggestions(items: T[]) {
this.containerEl.empty();
const els: HTMLDivElement[] = [];

items.forEach((item) => {
const suggestionEl = this.containerEl.createDiv("suggestion-item");
this.owner.renderSuggestion(item, suggestionEl);
els.push(suggestionEl);
});
this.items = items;
this.suggestions = els;
this.setSelectedItem(0, false);
}
useSelectedItem(event: MouseEvent | KeyboardEvent) {
if (!this.items || !this.items.length) return;
const currentValue = this.items[this.selectedItem];
if (currentValue) {
this.owner.selectSuggestion(currentValue, event);
}
}
wrap(value: number, size: number): number {
return ((value % size) + size) % size;
}
setSelectedItem(index: number, scroll: boolean) {
const nIndex = this.wrap(index, this.suggestions.length);
const prev = this.suggestions[this.selectedItem];
const next = this.suggestions[nIndex];

if (prev) prev.removeClass("is-selected");
if (next) next.addClass("is-selected");

this.selectedItem = nIndex;

if (scroll) {
next.scrollIntoView(false);
}
}
}

export abstract class SuggestionModal<T> extends FuzzySuggestModal<T> {
items: T[] = [];
suggestions: HTMLDivElement[];
popper: PopperInstance;
scope: Scope = new Scope();
suggester: Suggester<FuzzyMatch<T>>;
suggestEl: HTMLDivElement;
promptEl: HTMLDivElement;
emptyStateText: string = "No match found";
limit: number = 100;
constructor(app: App, inputEl: HTMLInputElement, items: T[]) {
super(app);
this.inputEl = inputEl;
this.items = items;

this.suggestEl = createDiv("suggestion-container");

this.contentEl = this.suggestEl.createDiv("suggestion");

this.suggester = new Suggester(this, this.contentEl, 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();
}
);
}
empty() {
this.suggester.empty();
}
onInputChanged(): void {
const inputStr = this.modifyInput(this.inputEl.value);
const suggestions = this.getSuggestions(inputStr);
if (suggestions.length > 0) {
this.suggester.setSuggestions(suggestions.slice(0, this.limit));
} else {
this.onNoSuggestion();
}
this.open();
}

modifyInput(input: string): string {
return input;
}
onNoSuggestion() {
this.empty();
this.renderSuggestion(
null,
this.contentEl.createDiv("suggestion-item")
);
}
open(): void {
// TODO: Figure out a better way to do this. Idea from Periodic Notes plugin
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(<any>this.app).keymap.pushScope(this.scope);

document.body.appendChild(this.suggestEl);
this.popper = createPopper(this.inputEl, this.suggestEl, {
placement: "bottom-start",
modifiers: [
{
name: "offset",
options: {
offset: [0, 10]
}
},
{
name: "flip",
options: {
fallbackPlacements: ["top"]
}
}
]
});
}

close(): void {
// TODO: Figure out a better way to do this. Idea from Periodic Notes plugin
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(<any>this.app).keymap.popScope(this.scope);

this.suggester.setSuggestions([]);
if (this.popper) {
this.popper.destroy();
}

this.suggestEl.detach();
}
createPrompt(prompts: HTMLSpanElement[]) {
if (!this.promptEl)
this.promptEl = this.suggestEl.createDiv("prompt-instructions");
let prompt = this.promptEl.createDiv("prompt-instruction");
for (let p of prompts) {
prompt.appendChild(p);
}
}
abstract onChooseItem(item: T, evt: MouseEvent | KeyboardEvent): void;
abstract getItemText(arg: T): string;
abstract getItems(): T[];
}

export class IconSuggestionModal extends SuggestionModal<IconName> {
icons: IconName[];
icon: IconName;
text: TextComponent;
constructor(app: App, input: TextComponent, items: IconName[]) {
super(app, input.inputEl, items);
this.icons = [...items];
this.text = input;

this.createPrompts();

this.inputEl.addEventListener("input", this.getItem.bind(this));
}
createPrompts() {}
getItem() {
const v = this.inputEl.value,
icon = this.icons.find((iconName) => iconName === v.trim());
if (icon == this.icon) return;
this.icon = icon;
if (this.icons) this.onInputChanged();
}
getItemText(item: IconName) {
return item;
}
onChooseItem(item: IconName) {
this.text.setValue(item);
this.icon = item;
}
selectSuggestion({ item }: FuzzyMatch<IconName>) {
this.text.setValue(item);
this.onClose();

this.close();
}
renderSuggestion(result: FuzzyMatch<IconName>, el: HTMLElement) {
let { item, match: matches } = result || {};
let content = el.createDiv({
cls: "suggestion-content icon"
});
if (!item) {
content.setText(this.emptyStateText);
content.parentElement.addClass("is-selected");
return;
}

const matchElements = matches.matches.map((m) => {
return createSpan("suggestion-highlight");
});
for (let i = 0; i < item.length; i++) {
let match = matches.matches.find((m) => m[0] === i);
if (match) {
let element = matchElements[matches.matches.indexOf(match)];
content.appendChild(element);
element.appendText(item.substring(match[0], match[1]));

i += match[1] - match[0] - 1;
continue;
}

content.appendText(item[i]);
}

const iconDiv = createDiv({
cls: "suggestion-flair"
});
iconDiv.appendChild(
icon(
findIconDefinition({
iconName: item,
prefix: "fas"
})
).node[0]
);

content.prepend(iconDiv);
}
getItems() {
return this.icons;
}
}
Loading

0 comments on commit c7ed951

Please sign in to comment.