diff --git a/manifest-beta.json b/manifest-beta.json index af5c605d..fcbea150 100644 --- a/manifest-beta.json +++ b/manifest-beta.json @@ -1,7 +1,7 @@ { "id": "pdf-plus", "name": "PDF++", - "version": "0.38.9", + "version": "0.38.10", "minAppVersion": "1.4.16", "description": "The most Obsidian-native PDF annotation tool ever.", "author": "Ryota Ushio", diff --git a/manifest.json b/manifest.json index af5c605d..fcbea150 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "pdf-plus", "name": "PDF++", - "version": "0.38.9", + "version": "0.38.10", "minAppVersion": "1.4.16", "description": "The most Obsidian-native PDF annotation tool ever.", "author": "Ryota Ushio", diff --git a/package-lock.json b/package-lock.json index 36475f8c..888893d0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "obsidian-pdf-plus", - "version": "0.38.9", + "version": "0.38.10", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "obsidian-pdf-plus", - "version": "0.38.9", + "version": "0.38.10", "license": "MIT", "devDependencies": { "@cantoo/pdf-lib": "^1.20.3", diff --git a/package.json b/package.json index 90a643c5..9a0dae5e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "obsidian-pdf-plus", - "version": "0.38.9", + "version": "0.38.10", "description": "The most Obsidian-native PDF annotation tool ever.", "scripts": { "dev": "node esbuild.config.mjs", diff --git a/src/auto-copy.ts b/src/auto-copy.ts index 0992bfb0..635309ed 100644 --- a/src/auto-copy.ts +++ b/src/auto-copy.ts @@ -1,3 +1,5 @@ +import { Menu } from 'obsidian'; + import PDFPlus from 'main'; import { PDFPlusComponent } from 'lib/component'; @@ -8,12 +10,34 @@ export class AutoCopyMode extends PDFPlusComponent { constructor(plugin: PDFPlus) { super(plugin); if (this.settings.autoCopyToggleRibbonIcon) { + let menuShown = false; + this.iconEl = plugin.settings.autoCopyToggleRibbonIcon ? plugin.addRibbonIcon( this.settings.autoCopyIconName, `${plugin.manifest.name}: Toggle auto-copy`, - () => this.toggle() + () => { + if (!menuShown) this.toggle(); + } ) : null; + + if (this.iconEl) { + this.registerDomEvent(this.iconEl, 'contextmenu', (evt) => { + if (menuShown) return; + + const menu = new Menu(); + menu.addItem((item) => { + item.setIcon('lucide-settings') + .setTitle('Customize...') + .onClick(() => { + this.plugin.openSettingTab().scrollToHeading('auto-copy'); + }); + }); + menu.onHide(() => { menuShown = false }); + menu.showAtMouseEvent(evt); + menuShown = true; + }); + } } } diff --git a/src/color-palette.ts b/src/color-palette.ts index 2b82d9e1..82b896e7 100644 --- a/src/color-palette.ts +++ b/src/color-palette.ts @@ -1,7 +1,7 @@ import { Menu, Notice, Platform, setIcon, setTooltip } from 'obsidian'; import PDFPlus from 'main'; -import { KeysOfType, getEventCoords, isHexString } from 'utils'; +import { KeysOfType, getEventCoords, isHexString, showMenuUnderParentEl } from 'utils'; import { PDFViewerChild, Rect } from 'typings'; import { PDFPlusComponent } from 'lib/component'; @@ -80,10 +80,10 @@ export class ColorPalette extends PDFPlusComponent { this.addCropButton(this.paletteEl); - if (this.lib.isEditable(this.child)) { - this.addWriteFileToggle(this.paletteEl); - } else if (this.child.isFileExternal) { + if (this.child.isFileExternal) { this.addImportButton(this.paletteEl); + } else { + this.addWriteFileToggle(this.paletteEl); } this.statusContainerEl = this.paletteEl.createDiv('pdf-plus-color-palette-status-container'); @@ -137,6 +137,27 @@ export class ColorPalette extends PDFPlusComponent { evt.preventDefault(); }); + + let shown = false; + itemEl.addEventListener('contextmenu', () => { + if (shown) return; + + const menu = new Menu() + .addItem((item) => { + item.setIcon('lucide-settings') + .setTitle('Customize...') + .onClick(() => { + this.plugin.openSettingTab() + .scrollTo('colors'); + }); + }); + menu.onHide(() => { + shown = false; + }); + + showMenuUnderParentEl(menu, itemEl); + shown = true; + }); } setActiveItem(name: string | null) { @@ -152,7 +173,10 @@ export class ColorPalette extends PDFPlusComponent { setTooltip(buttonEl, tooltip); buttonEl.dataset.checkedIndex = '' + this[checkedIndexKey]; + let shown = false; buttonEl.addEventListener('click', () => { + if (shown) return; + const menu = new Menu(); for (let i = 0; i < itemNames.length; i++) { @@ -176,14 +200,12 @@ export class ColorPalette extends PDFPlusComponent { beforeShowMenu?.(menu); - const { x, bottom, width } = buttonEl.getBoundingClientRect(); - menu.setParentElement(buttonEl).showAtPosition({ - x, - y: bottom, - width, - overlap: true, - left: false + menu.onHide(() => { + shown = false; }); + + showMenuUnderParentEl(menu, buttonEl); + shown = true; }); }); } @@ -266,9 +288,33 @@ export class ColorPalette extends PDFPlusComponent { this.removeWriteFileToggle(); this.writeFileButtonEl = paletteEl.createDiv('clickable-icon', (el) => { - setIcon(el, 'lucide-save'); + setIcon(el, 'lucide-edit'); setTooltip(el, `${this.plugin.manifest.name}: Add ${this.plugin.settings.selectionBacklinkVisualizeStyle}s to file directly`); + el.toggleClass('is-disabled', !this.lib.isEditable(this.child)); + + let shown = false; el.addEventListener('click', () => { + if (!this.lib.isEditable(this.child)) { + if (shown) return; + + const menu = new Menu() + .addItem((item) => { + item.setIcon('lucide-settings') + .setTitle('Enable PDF editing...') + .onClick(() => { + this.plugin.openSettingTab() + .scrollToHeading('edit'); + }); + }); + menu.onHide(() => { + shown = false; + }); + + showMenuUnderParentEl(menu, el); + shown = true; + return; + } + this.setWriteFile(!this.writeFile); if (this.plugin.settings.syncWriteFileToggle && this.plugin.settings.syncDefaultWriteFileToggle) { @@ -277,6 +323,36 @@ export class ColorPalette extends PDFPlusComponent { this.plugin.trigger('color-palette-state-change', { source: this }); }); + + el.addEventListener('contextmenu', () => { + if (shown) return; + + const menu = new Menu() + .addItem((item) => { + item.setIcon('lucide-settings') + .setTitle(this.lib.isEditable(this.child) ? 'Disable PDF editing...' : 'Enable PDF editing...') + .onClick(() => { + this.plugin.openSettingTab() + .scrollToHeading('edit'); + }); + }); + if (this.lib.isEditable(this.child)) { + menu.addItem((item) => { + item.setIcon('lucide-settings') + .setTitle('Customize...') + .onClick(() => { + this.plugin.openSettingTab() + .scrollToHeading('annot'); + }); + }); + } + menu.onHide(() => { + shown = false; + }); + + showMenuUnderParentEl(menu, el); + shown = true; + }); }); if (this.cropButtonEl) { @@ -344,7 +420,7 @@ export class ColorPalette extends PDFPlusComponent { await this.app.vault.modifyBinary(file, buffer); this.removeImportButton(); - if (this.lib.isEditable(this.child) && this.paletteEl) { + if (this.paletteEl) { this.addWriteFileToggle(this.paletteEl); } new Notice(`${this.plugin.manifest.name}: Successfully imported the PDF file into the vault.`); @@ -367,6 +443,27 @@ export class ColorPalette extends PDFPlusComponent { el.addEventListener('click', () => { this.startRectangularSelection(false); }); + + let shown = false; + el.addEventListener('contextmenu', () => { + if (shown) return; + + const menu = new Menu() + .addItem((item) => { + item.setIcon('lucide-settings') + .setTitle('Customize...') + .onClick(() => { + this.plugin.openSettingTab() + .scrollToHeading('rect'); + }); + }); + menu.onHide(() => { + shown = false; + }); + + showMenuUnderParentEl(menu, el); + shown = true; + }); }); } @@ -436,16 +533,22 @@ export class ColorPalette extends PDFPlusComponent { pageEl.removeEventListener('touchend', onMouseUp); pageEl.removeChild(boxEl); + // Discard empty selections + if (selectBox.height <= 0 || selectBox.width <= 0) return; + + // Get the screen coordinates of the selection box relative to the page const left = selectBox.left - (pageRect.left + borderLeft + paddingLeft); const top = selectBox.top - (pageRect.top + borderTop + paddingTop); const right = left + selectBox.width; const bottom = top + selectBox.height; + // Convert screen coordinates to PDF coordinates const rect = window.pdfjsLib.Util.normalizeRect([ ...pageView.getPagePoint(left, bottom), ...pageView.getPagePoint(right, top) ]) as Rect; + // Copy an embed link to the selection this.lib.copyLink.copyEmbedLinkToRect( false, child, pageView.id, rect, this.plugin.settings.includeColorWhenCopyingRectLink @@ -456,21 +559,28 @@ export class ColorPalette extends PDFPlusComponent { toggle(); }; - pageEl.addEventListener('mousemove', onMouseMove); - pageEl.addEventListener('touchmove', onMouseMove); - pageEl.addEventListener('mouseup', onMouseUp); - pageEl.addEventListener('touchend', onMouseUp); + // `pageEl` is not a part of this component, so just `pageEl.addEventListener` & `pageEl.removeEventListener`is not enough. + // We have to explicitly remove the event listeners not just when the selection is done, but also + // when this component gets unloaded. + this.registerDomEvent(pageEl, 'mousemove', onMouseMove); + this.registerDomEvent(pageEl, 'touchmove', onMouseMove); + this.registerDomEvent(pageEl, 'mouseup', onMouseUp); + this.registerDomEvent(pageEl, 'touchend', onMouseUp); }; const toggle = () => { cropButtonEl.toggleClass('is-active', !cropButtonEl.hasClass('is-active')); viewerEl.toggleClass('pdf-plus-selecting', cropButtonEl.hasClass('is-active')); + this.register(() => viewerEl.removeClass('pdf-plus-selecting')); activeWindow.getSelection()?.empty(); if (cropButtonEl.hasClass('is-active')) { - viewerEl.addEventListener('mousedown', onMouseDown); - viewerEl.addEventListener('touchstart', onMouseDown); + // `viewerEl` is not a part of this component, so just `viewerEl.addEventListener` & `viewerEl.removeEventListener`is not enough. + // We have to explicitly remove the event listeners not just when the selection is done, but also + // when this component gets unloaded. + this.registerDomEvent(viewerEl, 'mousedown', onMouseDown); + this.registerDomEvent(viewerEl, 'touchstart', onMouseDown); } else { viewerEl.removeEventListener('mousedown', onMouseDown); viewerEl.removeEventListener('touchstart', onMouseDown); diff --git a/src/main.ts b/src/main.ts index 9663d71e..0d49a4d4 100644 --- a/src/main.ts +++ b/src/main.ts @@ -108,7 +108,7 @@ export default class PDFPlus extends Plugin { this.startTrackingActiveMarkdownFile(); - this.registerObsidianProtocolHandler('pdf-plus', this.obsidianProtocolHandler.bind(this)); + this.registerObsidianProtocolHandler('pdf-plus', this.obsidianProtocolHandler.bind(this)); } private checkVersion() { @@ -201,17 +201,52 @@ export default class PDFPlus extends Plugin { this.register(() => this.autoCopyMode.unload()); if (this.settings.autoFocusToggleRibbonIcon) { + let menuShown = false; + this.autoFocusToggleIconEl = this.addRibbonIcon(this.settings.autoFocusIconName, `${this.manifest.name}: Toggle auto-focus`, () => { - this.toggleAutoFocus(); + if (!menuShown) this.toggleAutoFocus(); }); this.autoFocusToggleIconEl.toggleClass('is-active', this.settings.autoFocus); + + this.registerDomEvent(this.autoFocusToggleIconEl, 'contextmenu', (evt) => { + if (menuShown) return; + + const menu = new Menu(); + menu.addItem((item) => { + item.setIcon('lucide-settings') + .setTitle('Customize...') + .onClick(() => { + this.openSettingTab().scrollToHeading('auto-focus'); + }); + }); + menu.onHide(() => { menuShown = false }); + menu.showAtMouseEvent(evt); + menuShown = true; + }); } if (this.settings.autoPasteToggleRibbonIcon) { + let menuShown = false; + this.autoPasteToggleIconEl = this.addRibbonIcon(this.settings.autoPasteIconName, `${this.manifest.name}: Toggle auto-paste`, () => { - this.toggleAutoPaste(); + if (!menuShown) this.toggleAutoPaste(); }); this.autoPasteToggleIconEl.toggleClass('is-active', this.settings.autoPaste); + this.registerDomEvent(this.autoPasteToggleIconEl, 'contextmenu', (evt) => { + if (menuShown) return; + + const menu = new Menu(); + menu.addItem((item) => { + item.setIcon('lucide-settings') + .setTitle('Customize...') + .onClick(() => { + this.openSettingTab().scrollToHeading('auto-paste'); + }); + }); + menu.onHide(() => { menuShown = false }); + menu.showAtMouseEvent(evt); + menuShown = true; + }); } } diff --git a/src/toolbar.ts b/src/toolbar.ts index f92daf47..555e0ba4 100644 --- a/src/toolbar.ts +++ b/src/toolbar.ts @@ -4,7 +4,7 @@ import PDFPlus from 'main'; import { PDFPlusComponent } from 'lib/component'; import { ColorPalette } from 'color-palette'; import { PDFToolbar, PDFViewerChild } from 'typings'; -import { isMouseEventExternal } from 'utils'; +import { isMouseEventExternal, showMenuUnderParentEl } from 'utils'; import { ScrollMode, SpreadMode } from 'pdfjs-enums'; @@ -227,14 +227,7 @@ export class PDFPlusToolbar extends PDFPlusComponent { menu.onHide(() => { shown = false; }); - const { x, bottom, width } = dropdownEl.getBoundingClientRect(); - menu.setParentElement(dropdownEl).showAtPosition({ - x, - y: bottom, - width, - overlap: true, - left: false - }); + showMenuUnderParentEl(menu, dropdownEl); shown = true; } }); @@ -250,7 +243,7 @@ export class PDFPlusToolbar extends PDFPlusComponent { if (!this.settings.zoomLevelInputBoxInToolbar) return; const { toolbar } = this; - + const eventBus = toolbar.pdfViewer.eventBus; const pdfViewer = toolbar.pdfViewer.pdfViewer; if (!eventBus || !pdfViewer) return; diff --git a/src/utils/index.ts b/src/utils/index.ts index 7f67d2aa..a7fc3d99 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1,4 +1,4 @@ -import { Component, Modifier, Platform, CachedMetadata, ReferenceCache, parseLinktext } from 'obsidian'; +import { Component, Modifier, Platform, CachedMetadata, ReferenceCache, parseLinktext, Menu } from 'obsidian'; import { PDFDict, PDFName, PDFRef } from '@cantoo/pdf-lib'; import { ObsidianViewer } from 'typings'; @@ -19,6 +19,19 @@ export function getDirectPDFObj(dict: PDFDict, key: string) { return obj; } +export function showMenuUnderParentEl(menu: Menu, parentEl: HTMLElement) { + const { x, bottom, width } = parentEl.getBoundingClientRect(); + menu.setParentElement(parentEl) + .showAtPosition({ + x, + y: bottom, + width, + overlap: true, + left: false + }); + return menu; +} + // Thanks https://stackoverflow.com/a/54246501 export function camelCaseToKebabCase(camelCaseStr: string) { return camelCaseStr.replace(/[A-Z]/g, letter => `-${letter.toLowerCase()}`); diff --git a/styles.css b/styles.css index b65af49c..1f9ce088 100644 --- a/styles.css +++ b/styles.css @@ -152,6 +152,14 @@ settings: } } +.pdf-toolbar .clickable-icon.is-disabled { + background-color: inherit; + + &> svg { + color: var(--text-faint); + } +} + /* .pdf-page-input, */ .pdf-zoom-level-input { width: 6ch; @@ -164,7 +172,7 @@ settings: margin-right: var(--size-4-1); font-size: var(--font-ui-small); font-variant-numeric: tabular-nums; -} +} .pdf-plus-settings { --pdf-plus-settings-header-height: var(--size-4-12);