From f527c6ad29d4f405f8bd63217550a678090d1b38 Mon Sep 17 00:00:00 2001 From: RyotaUshio Date: Tue, 11 Jun 2024 20:55:20 +0900 Subject: [PATCH] release: 0.40.6 --- manifest-beta.json | 2 +- manifest.json | 2 +- package-lock.json | 4 ++-- package.json | 2 +- src/bib.ts | 13 +++++----- src/color-palette.ts | 7 +++--- src/context-menu.ts | 4 ++-- src/lib/copy-link.ts | 2 +- src/lib/index.ts | 28 +++++++--------------- src/lib/outlines.ts | 2 +- src/main.ts | 5 ++-- src/patchers/pdf-internals.ts | 27 +++++++++++++++++++++ src/post-process/pdf-link-like.ts | 6 ++--- src/settings.ts | 18 +++++++++++--- src/typings.d.ts | 40 ++++++++++++++++++++----------- src/utils/index.ts | 4 ++-- src/vim/command-line.ts | 3 ++- src/vim/visual.ts | 4 ++++ 18 files changed, 110 insertions(+), 63 deletions(-) diff --git a/manifest-beta.json b/manifest-beta.json index 3891ec9e..4c0b9d5a 100644 --- a/manifest-beta.json +++ b/manifest-beta.json @@ -1,7 +1,7 @@ { "id": "pdf-plus", "name": "PDF++", - "version": "0.40.5", + "version": "0.40.6", "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 3891ec9e..4c0b9d5a 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "pdf-plus", "name": "PDF++", - "version": "0.40.5", + "version": "0.40.6", "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 4b54d21d..fd8673bc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "obsidian-pdf-plus", - "version": "0.40.5", + "version": "0.40.6", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "obsidian-pdf-plus", - "version": "0.40.5", + "version": "0.40.6", "license": "MIT", "devDependencies": { "@cantoo/pdf-lib": "^1.21.0", diff --git a/package.json b/package.json index 3f3f8765..2121e5f1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "obsidian-pdf-plus", - "version": "0.40.5", + "version": "0.40.6", "description": "The most Obsidian-native PDF annotation tool ever.", "scripts": { "dev": "node esbuild.config.mjs", diff --git a/src/bib.ts b/src/bib.ts index f33be886..ed9f6776 100644 --- a/src/bib.ts +++ b/src/bib.ts @@ -4,7 +4,7 @@ import { PDFDocumentProxy } from 'pdfjs-dist'; import PDFPlus from 'main'; import { PDFPlusComponent } from 'lib/component'; import { genId, isCanvas, isEmbed, isHoverPopover, isNonEmbedLike, onModKeyPress, toSingleLine } from 'utils'; -import { PDFViewerChild, PDFjsDestArray, TextContentItem } from 'typings'; +import { PDFViewerChild, PDFJsDestArray, TextContentItem } from 'typings'; export type AnystyleJson = Partial<{ @@ -61,7 +61,7 @@ export class BibliographyManager extends PDFPlusComponent { const promises: Promise[] = []; for (const destId in dests) { if (destId.startsWith('cite.')) { - const destArray = dests[destId] as PDFjsDestArray; + const destArray = dests[destId] as PDFJsDestArray; promises.push( BibliographyManager.getBibliographyTextFromDest(destArray, doc) .then((bibInfo) => { @@ -151,7 +151,8 @@ export class BibliographyManager extends PDFPlusComponent { // Clean up the file when this PDF viewer is unloaded this.register(() => app.vault.adapter.remove(anystyleInputPath)); - const { spawn } = await import('child_process'); + // eslint-disable-next-line @typescript-eslint/no-var-requires + const { spawn } = require('child_process') as typeof import('child_process'); return new Promise((resolve) => { const anystyleProcess = spawn(anystylePath, ['parse', anystyleInputFullPath]); @@ -208,10 +209,10 @@ export class BibliographyManager extends PDFPlusComponent { return null; } - static async getBibliographyTextFromDest(dest: string | PDFjsDestArray, doc: PDFDocumentProxy) { - let explicitDest: PDFjsDestArray | null = null; + static async getBibliographyTextFromDest(dest: string | PDFJsDestArray, doc: PDFDocumentProxy) { + let explicitDest: PDFJsDestArray | null = null; if (typeof dest === 'string') { - explicitDest = (await doc.getDestination(dest)) as PDFjsDestArray | null; + explicitDest = (await doc.getDestination(dest)) as PDFJsDestArray | null; } else { explicitDest = dest; } diff --git a/src/color-palette.ts b/src/color-palette.ts index cefdadd0..d0352414 100644 --- a/src/color-palette.ts +++ b/src/color-palette.ts @@ -116,7 +116,8 @@ export class ColorPalette extends PDFPlusComponent { itemEl.createDiv(ColorPalette.CLS + '-item-inner'); this.setTooltipToActionItem(itemEl, name); - itemEl.addEventListener('click', (evt) => this.onItemClick(itemEl, name, evt)); + // Listen to pointerup, not click, to prevent the selection from being cleared before the handler is called on mobile devices. + itemEl.addEventListener('pointerup', (evt) => this.onItemPointerUp(itemEl, name, evt)); let shown = false; itemEl.addEventListener('contextmenu', () => { @@ -140,7 +141,7 @@ export class ColorPalette extends PDFPlusComponent { }); } - onItemClick(itemEl: HTMLElement, name: string | null, evt: MouseEvent) { + onItemPointerUp(itemEl: HTMLElement, name: string | null, evt: MouseEvent) { const colorChanged = !itemEl.hasClass('is-active'); this.setActiveItem(name); if (this.plugin.settings.syncColorPaletteItem && this.plugin.settings.syncDefaultColorPaletteItem) { @@ -159,7 +160,7 @@ export class ColorPalette extends PDFPlusComponent { this.lib.copyLink.copyLinkToSelection(false, { copyFormat: template }, name ?? undefined); } - evt.preventDefault(); + evt.preventDefault(); } setActiveItem(name: string | null) { diff --git a/src/context-menu.ts b/src/context-menu.ts index dc4b6f91..9c5d3f2e 100644 --- a/src/context-menu.ts +++ b/src/context-menu.ts @@ -198,7 +198,7 @@ export const onOutlineItemContextMenu = (plugin: PDFPlus, child: PDFViewerChild, plugin.lastCopiedDestInfo = { file, destName: dest }; } else { const pageNumber = await item.getPageNumber(); - const destArray = lib.normalizePDFjsDestArray(dest, pageNumber); + const destArray = lib.normalizePDFJsDestArray(dest, pageNumber); plugin.lastCopiedDestInfo = { file, destArray }; } }) @@ -706,7 +706,7 @@ export class PDFPlusContextMenu extends PDFPlusMenu { .setIcon('lucide-copy') .onClick(() => { // How does the electron version differ? - navigator.clipboard.writeText(selectedText); + navigator.clipboard.writeText(this.plugin.settings.copyAsSingleLine ? selectedText : (selectionObj?.toString() ?? '')); }); }); } diff --git a/src/lib/copy-link.ts b/src/lib/copy-link.ts index 95692932..5c985a60 100644 --- a/src/lib/copy-link.ts +++ b/src/lib/copy-link.ts @@ -171,7 +171,7 @@ export class copyLinkLib extends PDFPlusLibSubmodule { async getTextToCopyForOutlineItemDynamic(child: PDFViewerChild, file: TFile, item: PDFOutlineTreeNode) { const dest = await item.getExplicitDestination(); const pageNumber = await item.getPageNumber(); - const destArray = this.lib.normalizePDFjsDestArray(dest, pageNumber); + const destArray = this.lib.normalizePDFJsDestArray(dest, pageNumber); const subpath = this.lib.destArrayToSubpath(destArray); return (sourcePath?: string) => this.getTextToCopy( diff --git a/src/lib/index.ts b/src/lib/index.ts index 10627af7..17365fc7 100644 --- a/src/lib/index.ts +++ b/src/lib/index.ts @@ -14,11 +14,10 @@ import { PDFOutlines } from './outlines'; import { NameTree, NumberTree } from './name-or-number-trees'; import { PDFNamedDestinations } from './destinations'; import { PDFPageLabels } from './page-labels'; -import { AnnotationElement, CanvasFileNode, CanvasNode, CanvasView, DestArray, EventBus, ObsidianViewer, PDFOutlineViewer, PDFPageView, PDFSidebar, PDFThumbnailView, PDFView, PDFViewExtraState, PDFViewerChild, PDFjsDestArray, PDFViewer, PDFEmbed, PDFViewState, Rect, TextContentItem, PDFFindBar, PDFSearchSettings } from 'typings'; +import { AnnotationElement, CanvasFileNode, CanvasNode, CanvasView, DestArray, EventBus, ObsidianViewer, PDFPageView, PDFView, PDFViewExtraState, PDFViewerChild, PDFJsDestArray, PDFViewer, PDFEmbed, PDFViewState, Rect, TextContentItem, PDFFindBar, PDFSearchSettings, PDFJsEventMap } from 'typings'; import { PDFCroppedEmbed } from 'pdf-cropped-embed'; import { PDFBacklinkIndex } from './pdf-backlink-index'; import { Speech } from './speech'; -import { SidebarView } from 'pdfjs-enums'; export class PDFPlusLib { @@ -54,18 +53,7 @@ export class PDFPlusLib { /** * @param component A component such that the callback is unregistered when the component is unloaded, or `null` if the callback should be called only once. */ - registerPDFEvent(name: 'outlineloaded', eventBus: EventBus, component: Component | null, callback: (data: { source: PDFOutlineViewer, outlineCount: number, currentOutlineItemPromise: Promise }) => any): void; - registerPDFEvent(name: 'thumbnailrendered', eventBus: EventBus, component: Component | null, callback: (data: { source: PDFThumbnailView, pageNumber: number, pdfPage: PDFPageProxy }) => any): void; - registerPDFEvent(name: 'sidebarviewchanged', eventBus: EventBus, component: Component | null, callback: (data: { source: PDFSidebar, view: SidebarView }) => any): void; - registerPDFEvent(name: 'textlayerrendered', eventBus: EventBus, component: Component | null, callback: (data: { source: PDFPageView, pageNumber: number }) => any): void; - registerPDFEvent(name: 'annotationlayerrendered', eventBus: EventBus, component: Component | null, callback: (data: { source: PDFPageView, pageNumber: number }) => any): void; - registerPDFEvent(name: 'pagesloaded', eventBus: EventBus, component: Component | null, callback: (data: { source: PDFViewer, pagesCount: number }) => any): void; - registerPDFEvent(name: 'pagerendered', eventBus: EventBus, component: Component | null, callback: (data: { source: PDFPageView, pageNumber: number, cssTransform: boolean, timestamp: number, error: any }) => any): void; - registerPDFEvent(name: 'pagechanging', eventBus: EventBus, component: Component | null, callback: (data: { source: PDFViewer, pageNumber: number, pageLabel: string | null, previous: number }) => any): void; - registerPDFEvent(name: 'findbaropen', eventBus: EventBus, component: Component | null, callback: (data: { source: PDFFindBar }) => any): void; - registerPDFEvent(name: 'findbarclose', eventBus: EventBus, component: Component | null, callback: (data: { source: PDFFindBar }) => any): void; - - registerPDFEvent(name: string, eventBus: EventBus, component: Component | null, callback: (data: any) => any) { + registerPDFEvent(name: K, eventBus: EventBus, component: Component | null, callback: (data: PDFJsEventMap[K]) => any) { const listener = async (data: any) => { await callback(data); if (!component) eventBus.off(name, listener); @@ -293,14 +281,14 @@ export class PDFPlusLib { * shall be retained unchanged. A zoom value of 0 has the same meaning as a null value." */ async destIdToSubpath(destId: string, doc: PDFDocumentProxy) { - const dest = await doc.getDestination(destId) as PDFjsDestArray; + const dest = await doc.getDestination(destId) as PDFJsDestArray; if (!dest) return null; return this.pdfJsDestArrayToSubpath(dest, doc); } - async pdfJsDestArrayToSubpath(dest: PDFjsDestArray, doc: PDFDocumentProxy) { + async pdfJsDestArrayToSubpath(dest: PDFJsDestArray, doc: PDFDocumentProxy) { const page = await doc.getPageIndex(dest[0]); - return this.destArrayToSubpath(this.normalizePDFjsDestArray(dest, page + 1)); + return this.destArrayToSubpath(this.normalizePDFJsDestArray(dest, page + 1)); } /** @@ -308,7 +296,7 @@ export class PDFPlusLib { * @param dest * @param pageNumber 1-based page number */ - normalizePDFjsDestArray(dest: PDFjsDestArray, pageNumber: number): DestArray { + normalizePDFJsDestArray(dest: PDFJsDestArray, pageNumber: number): DestArray { return [ pageNumber - 1, dest[1].name, @@ -340,9 +328,9 @@ export class PDFPlusLib { async ensureDestArray(dest: string | DestArray, doc: PDFDocumentProxy) { if (typeof dest === 'string') { - const destArray = await doc.getDestination(dest) as PDFjsDestArray; + const destArray = await doc.getDestination(dest) as PDFJsDestArray; if (!destArray) return null; - dest = this.normalizePDFjsDestArray(destArray, await doc.getPageIndex(destArray[0]) + 1); + dest = this.normalizePDFJsDestArray(destArray, await doc.getPageIndex(destArray[0]) + 1); } return dest; diff --git a/src/lib/outlines.ts b/src/lib/outlines.ts index 67373a5e..6cbb9072 100644 --- a/src/lib/outlines.ts +++ b/src/lib/outlines.ts @@ -148,7 +148,7 @@ export class PDFOutlines { } } else { const pageNumber = await node.getPageNumber(); - if (JSON.stringify(this.lib.normalizePDFjsDestArray(dest, pageNumber)) === JSON.stringify(outlineDest)) { + if (JSON.stringify(this.lib.normalizePDFJsDestArray(dest, pageNumber)) === JSON.stringify(outlineDest)) { found = outlineItem; } } diff --git a/src/main.ts b/src/main.ts index 4d67a7f9..c9df490c 100644 --- a/src/main.ts +++ b/src/main.ts @@ -87,7 +87,6 @@ export default class PDFPlus extends Plugin { await this.loadSettings(); await this.saveSettings(); - this.addSettingTab(this.settingTab = new PDFPlusSettingTab(this)); this.domManager = this.addChild(new DomManager(this)); this.domManager.registerCalloutRenderer(); @@ -111,6 +110,8 @@ export default class PDFPlus extends Plugin { this.startTrackingActiveMarkdownFile(); this.registerObsidianProtocolHandler('pdf-plus', this.obsidianProtocolHandler.bind(this)); + + this.addSettingTab(this.settingTab = new PDFPlusSettingTab(this)); } async onunload() { @@ -129,7 +130,7 @@ export default class PDFPlus extends Plugin { } private checkVersion() { - const untestedVersion = '1.6.3'; + const untestedVersion = '1.7.0'; if (requireApiVersion(untestedVersion)) { console.warn(`${this.manifest.name}: This plugin has not been tested on Obsidian ${untestedVersion} or above. Please report any issue you encounter on GitHub (https://github.com/RyotaUshio/obsidian-pdf-plus/issues/new/choose).`); } diff --git a/src/patchers/pdf-internals.ts b/src/patchers/pdf-internals.ts index 0405c28c..0ebfb281 100644 --- a/src/patchers/pdf-internals.ts +++ b/src/patchers/pdf-internals.ts @@ -189,6 +189,19 @@ const patchPDFViewerChild = (plugin: PDFPlus, child: PDFViewerChild) => { addColorPaletteToToolbar(); plugin.on('update-dom', addColorPaletteToToolbar); + // Use !isMobile, not isDesktopApp, because in app.js, PDFViewerChild.onMobileCopy is called when isMobile is true. + if (!Platform.isMobile) { + const eventBus = this.pdfViewer.eventBus; + if (eventBus) { + eventBus.on('textlayerrendered', ({ source: pageView }) => { + const textLayerDiv = pageView?.textLayer?.div; + if (textLayerDiv) { + textLayerDiv.addEventListener('copy', onCopy); + } + }); + } + } + return ret; } @@ -820,6 +833,7 @@ const patchPDFViewerChild = (plugin: PDFPlus, child: PDFViewerChild) => { return function (this: PDFViewerChild, evt: ClipboardEvent, pageView: PDFPageView) { switch (plugin.settings.mobileCopyAction) { case 'text': + onCopy(evt); return; case 'pdf-plus': setTimeout(() => lib.commands.copyLink(false)); @@ -839,6 +853,19 @@ const patchPDFViewerChild = (plugin: PDFPlus, child: PDFViewerChild) => { } } })); + + const onCopy = (evt: ClipboardEvent) => { + if (!plugin.settings.copyAsSingleLine) return; + + const dataTransfer = evt.clipboardData; + if (!dataTransfer) return; + + let text = (evt.target as HTMLElement).win.getSelection()?.toString(); // dataTransfer.getData('text/plain'); + if (text) { + text = lib.toSingleLine(text); + dataTransfer.setData('text/plain', text); + } + }; } /** Monkey-patch ObsidianViewer so that it can open external PDF files. */ diff --git a/src/post-process/pdf-link-like.ts b/src/post-process/pdf-link-like.ts index 641c65af..d6dcd4dd 100644 --- a/src/post-process/pdf-link-like.ts +++ b/src/post-process/pdf-link-like.ts @@ -2,7 +2,7 @@ import { App, HoverParent, HoverPopover, Keymap } from 'obsidian'; import PDFPlus from 'main'; import { PDFPlusLib } from 'lib'; -import { AnnotationElement, PDFOutlineTreeNode, PDFViewerChild, PDFjsDestArray } from 'typings'; +import { AnnotationElement, PDFOutlineTreeNode, PDFViewerChild, PDFJsDestArray } from 'typings'; import { isCitationId, isMouseEventExternal, isTargetHTMLElement } from 'utils'; import { BibliographyManager } from 'bib'; @@ -159,7 +159,7 @@ abstract class PDFLinkLikePostProcessor implements HoverParent { * The destination can be either a string (a named destination) or an array (an explicit destination). */ abstract class PDFDestinationHolderPostProcessor extends PDFLinkLikePostProcessor { - abstract getDest(): string | PDFjsDestArray; + abstract getDest(): string | PDFJsDestArray; async getLinkText(evt: MouseEvent) { const { lib, child, targetEl } = this; @@ -226,7 +226,7 @@ export class PDFInternalLinkPostProcessor extends PDFDestinationHolderPostProces return super.getLinkText(evt); } - getDest(): string | PDFjsDestArray { + getDest(): string | PDFJsDestArray { return this.linkAnnotationElement.data.dest; } diff --git a/src/settings.ts b/src/settings.ts index a4217955..577dd2a9 100644 --- a/src/settings.ts +++ b/src/settings.ts @@ -270,6 +270,7 @@ export interface PDFPlusSettings { enableBibInEmbed: boolean; enableBibInHoverPopover: boolean; enableBibInCanvas: boolean; + copyAsSingleLine: boolean; removeWhitespaceBetweenCJChars: boolean; vim: boolean; vimrcPath: string; @@ -543,6 +544,7 @@ export const DEFAULT_SETTINGS: PDFPlusSettings = { enableBibInEmbed: false, enableBibInHoverPopover: false, enableBibInCanvas: true, + copyAsSingleLine: true, removeWhitespaceBetweenCJChars: true, vim: false, vimrcPath: '', @@ -2913,7 +2915,7 @@ export class PDFPlusSettingTab extends PluginSettingTab { this.addSetting() .then((setting) => { this.renderMarkdown([ - 'Currently, the following keys are supported (some might change in the future):', + 'The default keybindings are as follows. You can customize them be creating a "vimrc" file and providing its path in the setting below.', '', '- `j`/`k`/`h`/`l`: Scroll down/up/left/right', '- `J`: Go to next page', @@ -2942,7 +2944,7 @@ export class PDFPlusSettingTab extends PluginSettingTab { '- `f`: Enter hint mode by running `:hint` (experimental)', '- ``: Go back to normal mode, abort search, etc', '', - 'Many of them can be combined with counts. For example:', + 'Many of the commands above can be combined with counts. For example:', '- `2j` scrolls down the page twice as much as `j`.', '- `2J` advances two pages.', '- `10G` takes you to page 10.', @@ -2954,7 +2956,7 @@ export class PDFPlusSettingTab extends PluginSettingTab { .setDesc('Reopen the PDF viewers after changing this option.'); this.showConditionally([ this.addTextSetting('vimrcPath', undefined, () => this.plugin.vimrc = null) - .setName('Vimrc file path (experimental)') + .setName('Vimrc file path (optional)') .then(async (setting) => { await this.renderMarkdown([ 'Only the [Ex commands supported by PDF++](https://github.com/RyotaUshio/obsidian-pdf-plus/blob/main/src/vim/ex-commands.ts) are allowed.', @@ -2999,6 +3001,8 @@ export class PDFPlusSettingTab extends PluginSettingTab { 'Tips:', '- You can use `o` to swap the start and end of the selection.', '- As you know, `/` and `?` keys initiate search. Pressing `gn`/`gN` after the search will select the search result. You can also use search to extend the current selection to the search result.', + '', + 'Note: On mobile, word-wise motions (`w`/`e`/`b`) might not work as expected around punctuations. Contributions to fix this are welcome!', ], setting.descEl); }), this.addHeading('Outline mode', 'vim-outline'), @@ -3107,6 +3111,14 @@ export class PDFPlusSettingTab extends PluginSettingTab { this.addToggleSetting('removeWhitespaceBetweenCJChars') .setName('Remove half-width whitespace between two Chinese/Japanese characters when copying text') .setDesc('Such whitespace can be introduced as a result of poor post-processing of OCR (optical character recognition). Enable this option to remove it when copying links to text selections.'); + this.addToggleSetting('copyAsSingleLine') + .setName('Override the default copy behavior in the PDF viewer') + .then((setting) => { + setting.descEl.appendText('If enabled, whenever you copy text from the PDF viewer (using Ctrl/Cmd+C or via context menu), the text will go through the same pre-processing as the "' + this.plugin.lib.commands.stripCommandNamePrefix(this.plugin.lib.commands.getCommand('copy-link-to-selection').name) + '" command before written to the clipboard. The pre-processing includes transforming multi-line text into a single line by removing line breaks (if a word is split across lines, it will be concatenated), which is useful because it prevents the copied text from being split into multiple lines unnaturally. If the previous option is enabled, the whitespace removal will also be applied.'); + setting.descEl.appendText(' Also note that on mobile devices, the action performed by "Copy" depends on the '); + setting.descEl.appendChild(this.createLinkTo('mobileCopyAction')); + setting.descEl.appendText(' option.'); + }); if (Platform.isDesktopApp) { this.addTextAreaSetting('PATH') .then((setting) => { diff --git a/src/typings.d.ts b/src/typings.d.ts index 79447b0a..7b9e943d 100644 --- a/src/typings.d.ts +++ b/src/typings.d.ts @@ -255,20 +255,20 @@ interface PDFOutlineTreeNode { coverEl: HTMLElement; // probably the same as selfEl innerEl: HTMLElement; // div.tree-item-inner pageNumber?: number; - explicitDest?: PDFjsDestArray; + explicitDest?: PDFJsDestArray; item: { title: string; bold: boolean; italic: boolean; color: Uint8ClampedArray; - dest: string | PDFjsDestArray; + dest: string | PDFJsDestArray; url: string | null; items: PDFOutlineTreeNode[]; }; owner: PDFOutlineViewer; collapsed: boolean; getPageNumber(): Promise; // return this.pageNumber if set, otherwise newly fetch it - getExplicitDestination(): Promise; // return this.explicitDest if set, otherwise newly fetch it + getExplicitDestination(): Promise; // return this.explicitDest if set, otherwise newly fetch it getMarkdownLink(): Promise; setActive(active: boolean): void; setCollapsed(collapsed: boolean, smooth?: boolean): Promise; @@ -451,11 +451,11 @@ type Rect = [number, number, number, number]; * destType: "XYZ" or "FitBH" */ type DestArray = [page: number, destType: string, ...params: (number | null)[]]; -type PDFjsDestArray = [pageRef: { num: number, gen: number }, destType: { name: string }, ...params: (number | null)[]]; +type PDFJsDestArray = [pageRef: { num: number, gen: number }, destType: { name: string }, ...params: (number | null)[]]; type PdfLibDestArray = [pageRef: PDFRef, destType: PDFName, ...params: (PDFNumber | typeof PDFNull)[]]; interface PDFLinkService { - goToDestination(dest: string | PDFjsDestArray): Promise; + goToDestination(dest: string | PDFJsDestArray): Promise; } interface AnnotationElement { @@ -493,15 +493,27 @@ interface TextContentItem { } interface EventBus { - on(name: string, callback: (...args: any[]) => any): void; - on(name: 'outlineloaded', callback: (data: { source: PDFOutlineViewer, outlineCount: number, currentOutlineItemPromise: Promise }) => any): void; - on(name: 'thumbnailrendered', callback: (data: { source: PDFThumbnailView, pageNumber: number, pdfPage: PDFPageProxy }) => any): void; - off(name: string, callback: (...args: any[]) => any): void; - dispatch(name: string, data: { source?: any }): void; - dispatch(name: 'togglesidebar', data: { open: boolean, source?: any }): void; - dispatch(name: 'switchspreadmode', data: { mode: SpreadMode, source?: any }): void; - dispatch(name: 'switchscrollmode', data: { mode: ScrollMode, source?: any }): void; - dispatch(name: 'scalechanged', data: { value: string, source?: any }): void; + on(name: K, callback: (data: PDFJsEventMap[K]) => any): void; + off(name: K, callback: (data: PDFJsEventMap[K]) => any): void; + dispatch(name: K, data: PDFJsEventMap[K]): void; +} + +interface PDFJsEventMap { + outlineloaded: { source: PDFOutlineViewer, outlineCount: number, currentOutlineItemPromise: Promise }; + thumbnailrendered: { source: PDFThumbnailView, pageNumber: number, pdfPage: PDFPageProxy }; + sidebarviewchanged: { source: PDFSidebar, view: SidebarView }; + textlayerrendered: { source: PDFPageView, pageNumber: number }; + annotationlayerrendered: { source: PDFPageView, pageNumber: number }; + pagesloaded: { source: PDFViewer, pagesCount: number }; + pagerendered: { source: PDFPageView, pageNumber: number, cssTransform: boolean, timestamp: number, error: any }; + pagechanging: { source: PDFViewer, pageNumber: number, pageLabel: string | null, previous: number }; + findbaropen: { source: PDFFindBar }; + findbarclose: { source: PDFFindBar }; + togglesidebar: { open: boolean, source?: any }; + switchspreadmode: { mode: SpreadMode, source?: any }; + switchscrollmode: { mode: ScrollMode, source?: any }; + scalechanged: { value: string, source?: any }; + scalechanging: { source: PDFViewer, scale: number, presetValue?: number }; } interface PDFEmbed extends Embed { diff --git a/src/utils/index.ts b/src/utils/index.ts index e58e6f00..b996bf45 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1,7 +1,7 @@ import { Component, Modifier, Platform, CachedMetadata, ReferenceCache, parseLinktext, Menu, Scope, KeymapEventListener } from 'obsidian'; import { PDFDict, PDFName, PDFRef } from '@cantoo/pdf-lib'; -import { ObsidianViewer, PDFjsDestArray } from 'typings'; +import { ObsidianViewer, PDFJsDestArray } from 'typings'; export * from './color'; export * from './suggest'; @@ -348,7 +348,7 @@ export function encodeLinktext(linktext: string) { return linktext.replace(/[\\\x00\x08\x0B\x0C\x0E-\x1F ]/g, (component) => encodeURIComponent(component)); } -export function isCitationId(dest: string | PDFjsDestArray): dest is string { +export function isCitationId(dest: string | PDFJsDestArray): dest is string { return typeof dest === 'string' && dest.startsWith('cite.'); } diff --git a/src/vim/command-line.ts b/src/vim/command-line.ts index 9c6d71d3..0d7586c5 100644 --- a/src/vim/command-line.ts +++ b/src/vim/command-line.ts @@ -111,7 +111,8 @@ export class VimCommandLineMode extends VimBindingsMode { return; } - const { exec } = await import('child_process'); + // eslint-disable-next-line @typescript-eslint/no-var-requires + const { exec } = require('child_process') as typeof import('child_process'); const env = process.env; if (this.settings.PATH) env.PATH = this.settings.PATH; diff --git a/src/vim/visual.ts b/src/vim/visual.ts index 5f526772..2f0136bc 100644 --- a/src/vim/visual.ts +++ b/src/vim/visual.ts @@ -53,6 +53,10 @@ export class VimVisualMode extends VimBindingsMode { 'k': visualMotion((n) => this.extendSelectionByLine(-(n ?? 1))), 'h': visualMotion((n) => this.extendSelectionByChar(n ?? 1, false)), 'l': visualMotion((n) => this.extendSelectionByChar(n ?? 1, true)), + // On mobile, word-wise motions (w/e/b) does not work as expected around punctuations, + // and this is because the Selection.modify method (https://developer.mozilla.org/en-US/docs/Web/API/Selection/modify) + // behaves differently on mobile. + // TODO: fix this! 'w': visualMotion((n) => { const selection = this.doc.getSelection(); if (selection) {