diff --git a/Obsidian-File-Properties-Enhancer-1.2.0-beta.rar b/Obsidian-File-Properties-Enhancer-1.2.0-beta.rar deleted file mode 100644 index 711c908..0000000 Binary files a/Obsidian-File-Properties-Enhancer-1.2.0-beta.rar and /dev/null differ diff --git a/src/components/Banner.ts b/src/components/Banner.ts deleted file mode 100644 index cb88070..0000000 --- a/src/components/Banner.ts +++ /dev/null @@ -1,309 +0,0 @@ -import { Component, MarkdownView, TFile } from "obsidian"; -import clamp from "lodash/clamp"; - - -import FilePropertyEnhancerPlugin from "../filePropertyEnhancerIndex"; -import type { IBannerMetadata } from "../types/global"; - -type MTEvent = MouseEvent | TouchEvent; - -interface IDragData { - x: number | null; - y: number | null; - isDragging: boolean; - vertical: boolean; -} - -interface IBanner { - plugin: FilePropertyEnhancerPlugin; - bannerData: IBannerMetadata; - view: MarkdownView; - filepath: string; - wrapper: HTMLElement; - isEmbed: boolean; -} - -interface IElementListener { - target: HTMLElement; - ev: E; - func: (listener: HTMLElementEventMap[E]) => any; -} - -type ElListener = IElementListener; - -// Get current mouse position of event -const getMousePos = (e: MTEvent) => { - const {clientX, clientY} = - e instanceof MouseEvent ? e : e.targetTouches[0]; - return {x: clientX, y: clientY}; -}; - -// Begin image drag (and if a modifier key is required, only do so when pressed) -const handleDragStart = ( - e: MTEvent, - dragData: IDragData, - isModHeld: boolean, -) => { - if (!isModHeld && e instanceof MouseEvent) { - return; - } - const {x, y} = getMousePos(e); - const {clientHeight, clientWidth, naturalHeight, naturalWidth} = - e.target as HTMLImageElement; - dragData.x = x; - dragData.y = y; - dragData.isDragging = true; - dragData.vertical = - naturalHeight / naturalWidth >= clientHeight / clientWidth; -}; - -// Dragging image -// TODO: See if it's possible to rework drag so that it's consistent to the image's dimensions -const handleDragMove = (e: MTEvent, dragData: IDragData) => { - if (!dragData.isDragging) { - return; - } - - // Calculate delta and update current mouse position - const img = e.target as HTMLImageElement; - const {x, y} = getMousePos(e); - if (!dragData.x || !dragData.y) return; - - const delta = { - x: ((dragData.x - x) / img.clientWidth) * 30, - y: ((dragData.y - y) / img.clientHeight) * 30, - }; - dragData.x = x; - dragData.y = y; - - const [currentX, currentY] = img.style.objectPosition - .split(" ") - .map((n) => parseFloat(n)); - - // Update object position styling depending on banner dimensions - if (dragData.vertical) { - const newY = clamp(currentY + delta.y, 0, 100); - img.style.objectPosition = `${currentX}% ${newY}%`; - } else { - const newX = clamp(currentX + delta.x, 0, 100); - img.style.objectPosition = `${newX}% ${currentY}%`; - } -}; - -// Finish image drag -const handleDragEnd = async ( - img: HTMLImageElement, - view: MarkdownView, - path: string, - dragData: IDragData, - plugin: FilePropertyEnhancerPlugin, -) => { - if (!dragData.isDragging) { - return; - } - dragData.isDragging = false; - - // Upsert data to file's frontmatter - const [x, y] = img.style.objectPosition - .split(" ") - .map((n) => Math.round(parseFloat(n) * 1000) / 100000); - await plugin.metaManager.upsertBannerData( - view, - path, - dragData.vertical ? {y} : {x}, - ); -}; - -// Helper to get the URL path to the image file -const parseSource = ( - plugin: FilePropertyEnhancerPlugin, - src: string, - filepath: string, -): string => { - // Internal embed link format - "![[]]" - if (/^\!\[\[.+\]\]$/.test(src)) { - const link = src.slice(3, -2); - const file = plugin.app.metadataCache.getFirstLinkpathDest( - link, - filepath, - ); - return file ? plugin.app.vault.getResourcePath(file) : link; - } - - // Absolute paths, relative paths, & URLs - const path = src?.startsWith("/") ? src.slice(1) : src; - const file = plugin.app.vault.getAbstractFileByPath(path); - return file instanceof TFile ? plugin.app.vault.getResourcePath(file) : src; -}; - -class Banner extends Component { - private plugin: FilePropertyEnhancerPlugin; - private bannerData: IBannerMetadata; - private view: MarkdownView; - private filepath: string; - private wrapper: HTMLElement; - private isEmbed: boolean = false; - private listeners: ElListener[] = []; - - private messageBox: HTMLElement; - private img: HTMLImageElement; - private dragData: IDragData = { - x: null, - y: null, - isDragging: false, - vertical: true, - }; - - constructor({ - plugin, - bannerData, - view, - filepath, - wrapper, - isEmbed = false, - }: IBanner) { - super(); - this.plugin = plugin; - this.bannerData = bannerData; - this.view = view; - this.filepath = filepath; - this.wrapper = wrapper; - this.isEmbed = isEmbed; - } - - public onload(): [HTMLElement[]] { - const {src, x = 0.5, y = 0.5, lock} = this.bannerData; - - const canDrag = !this.isEmbed && !lock; - - this.initMessage(); - const {messageBox} = this.initImage(canDrag, { - src, - x, - y, - }); - - return [[messageBox, this.img]]; - } - - onunload() { - super.onunload(); - this.listeners.forEach(({target, ev, func}) => - target.removeEventListener(ev, func), - ); - } - - initMessage() { - this.messageBox = createEl("div", { - cls: "banner-message", - }); - const spinnerEl = this.messageBox.createEl("div", { - cls: "spinner", - }); - spinnerEl.createEl("div", {cls: "bounce1"}); - spinnerEl.createEl("div", {cls: "bounce2"}); - spinnerEl.createEl("div", {cls: "bounce3"}); - } - - initImage( - canDrag: boolean, - { - src, - x, - y, - }: { - src: string; - x: number; - y: number; - }, - ) { - const clampedX = clamp(x, 0, 1); - const clampedY = clamp(y, 0, 1); - this.img = createEl("img", { - cls: "banner-image full-width", - attr: { - src: parseSource(this.plugin, src, this.filepath), - draggable: false, - style: `object-position: ${clampedX * 100}% ${ - clampedY * 100 - }%;`, - }, - }); - this.img.onload = () => this.wrapper.toggleClass("loaded", true); - this.img.onerror = () => { - this.messageBox.innerHTML = `

