diff --git a/manifest-beta.json b/manifest-beta.json index 401bd19..2109fdc 100644 --- a/manifest-beta.json +++ b/manifest-beta.json @@ -1,7 +1,7 @@ { "id": "obsidian-excalidraw-plugin", "name": "Excalidraw", - "version": "2.6.8-beta-3", + "version": "2.6.8", "minAppVersion": "1.1.6", "description": "An Obsidian plugin to edit and view Excalidraw drawings", "author": "Zsolt Viczian", diff --git a/manifest.json b/manifest.json index fe6c466..9210142 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "obsidian-excalidraw-plugin", "name": "Excalidraw", - "version": "2.6.7", + "version": "2.6.8", "minAppVersion": "1.1.6", "description": "An Obsidian plugin to edit and view Excalidraw drawings", "author": "Zsolt Viczian", diff --git a/package.json b/package.json index d9ba926..3658656 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ "license": "MIT", "dependencies": { "@popperjs/core": "^2.11.8", - "@zsviczian/excalidraw": "0.17.6-17", + "@zsviczian/excalidraw": "0.17.6-18", "chroma-js": "^2.4.2", "clsx": "^2.0.0", "@zsviczian/colormaster": "^1.2.2", diff --git a/src/dialogs/FieldSuggester.ts b/src/Components/Suggesters/FieldSuggester.ts similarity index 92% rename from src/dialogs/FieldSuggester.ts rename to src/Components/Suggesters/FieldSuggester.ts index 95dea49..a3a3eb2 100644 --- a/src/dialogs/FieldSuggester.ts +++ b/src/Components/Suggesters/FieldSuggester.ts @@ -6,13 +6,16 @@ import { EditorSuggestTriggerInfo, TFile, } from "obsidian"; -import { FRONTMATTER_KEYS_INFO } from "./SuggesterInfo"; +import { FRONTMATTER_KEYS_INFO } from "../../dialogs/SuggesterInfo"; import { EXCALIDRAW_AUTOMATE_INFO, EXCALIDRAW_SCRIPTENGINE_INFO, -} from "./SuggesterInfo"; -import type ExcalidrawPlugin from "../main"; +} from "../../dialogs/SuggesterInfo"; +import type ExcalidrawPlugin from "../../main"; +/** + * The field suggester recommends document properties in source mode, ea and utils function and attribute names. + */ export class FieldSuggester extends EditorSuggest { plugin: ExcalidrawPlugin; suggestType: "ea" | "excalidraw" | "utils"; diff --git a/src/Components/Suggesters/FileSuggestionModal.ts b/src/Components/Suggesters/FileSuggestionModal.ts new file mode 100644 index 0000000..a746150 --- /dev/null +++ b/src/Components/Suggesters/FileSuggestionModal.ts @@ -0,0 +1,142 @@ +import { + FuzzyMatch, + TFile, + CachedMetadata, + TextComponent, + App, + setIcon, +} from "obsidian"; +import { SuggestionModal } from "./SuggestionModal"; +import { t } from "src/lang/helpers"; +import { LinkSuggestion } from "src/types/types"; +import ExcalidrawPlugin from "src/main"; +import { AUDIO_TYPES, CODE_TYPES, ICON_NAME, IMAGE_TYPES, VIDEO_TYPES } from "src/constants/constants"; + +export class FileSuggestionModal extends SuggestionModal { + text: TextComponent; + cache: CachedMetadata; + filesAndAliases: LinkSuggestion[]; + file: TFile; + constructor(app: App, input: TextComponent, items: TFile[], private plugin: ExcalidrawPlugin) { + const filesAndAliases = []; + for (const file of items) { + const path = file.path; + filesAndAliases.push({ file, path, alias: "" }); + const metadata = app.metadataCache.getFileCache(file); // Get metadata for the file + const aliases = metadata?.frontmatter?.aliases || []; // Check for frontmatter aliases + + for (const alias of aliases) { + if(!alias) continue; // Skip empty aliases + filesAndAliases.push({ file, path, alias }); + } + } + super(app, input.inputEl, filesAndAliases); + this.limit = 20; + this.filesAndAliases = filesAndAliases; + this.text = input; + this.suggestEl.style.maxWidth = "100%"; + this.suggestEl.style.width = `${input.inputEl.clientWidth}px`; + this.inputEl.addEventListener("input", () => this.getFile()); + this.setPlaceholder(t("SELECT_FILE_TO_INSERT")); + this.emptyStateText = t("NO_MATCH"); + } + + getFile() { + const v = this.inputEl.value; + const file = this.app.vault.getAbstractFileByPath(v); + if (file === this.file) { + return; + } + if (!(file instanceof TFile)) { + return; + } + this.file = file; + + this.onInputChanged(); + } + + getSelectedItem() { + return this.file; + } + + getItemText(item: LinkSuggestion) { + return `${item.file.path}${item.alias ? `|${item.alias}` : ""}`; + } + + onChooseItem(item: LinkSuggestion) { + this.file = item.file; + this.text.setValue(this.getItemText(item)); + this.text.onChanged(); + } + + selectSuggestion({ item }: FuzzyMatch) { + this.file = item.file; + this.text.setValue(this.getItemText(item)); + this.onClose(); + this.text.onChanged(); + this.close(); + } + + renderSuggestion(result: FuzzyMatch, itemEl: HTMLElement) { + const { item, match: matches } = result || {}; + itemEl.addClass("mod-complex"); + const contentEl = itemEl.createDiv("suggestion-content"); + const auxEl = itemEl.createDiv("suggestion-aux"); + const titleEl = contentEl.createDiv("suggestion-title"); + const noteEl = contentEl.createDiv("suggestion-note"); + + //el.style.flexDirection = "column"; + //content.style.flexDirection = "initial"; + + if (!item) { + titleEl.setText(this.emptyStateText); + itemEl.addClass("is-selected"); + return; + } + + const path = item.file?.path ?? item.path; + const pathLength = path.length - item.file.name.length; + const matchElements = matches.matches.map((m) => { + return createSpan("suggestion-highlight"); + }); + const itemText = this.getItemText(item); + for (let i = pathLength; i < itemText.length; i++) { + const match = matches.matches.find((m) => m[0] === i); + if (match) { + const element = matchElements[matches.matches.indexOf(match)]; + titleEl.appendChild(element); + element.appendText(itemText.substring(match[0], match[1])); + + i += match[1] - match[0] - 1; + continue; + } + + titleEl.appendText(itemText[i]); + } + noteEl.setText(path); + + if(this.plugin.isExcalidrawFile(item.file)) { + setIcon(auxEl, ICON_NAME); + } else if (item.file.extension === "md") { + setIcon(auxEl, "square-pen"); + } else if (IMAGE_TYPES.includes(item.file.extension)) { + setIcon(auxEl, "image"); + } else if (VIDEO_TYPES.includes(item.file.extension)) { + setIcon(auxEl, "monitor-play"); + } else if (AUDIO_TYPES.includes(item.file.extension)) { + setIcon(auxEl, "file-audio"); + } else if (CODE_TYPES.includes(item.file.extension)) { + setIcon(auxEl, "file-code"); + } else if (item.file.extension === "canvas") { + setIcon(auxEl, "layout-dashboard"); + } else if (item.file.extension === "pdf") { + setIcon(auxEl, "book-open-text"); + } else { + auxEl.setText(item.file.extension); + } + } + + getItems() { + return this.filesAndAliases; + } +} \ No newline at end of file diff --git a/src/Components/Suggesters/FolderSuggestionModal.ts b/src/Components/Suggesters/FolderSuggestionModal.ts new file mode 100644 index 0000000..b928dd7 --- /dev/null +++ b/src/Components/Suggesters/FolderSuggestionModal.ts @@ -0,0 +1,87 @@ +import { + FuzzyMatch, + CachedMetadata, + TextComponent, + App, + TFolder, +} from "obsidian"; +import { SuggestionModal } from "./SuggestionModal"; + +export class FolderSuggestionModal extends SuggestionModal { + text: TextComponent; + cache: CachedMetadata; + folders: TFolder[]; + folder: TFolder; + constructor(app: App, input: TextComponent, items: TFolder[]) { + super(app, input.inputEl, items); + this.folders = [...items]; + this.text = input; + + this.inputEl.addEventListener("input", () => this.getFolder()); + } + getFolder() { + const v = this.inputEl.value; + const folder = this.app.vault.getAbstractFileByPath(v); + if (folder == this.folder) { + return; + } + if (!(folder instanceof TFolder)) { + return; + } + this.folder = folder; + + this.onInputChanged(); + } + getItemText(item: TFolder) { + return item.path; + } + onChooseItem(item: TFolder) { + this.text.setValue(item.path); + this.folder = item; + } + selectSuggestion({ item }: FuzzyMatch) { + const link = item.path; + + this.text.setValue(link); + this.onClose(); + + this.close(); + } + renderSuggestion(result: FuzzyMatch, el: HTMLElement) { + const { item, match: matches } = result || {}; + const content = el.createDiv({ + cls: "suggestion-content", + }); + if (!item) { + content.setText(this.emptyStateText); + content.parentElement.addClass("is-selected"); + return; + } + + const pathLength = item.path.length - item.name.length; + const matchElements = matches.matches.map((m) => { + return createSpan("suggestion-highlight"); + }); + for (let i = pathLength; i < item.path.length; i++) { + const match = matches.matches.find((m) => m[0] === i); + if (match) { + const element = matchElements[matches.matches.indexOf(match)]; + content.appendChild(element); + element.appendText(item.path.substring(match[0], match[1])); + + i += match[1] - match[0] - 1; + continue; + } + + content.appendText(item.path[i]); + } + el.createDiv({ + cls: "suggestion-note", + text: item.path, + }); + } + + getItems() { + return this.folders; + } +} \ No newline at end of file diff --git a/src/Components/Suggesters/PathSuggestionModal.ts b/src/Components/Suggesters/PathSuggestionModal.ts new file mode 100644 index 0000000..8146309 --- /dev/null +++ b/src/Components/Suggesters/PathSuggestionModal.ts @@ -0,0 +1,163 @@ +import { + FuzzyMatch, + TFile, + BlockCache, + HeadingCache, + CachedMetadata, + TextComponent, + App, +} from "obsidian"; +import { SuggestionModal } from "./SuggestionModal"; + +export class PathSuggestionModal extends SuggestionModal< + TFile | BlockCache | HeadingCache +> { + file: TFile; + files: TFile[]; + text: TextComponent; + cache: CachedMetadata; + constructor(app: App, input: TextComponent, items: TFile[]) { + super(app, input.inputEl, items); + this.files = [...items]; + this.text = input; + //this.getFile(); + + this.inputEl.addEventListener("input", this.getFile.bind(this)); + } + + getFile() { + const v = this.inputEl.value; + const file = this.app.metadataCache.getFirstLinkpathDest( + v.split(/[\^#]/).shift() || "", + "", + ); + if (file == this.file) { + return; + } + this.file = file; + if (this.file) { + this.cache = this.app.metadataCache.getFileCache(this.file); + } + this.onInputChanged(); + } + getItemText(item: TFile | HeadingCache | BlockCache) { + if (item instanceof TFile) { + return item.path; + } + if (Object.prototype.hasOwnProperty.call(item, "heading")) { + return (item).heading; + } + if (Object.prototype.hasOwnProperty.call(item, "id")) { + return (item).id; + } + } + onChooseItem(item: TFile | HeadingCache | BlockCache) { + if (item instanceof TFile) { + this.text.setValue(item.basename); + this.file = item; + this.cache = this.app.metadataCache.getFileCache(this.file); + } else if (Object.prototype.hasOwnProperty.call(item, "heading")) { + this.text.setValue( + `${this.file.basename}#${(item).heading}`, + ); + } else if (Object.prototype.hasOwnProperty.call(item, "id")) { + this.text.setValue(`${this.file.basename}^${(item).id}`); + } + } + selectSuggestion({ item }: FuzzyMatch) { + let link: string; + if (item instanceof TFile) { + link = item.basename; + } else if (Object.prototype.hasOwnProperty.call(item, "heading")) { + link = `${this.file.basename}#${(item).heading}`; + } else if (Object.prototype.hasOwnProperty.call(item, "id")) { + link = `${this.file.basename}^${(item).id}`; + } + + this.text.setValue(link); + this.onClose(); + + this.close(); + } + renderSuggestion( + result: FuzzyMatch, + el: HTMLElement, + ) { + const { item, match: matches } = result || {}; + const content = el.createDiv({ + cls: "suggestion-content", + }); + if (!item) { + content.setText(this.emptyStateText); + content.parentElement.addClass("is-selected"); + return; + } + + if (item instanceof TFile) { + const pathLength = item.path.length - item.name.length; + const matchElements = matches.matches.map((m) => { + return createSpan("suggestion-highlight"); + }); + for ( + let i = pathLength; + i < item.path.length - item.extension.length - 1; + i++ + ) { + const match = matches.matches.find((m) => m[0] === i); + if (match) { + const element = matchElements[matches.matches.indexOf(match)]; + content.appendChild(element); + element.appendText(item.path.substring(match[0], match[1])); + + i += match[1] - match[0] - 1; + continue; + } + + content.appendText(item.path[i]); + } + el.createDiv({ + cls: "suggestion-note", + text: item.path, + }); + } else if (Object.prototype.hasOwnProperty.call(item, "heading")) { + content.setText((item).heading); + content.prepend( + createSpan({ + cls: "suggestion-flair", + text: `H${(item).level}`, + }), + ); + } else if (Object.prototype.hasOwnProperty.call(item, "id")) { + content.setText((item).id); + } + } + get headings() { + if (!this.file) { + return []; + } + if (!this.cache) { + this.cache = this.app.metadataCache.getFileCache(this.file); + } + return this.cache.headings || []; + } + get blocks() { + if (!this.file) { + return []; + } + if (!this.cache) { + this.cache = this.app.metadataCache.getFileCache(this.file); + } + return Object.values(this.cache.blocks || {}) || []; + } + getItems() { + const v = this.inputEl.value; + if (/#/.test(v)) { + this.modifyInput = (i) => i.split(/#/).pop(); + return this.headings; + } else if (/\^/.test(v)) { + this.modifyInput = (i) => i.split(/\^/).pop(); + return this.blocks; + } + return this.files; + } +} diff --git a/src/Components/Suggesters/Suggester.ts b/src/Components/Suggesters/Suggester.ts new file mode 100644 index 0000000..4cbad26 --- /dev/null +++ b/src/Components/Suggesters/Suggester.ts @@ -0,0 +1,119 @@ +import { + SuggestModal, + Scope, +} from "obsidian"; + +export class Suggester { + owner: SuggestModal; + items: T[]; + suggestions: HTMLDivElement[]; + selectedItem: number; + containerEl: HTMLElement; + constructor(owner: SuggestModal, 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); + } + } +} diff --git a/src/Components/Suggesters/SuggestionModal.ts b/src/Components/Suggesters/SuggestionModal.ts new file mode 100644 index 0000000..e1b001b --- /dev/null +++ b/src/Components/Suggesters/SuggestionModal.ts @@ -0,0 +1,128 @@ +import { + FuzzyMatch, + App, + FuzzySuggestModal, + Scope, +} from "obsidian"; +import { createPopper, Instance as PopperInstance } from "@popperjs/core"; +import { Suggester } from "./Suggester"; + +export abstract class SuggestionModal extends FuzzySuggestModal { + items: T[] = []; + suggestions: HTMLDivElement[]; + popper: WeakRef; + //@ts-ignore + scope: Scope = new Scope(this.app.scope); + suggester: Suggester>; + suggestEl: HTMLDivElement; + promptEl: HTMLDivElement; + emptyStateText: string = "No match found"; + limit: number = 100; + shouldNotOpen: boolean; + 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.onEscape.bind(this)); + this.inputEl.addEventListener("input", this.onInputChanged.bind(this)); + this.inputEl.addEventListener("focus", this.onFocus.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 { + if (this.shouldNotOpen) { + return; + } + 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(); + } + + onFocus(): void { + this.shouldNotOpen = false; + this.onInputChanged(); + } + + 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 + this.app.keymap.pushScope(this.scope); + this.inputEl.ownerDocument.body.appendChild(this.suggestEl); + this.popper = new WeakRef(createPopper(this.inputEl, this.suggestEl, { + placement: "bottom-start", + modifiers: [ + { + name: "offset", + options: { + offset: [0, 10], + }, + }, + { + name: "flip", + options: { + fallbackPlacements: ["top"], + }, + }, + ], + })); + } + + onEscape(): void { + this.close(); + this.shouldNotOpen = true; + } + + close(): void { + // TODO: Figure out a better way to do this. Idea from Periodic Notes plugin + this.app.keymap.popScope(this.scope); + this.suggester.setSuggestions([]); + if (this.popper?.deref()) { + this.popper.deref().destroy(); + } + this.inputEl.removeEventListener("input", this.onInputChanged.bind(this)); + this.inputEl.removeEventListener("focus", this.onFocus.bind(this)); + this.inputEl.removeEventListener("blur", this.close.bind(this)); + this.suggestEl.detach(); + } + + createPrompt(prompts: HTMLSpanElement[]) { + if (!this.promptEl) { + this.promptEl = this.suggestEl.createDiv("prompt-instructions"); + } + const prompt = this.promptEl.createDiv("prompt-instruction"); + for (const p of prompts) { + prompt.appendChild(p); + } + } + + abstract onChooseItem(item: T, evt: MouseEvent | KeyboardEvent): void; + abstract getItemText(arg: T): string; + abstract getItems(): T[]; +} \ No newline at end of file diff --git a/src/EmbeddedFileLoader.ts b/src/EmbeddedFileLoader.ts index 27b5eb2..34900e3 100644 --- a/src/EmbeddedFileLoader.ts +++ b/src/EmbeddedFileLoader.ts @@ -767,7 +767,7 @@ export class EmbeddedFilesLoader { }, 1200); const iterator = loadIterator.bind(this)(); - const concurency = 5; + const concurency = 3; await new PromisePool(iterator, concurency).all(); clearInterval(addFilesTimer); diff --git a/src/ExcalidrawView.ts b/src/ExcalidrawView.ts index 1fa195c..27fec20 100644 --- a/src/ExcalidrawView.ts +++ b/src/ExcalidrawView.ts @@ -3267,6 +3267,7 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{ markdownlink: string, path: string, alias: string, + originalLink?: string, ) { (process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.addLink, "ExcalidrawView.addLink", markdownlink, path, alias); const api = this.excalidrawAPI as ExcalidrawImperativeAPI; @@ -3280,16 +3281,28 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{ } const selectedElementId = Object.keys(api.getAppState().selectedElementIds)[0]; const selectedElement = api.getSceneElements().find(el=>el.id === selectedElementId); - if(!selectedElement || (selectedElement && selectedElement.link !== null)) { + if(!selectedElement || (!Boolean(originalLink) && (selectedElement && selectedElement.link !== null) )) { if(selectedElement) new Notice("Selected element already has a link. Inserting link as text."); this.addText(markdownlink); return; } const ea = getEA(this) as ExcalidrawAutomate; ea.copyViewElementsToEAforEditing([selectedElement]); + if(originalLink?.match(/\[\[(.*?)\]\]/)?.[1]) { + markdownlink = originalLink.replace(/(\[\[.*?\]\])/,markdownlink); + } ea.getElement(selectedElementId).link = markdownlink; await ea.addElementsToView(false, true); ea.destroy(); + if(Boolean(originalLink)) { + this.updateScene({ + appState: { + showHyperlinkPopup: { + newValue : "info", oldValue : "editor" + } + } + }); + } } public async addText ( @@ -5094,6 +5107,15 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{ new Notice("Image successfully converted to local file"); } + private insertLinkAction(linkVal: string) { + let link = linkVal.match(/\[\[(.*?)\]\]/)?.[1]; + if(!link) { + link = linkVal.replaceAll("[","").replaceAll("]",""); + link = link.split("|")[0].trim(); + } + this.plugin.insertLinkDialog.start(this.file.path, (markdownlink: string, path:string, alias:string) => this.addLink(markdownlink, path, alias, linkVal), link); + } + private onContextMenu(elements: readonly ExcalidrawElement[], appState: AppState, onClose: (callback?: () => void) => void) { const React = this.packages.react; const contextMenuActions = []; @@ -5916,6 +5938,7 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{ renderEmbeddable: this.renderEmbeddable.bind(this), renderMermaid: shouldRenderMermaid, showDeprecatedFonts: true, + insertLinkAction: this.insertLinkAction.bind(this), }, this.renderCustomActionsMenu(), this.renderWelcomeScreen(), diff --git a/src/constants/constants.ts b/src/constants/constants.ts index 59ac165..cbecf5f 100644 --- a/src/constants/constants.ts +++ b/src/constants/constants.ts @@ -195,6 +195,10 @@ export const ANIMATED_IMAGE_TYPES = ["gif", "webp", "apng", "svg"]; export const EXPORT_TYPES = ["svg", "dark.svg", "light.svg", "png", "dark.png", "light.png"]; export const MAX_IMAGE_SIZE = 500; +export const VIDEO_TYPES = ["mp4", "webm", "ogv", "mov", "mkv"]; +export const AUDIO_TYPES = ["mp3", "wav", "m4a", "3gp", "flac", "ogg", "oga", "opus"]; +export const CODE_TYPES = ["json", "css", "js"]; + export const FRONTMATTER_KEYS:{[key:string]: {name: string, type: string, depricated?:boolean}} = { "plugin": {name: "excalidraw-plugin", type: "text"}, "export-transparent": {name: "excalidraw-export-transparent", type: "checkbox"}, diff --git a/src/dialogs/FolderSuggester.ts b/src/dialogs/FolderSuggester.ts deleted file mode 100644 index 2a826af..0000000 --- a/src/dialogs/FolderSuggester.ts +++ /dev/null @@ -1,569 +0,0 @@ -import { - FuzzyMatch, - TFile, - BlockCache, - HeadingCache, - CachedMetadata, - TextComponent, - App, - TFolder, - FuzzySuggestModal, - SuggestModal, - Scope, -} from "obsidian"; -import { createPopper, Instance as PopperInstance } from "@popperjs/core"; - -class Suggester { - owner: SuggestModal; - items: T[]; - suggestions: HTMLDivElement[]; - selectedItem: number; - containerEl: HTMLElement; - constructor(owner: SuggestModal, 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 extends FuzzySuggestModal { - items: T[] = []; - suggestions: HTMLDivElement[]; - popper: WeakRef; - //@ts-ignore - scope: Scope = new Scope(this.app.scope); - suggester: Suggester>; - suggestEl: HTMLDivElement; - promptEl: HTMLDivElement; - emptyStateText: string = "No match found"; - limit: number = 100; - shouldNotOpen: boolean; - 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.onEscape.bind(this)); - - this.inputEl.addEventListener("input", this.onInputChanged.bind(this)); - this.inputEl.addEventListener("focus", this.onFocus.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 { - if (this.shouldNotOpen) { - return; - } - 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(); - } - onFocus(): void { - this.shouldNotOpen = false; - this.onInputChanged(); - } - 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 - this.app.keymap.pushScope(this.scope); - - this.inputEl.ownerDocument.body.appendChild(this.suggestEl); - this.popper = new WeakRef(createPopper(this.inputEl, this.suggestEl, { - placement: "bottom-start", - modifiers: [ - { - name: "offset", - options: { - offset: [0, 10], - }, - }, - { - name: "flip", - options: { - fallbackPlacements: ["top"], - }, - }, - ], - })); - } - - onEscape(): void { - this.close(); - this.shouldNotOpen = true; - } - close(): void { - // TODO: Figure out a better way to do this. Idea from Periodic Notes plugin - this.app.keymap.popScope(this.scope); - - this.suggester.setSuggestions([]); - if (this.popper?.deref()) { - this.popper.deref().destroy(); - } - - this.inputEl.removeEventListener("input", this.onInputChanged.bind(this)); - this.inputEl.removeEventListener("focus", this.onFocus.bind(this)); - this.inputEl.removeEventListener("blur", this.close.bind(this)); - - this.suggestEl.detach(); - } - createPrompt(prompts: HTMLSpanElement[]) { - if (!this.promptEl) { - this.promptEl = this.suggestEl.createDiv("prompt-instructions"); - } - const prompt = this.promptEl.createDiv("prompt-instruction"); - for (const p of prompts) { - prompt.appendChild(p); - } - } - abstract onChooseItem(item: T, evt: MouseEvent | KeyboardEvent): void; - abstract getItemText(arg: T): string; - abstract getItems(): T[]; -} - -export class PathSuggestionModal extends SuggestionModal< - TFile | BlockCache | HeadingCache -> { - file: TFile; - files: TFile[]; - text: TextComponent; - cache: CachedMetadata; - constructor(app: App, input: TextComponent, items: TFile[]) { - super(app, input.inputEl, items); - this.files = [...items]; - this.text = input; - //this.getFile(); - - this.inputEl.addEventListener("input", this.getFile.bind(this)); - } - - getFile() { - const v = this.inputEl.value; - const file = this.app.metadataCache.getFirstLinkpathDest( - v.split(/[\^#]/).shift() || "", - "", - ); - if (file == this.file) { - return; - } - this.file = file; - if (this.file) { - this.cache = this.app.metadataCache.getFileCache(this.file); - } - this.onInputChanged(); - } - getItemText(item: TFile | HeadingCache | BlockCache) { - if (item instanceof TFile) { - return item.path; - } - if (Object.prototype.hasOwnProperty.call(item, "heading")) { - return (item).heading; - } - if (Object.prototype.hasOwnProperty.call(item, "id")) { - return (item).id; - } - } - onChooseItem(item: TFile | HeadingCache | BlockCache) { - if (item instanceof TFile) { - this.text.setValue(item.basename); - this.file = item; - this.cache = this.app.metadataCache.getFileCache(this.file); - } else if (Object.prototype.hasOwnProperty.call(item, "heading")) { - this.text.setValue( - `${this.file.basename}#${(item).heading}`, - ); - } else if (Object.prototype.hasOwnProperty.call(item, "id")) { - this.text.setValue(`${this.file.basename}^${(item).id}`); - } - } - selectSuggestion({ item }: FuzzyMatch) { - let link: string; - if (item instanceof TFile) { - link = item.basename; - } else if (Object.prototype.hasOwnProperty.call(item, "heading")) { - link = `${this.file.basename}#${(item).heading}`; - } else if (Object.prototype.hasOwnProperty.call(item, "id")) { - link = `${this.file.basename}^${(item).id}`; - } - - this.text.setValue(link); - this.onClose(); - - this.close(); - } - renderSuggestion( - result: FuzzyMatch, - el: HTMLElement, - ) { - const { item, match: matches } = result || {}; - const content = el.createDiv({ - cls: "suggestion-content", - }); - if (!item) { - content.setText(this.emptyStateText); - content.parentElement.addClass("is-selected"); - return; - } - - if (item instanceof TFile) { - const pathLength = item.path.length - item.name.length; - const matchElements = matches.matches.map((m) => { - return createSpan("suggestion-highlight"); - }); - for ( - let i = pathLength; - i < item.path.length - item.extension.length - 1; - i++ - ) { - const match = matches.matches.find((m) => m[0] === i); - if (match) { - const element = matchElements[matches.matches.indexOf(match)]; - content.appendChild(element); - element.appendText(item.path.substring(match[0], match[1])); - - i += match[1] - match[0] - 1; - continue; - } - - content.appendText(item.path[i]); - } - el.createDiv({ - cls: "suggestion-note", - text: item.path, - }); - } else if (Object.prototype.hasOwnProperty.call(item, "heading")) { - content.setText((item).heading); - content.prepend( - createSpan({ - cls: "suggestion-flair", - text: `H${(item).level}`, - }), - ); - } else if (Object.prototype.hasOwnProperty.call(item, "id")) { - content.setText((item).id); - } - } - get headings() { - if (!this.file) { - return []; - } - if (!this.cache) { - this.cache = this.app.metadataCache.getFileCache(this.file); - } - return this.cache.headings || []; - } - get blocks() { - if (!this.file) { - return []; - } - if (!this.cache) { - this.cache = this.app.metadataCache.getFileCache(this.file); - } - return Object.values(this.cache.blocks || {}) || []; - } - getItems() { - const v = this.inputEl.value; - if (/#/.test(v)) { - this.modifyInput = (i) => i.split(/#/).pop(); - return this.headings; - } else if (/\^/.test(v)) { - this.modifyInput = (i) => i.split(/\^/).pop(); - return this.blocks; - } - return this.files; - } -} - -export class FolderSuggestionModal extends SuggestionModal { - text: TextComponent; - cache: CachedMetadata; - folders: TFolder[]; - folder: TFolder; - constructor(app: App, input: TextComponent, items: TFolder[]) { - super(app, input.inputEl, items); - this.folders = [...items]; - this.text = input; - - this.inputEl.addEventListener("input", () => this.getFolder()); - } - getFolder() { - const v = this.inputEl.value; - const folder = this.app.vault.getAbstractFileByPath(v); - if (folder == this.folder) { - return; - } - if (!(folder instanceof TFolder)) { - return; - } - this.folder = folder; - - this.onInputChanged(); - } - getItemText(item: TFolder) { - return item.path; - } - onChooseItem(item: TFolder) { - this.text.setValue(item.path); - this.folder = item; - } - selectSuggestion({ item }: FuzzyMatch) { - const link = item.path; - - this.text.setValue(link); - this.onClose(); - - this.close(); - } - renderSuggestion(result: FuzzyMatch, el: HTMLElement) { - const { item, match: matches } = result || {}; - const content = el.createDiv({ - cls: "suggestion-content", - }); - if (!item) { - content.setText(this.emptyStateText); - content.parentElement.addClass("is-selected"); - return; - } - - const pathLength = item.path.length - item.name.length; - const matchElements = matches.matches.map((m) => { - return createSpan("suggestion-highlight"); - }); - for (let i = pathLength; i < item.path.length; i++) { - const match = matches.matches.find((m) => m[0] === i); - if (match) { - const element = matchElements[matches.matches.indexOf(match)]; - content.appendChild(element); - element.appendText(item.path.substring(match[0], match[1])); - - i += match[1] - match[0] - 1; - continue; - } - - content.appendText(item.path[i]); - } - el.createDiv({ - cls: "suggestion-note", - text: item.path, - }); - } - - getItems() { - return this.folders; - } -} - -export class FileSuggestionModal extends SuggestionModal { - text: TextComponent; - cache: CachedMetadata; - files: TFile[]; - file: TFile; - constructor(app: App, input: TextComponent, items: TFile[]) { - super(app, input.inputEl, items); - this.limit = 20; - this.files = [...items]; - this.text = input; - this.inputEl.addEventListener("input", () => this.getFile()); - } - - getFile() { - const v = this.inputEl.value; - const file = this.app.vault.getAbstractFileByPath(v); - if (file === this.file) { - return; - } - if (!(file instanceof TFile)) { - return; - } - this.file = file; - - this.onInputChanged(); - } - - getSelectedItem() { - return this.file; - } - - getItemText(item: TFile) { - return item.path; - } - - onChooseItem(item: TFile) { - this.file = item; - this.text.setValue(item.path); - this.text.onChanged(); - } - - selectSuggestion({ item }: FuzzyMatch) { - this.file = item; - this.text.setValue(item.path); - this.onClose(); - this.text.onChanged(); - this.close(); - } - - renderSuggestion(result: FuzzyMatch, el: HTMLElement) { - const { item, match: matches } = result || {}; - const content = el.createDiv({ - cls: "suggestion-content", - }); - if (!item) { - content.setText(this.emptyStateText); - content.parentElement.addClass("is-selected"); - return; - } - - const pathLength = item.path.length - item.name.length; - const matchElements = matches.matches.map((m) => { - return createSpan("suggestion-highlight"); - }); - for (let i = pathLength; i < item.path.length; i++) { - const match = matches.matches.find((m) => m[0] === i); - if (match) { - const element = matchElements[matches.matches.indexOf(match)]; - content.appendChild(element); - element.appendText(item.path.substring(match[0], match[1])); - - i += match[1] - match[0] - 1; - continue; - } - - content.appendText(item.path[i]); - } - el.createDiv({ - cls: "suggestion-note", - text: item.path, - }); - } - - getItems() { - return this.files; - } -} \ No newline at end of file diff --git a/src/dialogs/InsertLinkDialog.ts b/src/dialogs/InsertLinkDialog.ts index a6728d7..6915da4 100644 --- a/src/dialogs/InsertLinkDialog.ts +++ b/src/dialogs/InsertLinkDialog.ts @@ -1,10 +1,12 @@ -import { App, FuzzySuggestModal, TFile } from "obsidian"; -import { REG_LINKINDEX_INVALIDCHARS } from "../constants/constants"; +import { FuzzyMatch, FuzzySuggestModal, setIcon } from "obsidian"; +import { AUDIO_TYPES, CODE_TYPES, ICON_NAME, IMAGE_TYPES, REG_LINKINDEX_INVALIDCHARS, VIDEO_TYPES } from "../constants/constants"; import { t } from "../lang/helpers"; import ExcalidrawPlugin from "src/main"; import { getLink } from "src/utils/FileUtils"; +import { LinkSuggestion } from "src/types/types"; -export class InsertLinkDialog extends FuzzySuggestModal { + +export class InsertLinkDialog extends FuzzySuggestModal { private addText: Function; private drawingPath: string; @@ -28,7 +30,7 @@ export class InsertLinkDialog extends FuzzySuggestModal { this.emptyStateText = t("NO_MATCH"); } - getItems(): any[] { + getItems(): LinkSuggestion[] { //https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/422 return ( this.app.metadataCache @@ -39,11 +41,11 @@ export class InsertLinkDialog extends FuzzySuggestModal { ); } - getItemText(item: any): string { + getItemText(item: LinkSuggestion): string { return item.path + (item.alias ? `|${item.alias}` : ""); } - onChooseItem(item: any): void { + onChooseItem(item: LinkSuggestion): void { let filepath = item.path; if (item.file) { filepath = this.app.metadataCache.fileToLinktext( @@ -56,6 +58,65 @@ export class InsertLinkDialog extends FuzzySuggestModal { this.addText(getLink(this.plugin,{embed: false, path: filepath, alias: item.alias}), filepath, item.alias); } + renderSuggestion(result: FuzzyMatch, itemEl: HTMLElement) { + const { item, match: matches } = result || {}; + itemEl.addClass("mod-complex"); + const contentEl = itemEl.createDiv("suggestion-content"); + const auxEl = itemEl.createDiv("suggestion-aux"); + const titleEl = contentEl.createDiv("suggestion-title"); + const noteEl = contentEl.createDiv("suggestion-note"); + + if (!item) { + titleEl.setText(this.emptyStateText); + itemEl.addClass("is-selected"); + return; + } + + const path = item.file?.path ?? item.path; + + const pathLength = path.length - (item.file?.name.length ?? 0); + const matchElements = matches.matches.map((m) => { + return createSpan("suggestion-highlight"); + }); + const itemText = this.getItemText(item); + for (let i = pathLength; i < itemText.length; i++) { + const match = matches.matches.find((m) => m[0] === i); + if (match) { + const element = matchElements[matches.matches.indexOf(match)]; + titleEl.appendChild(element); + element.appendText(itemText.substring(match[0], match[1])); + + i += match[1] - match[0] - 1; + continue; + } + + titleEl.appendText(itemText[i]); + } + noteEl.setText(path); + + if(!item.file) { + setIcon(auxEl, "ghost"); + } else if(this.plugin.isExcalidrawFile(item.file)) { + setIcon(auxEl, ICON_NAME); + } else if (item.file.extension === "md") { + setIcon(auxEl, "square-pen"); + } else if (IMAGE_TYPES.includes(item.file.extension)) { + setIcon(auxEl, "image"); + } else if (VIDEO_TYPES.includes(item.file.extension)) { + setIcon(auxEl, "monitor-play"); + } else if (AUDIO_TYPES.includes(item.file.extension)) { + setIcon(auxEl, "file-audio"); + } else if (CODE_TYPES.includes(item.file.extension)) { + setIcon(auxEl, "file-code"); + } else if (item.file.extension === "canvas") { + setIcon(auxEl, "layout-dashboard"); + } else if (item.file.extension === "pdf") { + setIcon(auxEl, "book-open-text"); + } else { + auxEl.setText(item.file.extension); + } + } + onClose(): void { window.setTimeout(()=>{ this.addText = null @@ -63,9 +124,19 @@ export class InsertLinkDialog extends FuzzySuggestModal { super.onClose(); } - public start(drawingPath: string, addText: Function) { + private inLink: string; + onOpen(): void { + super.onOpen(); + if(this.inLink) { + this.inputEl.value = this.inLink; + this.inputEl.dispatchEvent(new Event('input')); + } + } + + public start(drawingPath: string, addText: Function, link?: string) { this.addText = addText; this.drawingPath = drawingPath; + this.inLink = link; this.open(); } } diff --git a/src/dialogs/InsertPDFModal.ts b/src/dialogs/InsertPDFModal.ts index 82fc7fb..a0da709 100644 --- a/src/dialogs/InsertPDFModal.ts +++ b/src/dialogs/InsertPDFModal.ts @@ -3,7 +3,7 @@ import ExcalidrawView from "../ExcalidrawView"; import ExcalidrawPlugin from "../main"; import { getPDFDoc } from "src/utils/FileUtils"; import { Modal, Setting, TextComponent } from "obsidian"; -import { FileSuggestionModal } from "./FolderSuggester"; +import { FileSuggestionModal } from "../Components/Suggesters/FileSuggestionModal"; import { getEA } from "src"; import { ExcalidrawAutomate } from "src/ExcalidrawAutomate"; import { ExcalidrawImperativeAPI } from "@zsviczian/excalidraw/types/excalidraw/types"; @@ -206,7 +206,11 @@ export class InsertPDFModal extends Modal { const search = new TextComponent(ce); search.inputEl.style.width = "100%"; - const suggester = new FileSuggestionModal(this.app, search,app.vault.getFiles().filter((f: TFile) => f.extension.toLowerCase() === "pdf")); + const suggester = new FileSuggestionModal( + this.app, + search,this.app.vault.getFiles().filter((f: TFile) => f.extension.toLowerCase() === "pdf"), + this.plugin + ); search.onChange(async () => { const file = suggester.getSelectedItem(); await setFile(file); diff --git a/src/dialogs/Messages.ts b/src/dialogs/Messages.ts index f10b2d7..d9d2e4f 100644 --- a/src/dialogs/Messages.ts +++ b/src/dialogs/Messages.ts @@ -19,15 +19,19 @@ I develop this plugin as a hobby, spending my free time doing this. If you find `, "2.6.8":` ## New -- Text Element cursor color matched the text color. -- [Image Occlusion](https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Image%20Occlusion.md) script by [@TrillStones](https://github.com/TrillStones) 🙏 +- **QoL improvements**: + - Obsidian-link search button in Element Link Editor. + - Add Any File now searches file aliases as well. + - Cosmetic changes to file search modals (display path, show file type icon). + - Text Element cursor-color matches the text color. +- New script in script store: [Image Occlusion](https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Image%20Occlusion.md) by [@TrillStones](https://github.com/TrillStones) 🙏 ## Fixed -- BUG: icon on the ribbon menu keeps reappearing even if you hide it every time you reopen Obsidian [#2115](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2115) -- In pen mode, when single-finger panning is enabled, allow actions with the mouse. -- When editing an Excalidraw file in split mode (drawing on one side, markdown view on the other), editing the markdown sometimes causes the drawing to re-zoom and jump away from the selected area. -- Hover-Editor compatibility -- ${String.fromCharCode(96)}ExcalidrawAutomate.create() ${String.fromCharCode(96)} will now correctly include the markdown text in templates above Excalidraw Data and below YAML front matter. This also fixes the same issue with the Deconstruct Selected Element script. +- Excalidraw icon on the **ribbon menu kept reappearing** every time you reopen Obsidian [#2115](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2115) +- In pen mode, when **single-finger panning** is enabled, Excalidraw should still **allow actions with the mouse**. +- When **editing a drawing in split mode** (drawing is on one side, markdown view is on the other), editing the markdown note sometimes causes the drawing to re-zoom and jump away from the selected area. +- Hover-Editor compatibility resolved [2041](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2041) +- ${String.fromCharCode(96)}ExcalidrawAutomate.create() ${String.fromCharCode(96)} will now correctly include the markdown text in templates above Excalidraw Data and below YAML front matter. This also fixes the same issue with the **Deconstruct Selected Element script**. `, "2.6.7":` diff --git a/src/dialogs/UniversalInsertFileModal.ts b/src/dialogs/UniversalInsertFileModal.ts index 846b22e..ce8cfa8 100644 --- a/src/dialogs/UniversalInsertFileModal.ts +++ b/src/dialogs/UniversalInsertFileModal.ts @@ -2,7 +2,7 @@ import { ButtonComponent, DropdownComponent, TFile, ToggleComponent } from "obsi import ExcalidrawView from "../ExcalidrawView"; import ExcalidrawPlugin from "../main"; import { Modal, Setting, TextComponent } from "obsidian"; -import { FileSuggestionModal } from "./FolderSuggester"; +import { FileSuggestionModal } from "../Components/Suggesters/FileSuggestionModal"; import { IMAGE_TYPES, sceneCoordsToViewportCoords, viewportCoordsToSceneCoords, MAX_IMAGE_SIZE, ANIMATED_IMAGE_TYPES, MD_EX_SECTIONS } from "src/constants/constants"; import { insertEmbeddableToView, insertImageToView } from "src/utils/ExcalidrawViewUtils"; import { getEA } from "src"; @@ -146,7 +146,9 @@ export class UniversalInsertFileModal extends Modal { const suggester = new FileSuggestionModal( this.app, search, - this.app.vault.getFiles().filter((f: TFile) => sections?.length > 0 || f!==this.view.file)); + this.app.vault.getFiles().filter((f: TFile) => sections?.length > 0 || f!==this.view.file), + this.plugin + ); search.onChange(() => { file = suggester.getSelectedItem(); updateForm(); diff --git a/src/lang/locale/en.ts b/src/lang/locale/en.ts index 51e693b..b647b03 100644 --- a/src/lang/locale/en.ts +++ b/src/lang/locale/en.ts @@ -1,3 +1,4 @@ +import { FILE } from "dns"; import { DEVICE, FRONTMATTER_KEYS, @@ -10,6 +11,8 @@ declare const PLUGIN_VERSION:string; // English export default { + // Sugester + SELECT_FILE_TO_INSERT: "Select a file to insert", // main.ts CONVERT_URL_TO_FILE: "Save image from URL to local file", UNZIP_CURRENT_FILE: "Decompress current Excalidraw file", diff --git a/src/main.ts b/src/main.ts index 1913d19..decdd23 100644 --- a/src/main.ts +++ b/src/main.ts @@ -113,7 +113,7 @@ import { legacyExcalidrawPopoverObserver, } from "./MarkdownPostProcessor"; -import { FieldSuggester } from "./dialogs/FieldSuggester"; +import { FieldSuggester } from "./Components/Suggesters/FieldSuggester"; import { ReleaseNotes } from "./dialogs/ReleaseNotes"; import { Packages } from "./types/types"; import { PreviewImageType } from "./utils/UtilTypes"; diff --git a/src/types/types.d.ts b/src/types/types.d.ts index b1ce820..59100fe 100644 --- a/src/types/types.d.ts +++ b/src/types/types.d.ts @@ -1,3 +1,4 @@ +import { TFile } from "obsidian"; import { ExcalidrawAutomate } from "../ExcalidrawAutomate"; import { ExcalidrawLib } from "../ExcalidrawLib"; @@ -33,6 +34,12 @@ export type DeviceType = { export type Point = [number, number]; +export type LinkSuggestion = { + file: TFile; + path: string; + alias?: string; +} + declare global { interface Window { ExcalidrawAutomate: ExcalidrawAutomate;