Error loading banner image! Is the ${this.plugin.settings.banners["frontmatterField"]} field valid?

`; - this.wrapper.toggleClass("error", true); - }; - - const body = document.querySelector("body"); - if (canDrag && body) { - this.img.toggleClass( - "draggable", - this.plugin.settings.banners.bannerDragModifier === "none" || - this.plugin.settings.banners.holdingDragModKey, - ); - - // Image drag - const imgDragStart = (e: MTEvent) => - handleDragStart( - e, - this.dragData, - this.plugin.settings.banners.holdingDragModKey, - ); - const imgDragMove = (e: MTEvent) => - handleDragMove(e, this.dragData); - const imgDragEnd = (e: MTEvent) => { - handleDragEnd( - this.img, - this.view, - this.filepath, - this.dragData, - this.plugin, - ); - }; - this.listeners.push( - {target: this.img, ev: "mousedown", func: imgDragStart}, - {target: this.img, ev: "mousemove", func: imgDragMove}, - {target: body, ev: "mouseup", func: imgDragEnd}, - {target: body, ev: "mouseleave", func: imgDragEnd}, - { - target: this.img, - ev: "click", - func: (e: MTEvent) => e.stopPropagation(), - }, - ); - - // Only allow dragging in mobile when desired from settings - if (this.plugin.settings.banners.allowMobileDrag && body) { - this.listeners.push( - {target: this.img, ev: "touchstart", func: imgDragStart}, - {target: this.img, ev: "touchmove", func: imgDragMove}, - {target: body, ev: "touchend", func: imgDragEnd}, - { - target: this.img, - ev: "click", - func: (e: MTEvent) => e.stopPropagation(), - }, - ); - } - } - - this.listeners.forEach(({target, ev, func}) => - target.addEventListener(ev, func), - ); - const removeListeners = () => - this.listeners.forEach(({target, ev, func}) => - target.removeEventListener(ev, func), - ); - - return { - messageBox: this.messageBox, - removeListeners, - }; - } - - initChangeControls() { - } -} - -export default Banner; diff --git a/src/components/Header.ts b/src/components/Header.ts deleted file mode 100644 index e444edc..0000000 --- a/src/components/Header.ts +++ /dev/null @@ -1,91 +0,0 @@ -import FilePropertyEnhancerPlugin from "../filePropertyEnhancerIndex"; -import { Component, MarkdownView, TFile } from "obsidian"; -import MetaManager from "./MetaManager"; -import Icon from "./Icon"; - -export default class Header extends Component { - plugin: FilePropertyEnhancerPlugin; - title: string | null; - icon: string; - file: TFile; - view: MarkdownView; - metaManager: MetaManager; - - private titleDiv: HTMLDivElement; - private wrap: HTMLElement; - - iconComponent: Icon; - - constructor(plugin: FilePropertyEnhancerPlugin, view: MarkdownView, icon: string) { - super(); - this.plugin = plugin; - this.icon = icon; - - this.view = view; - this.title = view.file?.basename || ""; - this.file = view.file as TFile; - this.metaManager = plugin.metaManager; - } - - onload() { - this.loadWrap(); - this.loadTitle(); - this.wrap.append(this.titleDiv); - return this.wrap; - } - - onunload() { - this.titleDiv.remove(); - this.wrap.remove(); - } - - loadWrap() { - this.wrap = createEl("div"); - this.wrap.addClass( - "obsidian-banner-header", - `title-placement-${this.plugin.settings.banners["titlePlacement"]}`, - ); - this.wrap.addClass( - "obsidian-banner-icon", - `h-${this.plugin.settings.banners["iconHorizontalAlignment"]}`, - `v-${this.plugin.settings.banners["iconVerticalAlignment"]}`, - ); - if (this.icon) { - this.iconComponent = new Icon({ - plugin: this.plugin, - icon: this.icon, - view: this.view, - file: this.file, - }); - this.wrap.append(this.iconComponent.onload()); - } - } - - loadTitle() { - // title - this.titleDiv = createEl("div"); - this.titleDiv.addClass("obsidian-banner-title", "HyperMD-header-1"); - this.titleDiv.textContent = this.title; - this.titleDiv.contentEditable = "true"; - this.titleDiv.addEventListener("blur", async (e) => { - const newTitle = this.titleDiv.textContent; - if (newTitle !== this.title && newTitle) { - this.title = newTitle; - await this.metaManager.upsertBannerData(this.view, this.file, { - title: newTitle, - }); - } - }); - - this.titleDiv.addEventListener("keydown", (e) => { - if (e.key === "Enter") { - e.preventDefault(); - this.titleDiv.blur(); - } - }); - - this.titleDiv.addEventListener("click", (e) => { - this.titleDiv.focus(); - }); - } -} diff --git a/src/filePropertyEnhancerIndex.ts b/src/filePropertyEnhancerIndex.ts index ac0ff3d..c48d132 100644 --- a/src/filePropertyEnhancerIndex.ts +++ b/src/filePropertyEnhancerIndex.ts @@ -4,365 +4,341 @@ import "./styles/custom.css"; import { createModal, getIcon, setPropertyIcon } from "./utils/utils"; import MetaManager from "./components/MetaManager"; import "./styles/styles.scss"; -import type { ILeafBanner, MetadataIcon } from "./types/global"; import type { filePropertyEnhancerSettings } from "./filePropertyEnhancerSettings"; import { DEFAULT_SETTINGS } from "./filePropertyEnhancerSettings"; +import type { ILeafBanner, MetadataIcon } from "./types/global"; type Modifier = 'Mod' | 'Shift' | 'Alt'; type ShortcutConfig = { - key: string; - modifiers: Modifier[]; - noText: string; - withTextPrefix: string; - withTextSuffix?: string; + key: string; + modifiers: Modifier[]; + noText: string; + withTextPrefix: string; + withTextSuffix?: string; }; const SHORTCUTS: ShortcutConfig[] = [ - {key: 'i', modifiers: ['Mod'], noText: '**', withTextPrefix: '*'}, - {key: 'b', modifiers: ['Mod'], noText: '****', withTextPrefix: '**'}, - {key: 'k', modifiers: ['Mod', 'Shift'], noText: '[]()', withTextPrefix: '[', withTextSuffix: ']()'}, - {key: 'h', modifiers: ['Mod', 'Shift'], noText: '====', withTextPrefix: '=='}, - {key: 'l', modifiers: ['Mod'], noText: '[[]]', withTextPrefix: '[[', withTextSuffix: ']]'}, + {key: 'i', modifiers: ['Mod'], noText: '**', withTextPrefix: '*'}, + {key: 'b', modifiers: ['Mod'], noText: '****', withTextPrefix: '**'}, + {key: 'k', modifiers: ['Mod', 'Shift'], noText: '[]()', withTextPrefix: '[', withTextSuffix: ']()'}, + {key: 'h', modifiers: ['Mod', 'Shift'], noText: '====', withTextPrefix: '=='}, + {key: 'l', modifiers: ['Mod'], noText: '[[]]', withTextPrefix: '[[', withTextSuffix: ']]'}, ]; export default class FilePropertyEnhancerPlugin extends Plugin { - settings: filePropertyEnhancerSettings; - metaManager: MetaManager; - loadedBanners: Set = new Set(); - - async onload() { - await this.loadSettings(); - this.registerCommands(); - this.app.workspace.onLayoutReady(() => { - this.patchFileProperty(); - this.patchFileTextProperty(); - this.patchAllProperties(); - }) - } - - onunload() { - - this.app.workspace.onLayoutReady(() => { - this.unpatchAllProperties(); - this.unpatchFileProperty(); - - console.log("Metadata-Style: metadata editor get unpatched"); - }) - } - - registerCommands() { - // Deprecated because Obsidian Team Support This - this.addCommand({ - id: "fold-unfold-file-property", - name: "Fold/Unfold Current File Property", - checkCallback: (checking: boolean) => { - // Conditions to check - const activeEditor = this.app.workspace.activeEditor; - if (activeEditor) { - if (!checking) { - const metadataEditor = activeEditor.metadataEditor; - if (metadataEditor) { - metadataEditor.setCollapse(!metadataEditor.collapsed); - } - } - - return true; - } - } - }); - } - - patchFileProperty() { - const createIconModal = (property: any) => createModal(this, property); - const getMetadataIcon = (key: string): MetadataIcon | null => getIcon(this, key); - - const patchPropertyInList = () => { - const editor = this.app.workspace.activeEditor; - - if (!editor) return false; - const property = editor.metadataEditor.rendered.first(); - - if (!property) return false; - const propertyCON = property.constructor; - - this.register( - around(propertyCON.prototype, { - showPropertyMenu: (next: any) => - function (this: any, ...args: any) { - if ((args[0] as PointerEvent).ctrlKey || (args[0] as PointerEvent).metaKey) { - createIconModal(this).open(); - return; - } - next.call(this, ...args); - }, - renderProperty: (next: any) => - async function (this: any, ...args: any) { - next.apply(this, args); - - const icon = getMetadataIcon(args[0].key || this.entry.key); - if (icon) { - setPropertyIcon(this, icon, "file-property"); - return; - } - - if (this.entry.type === "number") { - if (!this.valueEl) return; - if (this.valueEl.children.length > 1) return; - const plusEl = createDiv({ - cls: "metadata-input-plus-btn", - }) - const minusEl = createDiv({ - cls: "metadata-input-minus-btn", - }); - setIcon(plusEl, "plus"); - setIcon(minusEl, "minus"); - this.valueEl.appendChild(plusEl); - this.valueEl.appendChild(minusEl); - - plusEl.onClickEvent(() => { - this.handleUpdateValue(this.entry.value + 1); - this.renderProperty(this.entry, true); - }); - minusEl.onClickEvent(() => { - this.handleUpdateValue(this.entry.value - 1); - this.renderProperty(this.entry, true); - }); - } - - }, - focusValue: (next: any) => - function (this: any, ...args: any) { - const result = next && next.apply(this, args); - // Prevent unfocus when changing type of property. - setTimeout(() => { - next.apply(this, args); - }, 30); - return result; - } - }) - ); - editor.leaf?.rebuildView(); - console.log("Metadata-Style: metadata editor get patched"); - return true; - }; - this.app.workspace.onLayoutReady(() => { - if (!patchPropertyInList()) { - const evt = this.app.workspace.on("layout-change", () => { - patchPropertyInList() && this.app.workspace.offref(evt); - }); - this.registerEvent(evt); - } - }); - } - - patchFileTextProperty() { - - const isModifiersMatched = (evt: KeyboardEvent, modifiers: Modifier[]): boolean => { - const checks = { - 'Mod': () => evt.ctrlKey || evt.metaKey, - 'Shift': () => evt.shiftKey, - 'Alt': () => evt.altKey - }; - - return modifiers.every(modifier => checks[modifier]()); - } - const insertTextAtSelection = (text: string) => { - const selection = window.getSelection(); - if (!selection) return; - - const range = selection.getRangeAt(0); - range.deleteContents(); - - const textNode = document.createTextNode(text); - range.insertNode(textNode); - - // Move the cursor after the inserted text - range.setStartAfter(textNode); - range.setEndAfter(textNode); - selection.removeAllRanges(); - selection.addRange(range); - } - - const handleKeyShortcut = (evt: KeyboardEvent, config: ShortcutConfig) => { - if (evt.key.toLowerCase() !== config.key || !isModifiersMatched(evt, config.modifiers)) return; - evt.preventDefault(); - - const selection = window.getSelection(); - if (!selection) return; - - const selectedText = selection.toString().trim(); - - if (selectedText === '') { - insertTextAtSelection(config.noText); - } else { - insertTextAtSelection(`${config.withTextPrefix}${selectedText}${config.withTextSuffix || config.withTextPrefix}`); - } - } - - const patchTextPropertyInList = () => { - const editor = this.app.workspace.activeEditor; - const propertyList = editor?.metadataEditor?.rendered.filter((property: any) => property.entry.type === "text"); - - if (!propertyList?.length) return false; - - const property = propertyList[0]; - if (!property) return false; - - const renderer = property.rendered; - - this.register( - around(renderer.constructor.prototype, { - render: (next: any) => - async function (this: any, ...args: any) { - - next.apply(this, ...args); - if (!this.addedEvent) { - this.addedEvent = true; - if (!this.inputEl) return; - // console.log(this.editing, this.value); - (this.inputEl as HTMLElement)?.onClickEvent(() => { - if (this.editing) return; - this.editing = true; - this.inputEl.toggleClass('is-editing', this.editing); - - this.inputEl.empty(); - this.inputEl.setText(this.value); - }); - (this.inputEl as HTMLElement).addEventListener('blur', () => { - this.editing = false; - this.inputEl.toggleClass('is-editing', this.editing); - this.render(); - }); - (this.inputEl as HTMLElement).addEventListener('keyup', (evt) => { - if (!this.editing) return; - // Support basic markdown shortcuts - for (const shortcut of SHORTCUTS) { - handleKeyShortcut(evt, shortcut); - } - }); - - } - if (this.editing) return; - this.inputEl.empty(); - await MarkdownRenderer.render( - this.ctx.app, - this.value, - this.inputEl, - this.ctx.metadataEditor.owner.file.path, - this.ctx.metadataEditor, - ); - }, - onFocus: (next: any) => - function (this: any, ...args: any) { - // if (this.editing) return; - next.apply(this, args); - if (this.editing) return; - this.editing = true; - this.inputEl.toggleClass('is-editing', this.editing); - - this.inputEl.empty(); - this.inputEl.setText(this.value); - } - }) - ); - editor?.leaf?.rebuildView(); - console.log("Metadata-Style: metadata editor get patched"); - return true; - }; - this.app.workspace.onLayoutReady(() => { - if (!patchTextPropertyInList()) { - const evt = this.app.workspace.on("layout-change", () => { - patchTextPropertyInList() && this.app.workspace.offref(evt); - }); - this.registerEvent(evt); - } - }); - } - - patchAllProperties() { - const createIconModal = (property: any) => createModal(this, property); - const getMetadataIcon = (key: string): MetadataIcon | null => getIcon(this, key); - - const patchProperty = () => { - const allPropertiesView = this.app.workspace.getLeavesOfType("all-properties")[0]?.view as any; - - if (!allPropertiesView) return false; - // @ts-ignore - const treeItem = allPropertiesView.root.vChildren._children?.first(); - - if (!treeItem) return false; - const treeItemConstructor = treeItem.constructor; - - this.register( - around(treeItemConstructor.prototype, { - setProperty: (next: any) => - function (this: any, ...args: any) { - next.apply(this, args); - const icon = getMetadataIcon(this.property.key); - if (!icon) return; - const button = setPropertyIcon(this, icon, "all-properties"); - if (!button) return; - button.onClick(() => createIconModal(this.property)); - }, - onSelfClick: (next: any) => - function (this: any, ...args: any) { - if ((args[0] as PointerEvent).ctrlKey || (args[0] as PointerEvent).metaKey) { - createIconModal(this).open(); - return; - } - next.call(this, ...args); - } - }) - ); - allPropertiesView.leaf?.rebuildView(); - console.log("Metadata-Style: all property view get patched"); - return true; - }; - this.app.workspace.onLayoutReady(() => { - if (!patchProperty()) { - const evt = this.app.workspace.on("layout-change", () => { - patchProperty() && this.app.workspace.offref(evt); - }); - this.registerEvent(evt); - } - }); - } - - unpatchAllProperties() { - const leaf = this.app.workspace.getLeavesOfType("all-properties"); - if (leaf.length === 0) return; - for (const item of leaf) { - item.rebuildView(); - } - } - - unpatchFileProperty() { - const leaves = this.app.workspace.getLeavesOfType('markdown'); - for (const leaf of leaves) { - if (leaf.view.currentMode.sourceMode === true) continue; - const metadataEditor = leaf.view.metadataEditor; - if (!metadataEditor) continue; - const propertyList = metadataEditor.rendered; - if (!propertyList) continue; - propertyList.forEach((property: any) => { - const item = property.entry; - try { - property.renderProperty(item, true); - } catch (e) { - console.log(e); - } - }); - } - } - - async loadSettings() { - this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData()); - } - - async saveSettings() { - await this.saveData(this.settings); - } + settings: filePropertyEnhancerSettings; + metaManager: MetaManager; + + async onload() { + await this.loadSettings(); + this.app.workspace.onLayoutReady(() => { + this.patchFileProperty(); + this.patchFileTextProperty(); + this.patchAllProperties(); + }); + } + + onunload() { + + this.app.workspace.onLayoutReady(() => { + this.unpatchAllProperties(); + this.unpatchFileProperty(); + + console.log("Metadata-Style: metadata editor get unpatched"); + }); + } + + patchFileProperty() { + const createIconModal = (property: any) => createModal(this, property); + const getMetadataIcon = (key: string): MetadataIcon | null => getIcon(this, key); + + const patchPropertyInList = () => { + const editor = this.app.workspace.activeEditor; + + if (!editor) return false; + const property = editor.metadataEditor.rendered.first(); + + if (!property) return false; + const propertyCON = property.constructor; + + this.register( + around(propertyCON.prototype, { + showPropertyMenu: (next: any) => + function (this: any, ...args: any) { + if ((args[0] as PointerEvent).ctrlKey || (args[0] as PointerEvent).metaKey) { + createIconModal(this).open(); + return; + } + next.call(this, ...args); + }, + renderProperty: (next: any) => + async function (this: any, ...args: any) { + next.apply(this, args); + + const icon = getMetadataIcon(args[0].key || this.entry.key); + if (icon) { + setPropertyIcon(this, icon, "file-property"); + return; + } + + if (this.entry.type === "number") { + if (!this.valueEl) return; + if (this.valueEl.children.length > 1) return; + const plusEl = createDiv({ + cls: "metadata-input-plus-btn", + }); + const minusEl = createDiv({ + cls: "metadata-input-minus-btn", + }); + setIcon(plusEl, "plus"); + setIcon(minusEl, "minus"); + this.valueEl.appendChild(plusEl); + this.valueEl.appendChild(minusEl); + + plusEl.onClickEvent(() => { + this.handleUpdateValue(this.entry.value + 1); + this.renderProperty(this.entry, true); + }); + minusEl.onClickEvent(() => { + this.handleUpdateValue(this.entry.value - 1); + this.renderProperty(this.entry, true); + }); + } + + }, + focusValue: (next: any) => + function (this: any, ...args: any) { + const result = next && next.apply(this, args); + // Prevent unfocus when changing type of property. + setTimeout(() => { + next.apply(this, args); + }, 30); + return result; + } + }) + ); + editor.leaf?.rebuildView(); + console.log("Metadata-Style: metadata editor get patched"); + return true; + }; + this.app.workspace.onLayoutReady(() => { + if (!patchPropertyInList()) { + const evt = this.app.workspace.on("layout-change", () => { + patchPropertyInList() && this.app.workspace.offref(evt); + }); + this.registerEvent(evt); + } + }); + } + + patchFileTextProperty() { + + const isModifiersMatched = (evt: KeyboardEvent, modifiers: Modifier[]): boolean => { + const checks = { + 'Mod': () => evt.ctrlKey || evt.metaKey, + 'Shift': () => evt.shiftKey, + 'Alt': () => evt.altKey + }; + + return modifiers.every(modifier => checks[modifier]()); + }; + const insertTextAtSelection = (text: string) => { + const selection = window.getSelection(); + if (!selection) return; + + const range = selection.getRangeAt(0); + range.deleteContents(); + + const textNode = document.createTextNode(text); + range.insertNode(textNode); + + // Move the cursor after the inserted text + range.setStartAfter(textNode); + range.setEndAfter(textNode); + selection.removeAllRanges(); + selection.addRange(range); + }; + + const handleKeyShortcut = (evt: KeyboardEvent, config: ShortcutConfig) => { + if (evt.key.toLowerCase() !== config.key || !isModifiersMatched(evt, config.modifiers)) return; + evt.preventDefault(); + + const selection = window.getSelection(); + if (!selection) return; + + const selectedText = selection.toString().trim(); + + if (selectedText === '') { + insertTextAtSelection(config.noText); + } else { + insertTextAtSelection(`${config.withTextPrefix}${selectedText}${config.withTextSuffix || config.withTextPrefix}`); + } + }; + + const patchTextPropertyInList = () => { + const editor = this.app.workspace.activeEditor; + const propertyList = editor?.metadataEditor?.rendered.filter((property: any) => property.entry.type === "text"); + + if (!propertyList?.length) return false; + + const property = propertyList[0]; + if (!property) return false; + + const renderer = property.rendered; + + this.register( + around(renderer.constructor.prototype, { + render: (next: any) => + async function (this: any, ...args: any) { + + next.apply(this, ...args); + if (!this.addedEvent) { + this.addedEvent = true; + if (!this.inputEl) return; + // console.log(this.editing, this.value); + (this.inputEl as HTMLElement)?.onClickEvent(() => { + if (this.editing) return; + this.editing = true; + this.inputEl.toggleClass('is-editing', this.editing); + + this.inputEl.empty(); + this.inputEl.setText(this.value); + }); + (this.inputEl as HTMLElement).addEventListener('blur', () => { + this.editing = false; + this.inputEl.toggleClass('is-editing', this.editing); + this.render(); + }); + (this.inputEl as HTMLElement).addEventListener('keyup', (evt) => { + if (!this.editing) return; + // Support basic markdown shortcuts + for (const shortcut of SHORTCUTS) { + handleKeyShortcut(evt, shortcut); + } + }); + + } + if (this.editing) return; + this.inputEl.empty(); + await MarkdownRenderer.render( + this.ctx.app, + this.value, + this.inputEl, + this.ctx.metadataEditor.owner.file.path, + this.ctx.metadataEditor, + ); + }, + onFocus: (next: any) => + function (this: any, ...args: any) { + // if (this.editing) return; + next.apply(this, args); + if (this.editing) return; + this.editing = true; + this.inputEl.toggleClass('is-editing', this.editing); + + this.inputEl.empty(); + this.inputEl.setText(this.value); + } + }) + ); + editor?.leaf?.rebuildView(); + console.log("Metadata-Style: metadata editor get patched"); + return true; + }; + this.app.workspace.onLayoutReady(() => { + if (!patchTextPropertyInList()) { + const evt = this.app.workspace.on("layout-change", () => { + patchTextPropertyInList() && this.app.workspace.offref(evt); + }); + this.registerEvent(evt); + } + }); + } + + patchAllProperties() { + const createIconModal = (property: any) => createModal(this, property); + const getMetadataIcon = (key: string): MetadataIcon | null => getIcon(this, key); + + const patchProperty = () => { + const allPropertiesView = this.app.workspace.getLeavesOfType("all-properties")[0]?.view as any; + + if (!allPropertiesView) return false; + // @ts-ignore + const treeItem = allPropertiesView.root.vChildren._children?.first(); + + if (!treeItem) return false; + const treeItemConstructor = treeItem.constructor; + + this.register( + around(treeItemConstructor.prototype, { + setProperty: (next: any) => + function (this: any, ...args: any) { + next.apply(this, args); + const icon = getMetadataIcon(this.property.key); + if (!icon) return; + const button = setPropertyIcon(this, icon, "all-properties"); + if (!button) return; + button.onClick(() => createIconModal(this.property)); + }, + onSelfClick: (next: any) => + function (this: any, ...args: any) { + if ((args[0] as PointerEvent).ctrlKey || (args[0] as PointerEvent).metaKey) { + createIconModal(this).open(); + return; + } + next.call(this, ...args); + } + }) + ); + allPropertiesView.leaf?.rebuildView(); + console.log("Metadata-Style: all property view get patched"); + return true; + }; + this.app.workspace.onLayoutReady(() => { + if (!patchProperty()) { + const evt = this.app.workspace.on("layout-change", () => { + patchProperty() && this.app.workspace.offref(evt); + }); + this.registerEvent(evt); + } + }); + } + + unpatchAllProperties() { + const leaf = this.app.workspace.getLeavesOfType("all-properties"); + if (leaf.length === 0) return; + for (const item of leaf) { + item.rebuildView(); + } + } + + unpatchFileProperty() { + const leaves = this.app.workspace.getLeavesOfType('markdown'); + for (const leaf of leaves) { + if (leaf.view.currentMode.sourceMode === true) continue; + const metadataEditor = leaf.view.metadataEditor; + if (!metadataEditor) continue; + const propertyList = metadataEditor.rendered; + if (!propertyList) continue; + propertyList.forEach((property: any) => { + const item = property.entry; + try { + property.renderProperty(item, true); + } catch (e) { + console.log(e); + } + }); + } + } + + async loadSettings() { + this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData()); + } + + async saveSettings() { + await this.saveData(this.settings); + } } diff --git a/src/filePropertyEnhancerSettings.ts b/src/filePropertyEnhancerSettings.ts index 92b932c..39193a7 100644 --- a/src/filePropertyEnhancerSettings.ts +++ b/src/filePropertyEnhancerSettings.ts @@ -1,57 +1,9 @@ import type { MetadataIcon } from "./types/global"; -type StyleOption = "solid" | "gradient"; -export type BannerDragModOption = "none" | "shift" | "ctrl" | "alt" | "meta"; -type IconHorizontalOption = "left" | "center" | "right" | "custom"; -type IconVerticalOption = "above" | "center" | "below" | "custom"; -type TitlePlacementOption = "below-icon" | "next-to-icon"; - export interface filePropertyEnhancerSettings { iconList: MetadataIcon[]; - banners: { - height: number | null; - style: StyleOption; - holdingDragModKey: boolean; - frontmatterField: string; - bannerDragModifier: BannerDragModOption; - iconHorizontalAlignment: IconHorizontalOption; - iconHorizontalTransform: string | null; - iconVerticalAlignment: IconVerticalOption; - iconVerticalTransform: string | null; - useTwemoji: boolean; - showInInternalEmbed: boolean; - showInPreviewEmbed: boolean; - internalEmbedHeight: number | null; - previewEmbedHeight: number | null; - showPreviewInLocalModal: boolean; - localSuggestionsLimit: number | null; - bannersFolder: string | null; - allowMobileDrag: boolean; - titlePlacement: TitlePlacementOption; - } } export const DEFAULT_SETTINGS: filePropertyEnhancerSettings = { iconList: [], - banners: { - height: 320, - bannerDragModifier: "alt", - holdingDragModKey: false, - style: "solid", - showInInternalEmbed: true, - showInPreviewEmbed: true, - iconHorizontalAlignment: "left", - iconVerticalAlignment: "center", - useTwemoji: false, - showPreviewInLocalModal: true, - allowMobileDrag: false, - titlePlacement: "below-icon", - internalEmbedHeight: 200, - previewEmbedHeight: 120, - frontmatterField: "banner", - iconHorizontalTransform: "0px", - iconVerticalTransform: "0px", - localSuggestionsLimit: 10, - bannersFolder: "/", - } }; diff --git a/src/types/global.d.ts b/src/types/global.d.ts index d773257..8f2c955 100644 --- a/src/types/global.d.ts +++ b/src/types/global.d.ts @@ -1,8 +1,5 @@ /// -import Banner from "../components/Banner"; -import Header from "../components/Header"; - type IconType = "emoji" | "lucide"; interface MetadataIcon { @@ -25,6 +22,5 @@ type BannerMetadataKey = keyof IBannerMetadata; export interface ILeafBanner { leafID: string; filePath: string; - banner: Banner; header: Header; }