Skip to content

Commit

Permalink
release: 0.4.0
Browse files Browse the repository at this point in the history
  • Loading branch information
RyotaUshio committed Dec 20, 2023
1 parent 347dfa9 commit 886adac
Show file tree
Hide file tree
Showing 8 changed files with 192 additions and 47 deletions.
2 changes: 1 addition & 1 deletion manifest.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"id": "pdf-plus",
"name": "PDF++",
"version": "0.3.0",
"version": "0.4.0",
"minAppVersion": "1.3.5",
"description": "Enhance PDF viewer & embeds.",
"author": "Ryota Ushio",
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "obsidian-pdf-plus",
"version": "0.3.0",
"version": "0.4.0",
"description": "An Obsidian.md plugin to enhance PDF viewer & embeds.",
"scripts": {
"dev": "node esbuild.config.mjs",
Expand Down
34 changes: 33 additions & 1 deletion src/main.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { Notice, Plugin } from 'obsidian';
import { DEFAULT_SETTINGS, PDFPlusSettings, PDFPlusSettingTab } from './settings';
import { DEFAULT_SETTINGS, PDFPlusSettings, PDFPlusSettingTab } from 'settings';
import { patchPDF, patchWorkspace } from 'patch';
import { PDFView, PDFViewerChild } from 'typings';


export default class PDFPlus extends Plugin {
settings: PDFPlusSettings;
pdfViwerChildren: Map<HTMLElement, PDFViewerChild> = new Map();

async onload() {
await this.loadSettings();
Expand Down Expand Up @@ -37,6 +39,8 @@ export default class PDFPlus extends Plugin {
evt.preventDefault();
}
}, { passive: false });

this.registerCommands();
}

async loadSettings() {
Expand All @@ -46,4 +50,32 @@ export default class PDFPlus extends Plugin {
async saveSettings() {
await this.saveData(this.settings);
}

registerCommands() {
this.addCommand({
id: 'copy-selection',
name: 'Copy selection',
checkCallback: (checking: boolean) => {
const selection = window.getSelection();
if (!selection) return false;
const range = selection.rangeCount > 0 ? selection.getRangeAt(0) : null;
const pageEl = range?.startContainer.parentElement?.closest('.page');
if (!pageEl || !(pageEl.instanceOf(HTMLElement)) || pageEl.dataset.pageNumber === undefined) return false;

const viewerEl = pageEl.closest<HTMLElement>('.pdf-viewer');
if (!viewerEl) return false;

const child = this.pdfViwerChildren.get(viewerEl);
if (!child) return false;

if (!checking) {
const page = parseInt(pageEl.dataset.pageNumber);
const selectionStr = child.getTextSelectionRangeStr(pageEl);
const linktext = child.getMarkdownLink(`#page=${page}&selection=${selectionStr}`, child.getPageLinkAlias(page));
navigator.clipboard.writeText(linktext);
}
return true;
}
});
}
}
96 changes: 65 additions & 31 deletions src/patch.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import PDFPlus from "main";
import { around } from "monkey-around";
import { EditableFileView, Workspace, parseLinktext } from "obsidian";
import { ObsidianViewer, PDFView, PDFViewerChild } from "typings";
import { ObsidianViewer, PDFAnnotationHighlight, PDFTextHighlight, PDFView, PDFViewerChild } from "typings";
import { onAnnotationLayerReady, onTextLayerReady } from "utils";

export const patchPDF = (plugin: PDFPlus): boolean => {
const app = plugin.app;
Expand All @@ -12,23 +13,49 @@ export const patchPDF = (plugin: PDFPlus): boolean => {
const viewer = child.pdfViewer;
if (!viewer) return false;

(window as any).child = child;
(window as any).viewer = viewer;

plugin.register(around(child.constructor.prototype, {
onResize(old) {
return function () {
const self = this as PDFViewerChild;
const ret = old.call(this);
plugin.pdfViwerChildren.set(self.containerEl.find('.pdf-viewer'), self);
// (window as any).child = self;
return ret;
}
},
getMarkdownLink(old) {
return function (subpath?: string, alias?: string, embed?: boolean): string {
return old.call(this, subpath, plugin.settings.alias ? alias : undefined, embed);
}
},
clearTextHighlight(old) {
return function () {
const self = this as PDFViewerChild;
if (plugin.settings.persistentHighlightsInEmbed && self.pdfViewer.isEmbed) {
return;
}
old.call(this);
}
},
clearAnnotationHighlight(old) {
return function () {
const self = this as PDFViewerChild;
if (plugin.settings.persistentHighlightsInEmbed && self.pdfViewer.isEmbed) {
return;
}
old.call(this);
}
}
}));

plugin.register(around(viewer.constructor.prototype, {
setHeight(old) {
return function (height?: number | "page" | "auto") {
const self = this as ObsidianViewer;

// (window as any).viewer = self;

if (plugin.settings.trimSelectionEmbed && self.isEmbed && self.dom && typeof self.page === 'number' && typeof height !== 'number') {
(window as any).embedViewer = self;
const beginSelectionEl = self.dom.viewerEl.querySelector('.mod-focused.begin.selected')
const endSelectionEl = self.dom.viewerEl.querySelector('.mod-focused.endselected')
if (beginSelectionEl && endSelectionEl) {
Expand All @@ -38,17 +65,14 @@ export const patchPDF = (plugin: PDFPlus): boolean => {
}

if (self.isEmbed && plugin.settings.zoomInEmbed) {
const listener = async () => {
onTextLayerReady(self, async () => {
for (self._zoomedIn ??= 0; self._zoomedIn < plugin.settings.zoomInEmbed; self._zoomedIn++) {
console.log(self._zoomedIn);
self.zoomIn();
await new Promise<void>((resolve) => {
setTimeout(resolve, 50);
})
}
self.eventBus._off("textlayerrendered", listener);
};
self.eventBus._on("textlayerrendered", listener);
});
}

old.call(this, height);
Expand All @@ -65,32 +89,42 @@ export const patchWorkspace = (plugin: PDFPlus) => {
plugin.register(around(Workspace.prototype, {
openLinkText(old) {
return function (linktext: string, sourcePath: string, ...args: any[]) {
const { path, subpath } = parseLinktext(linktext);
const file = app.metadataCache.getFirstLinkpathDest(path, sourcePath);
if (plugin.settings.openLinkCleverly) {
const { path, subpath } = parseLinktext(linktext);
const file = app.metadataCache.getFirstLinkpathDest(path, sourcePath);

if (file && file.extension === 'pdf') {
const leaf = app.workspace.getLeavesOfType('pdf').find(leaf => {
return leaf.view instanceof EditableFileView && leaf.view.file === file;
});
if (leaf) {
const view = leaf.view as PDFView;
const self = this as Workspace;
console.log(view);
self.setActiveLeaf(leaf);
const child = view.viewer.child;
if (child) {
child.applySubpath(subpath);
if (child.subpathHighlight?.type === 'text') {
const { page, range } = child.subpathHighlight;
child.highlightText(page, range);
} else if (child.subpathHighlight?.type === 'annotation') {
const { page, id } = child.subpathHighlight;
child.highlightAnnotation(page, id);
if (file && file.extension === 'pdf') {
const leaf = app.workspace.getLeavesOfType('pdf').find(leaf => {
return leaf.view instanceof EditableFileView && leaf.view.file === file;
});
if (leaf) {
const view = leaf.view as PDFView;
const self = this as Workspace;
self.setActiveLeaf(leaf);
const child = view.viewer.child;
if (child) {
child.applySubpath(subpath);
if (child.subpathHighlight?.type === 'text') {
onTextLayerReady(child.pdfViewer, () => {
const { page, range } = child.subpathHighlight as PDFTextHighlight;
child.highlightText(page, range);
const duration = plugin.settings.highlightDuration;
if (duration > 0) setTimeout(() => child.clearTextHighlight(), duration * 1000);
});
} else if (child.subpathHighlight?.type === 'annotation') {
onAnnotationLayerReady(child.pdfViewer, () => {
const { page, id } = child.subpathHighlight as PDFAnnotationHighlight;
child.highlightAnnotation(page, id);
const duration = plugin.settings.highlightDuration;
if (duration > 0) setTimeout(() => child.clearAnnotationHighlight(), duration * 1000);
});
}
}
return;
}
return;
}
}

return old.call(this, linktext, sourcePath, ...args);
}
}
Expand Down
61 changes: 52 additions & 9 deletions src/settings.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { PluginSettingTab, Setting } from 'obsidian';
import PDFPlus from './main';
import PDFPlus from 'main';


export interface PDFPlusSettings {
Expand All @@ -8,14 +8,20 @@ export interface PDFPlusSettings {
padding: number;
embedUnscrollable: boolean;
zoomInEmbed: number;
openLinkCleverly: boolean;
highlightDuration: number;
persistentHighlightsInEmbed: boolean;
}

export const DEFAULT_SETTINGS: PDFPlusSettings = {
alias: true,
trimSelectionEmbed: true,
padding: 80,
embedUnscrollable: false,
zoomInEmbed: 1
zoomInEmbed: 0,
openLinkCleverly: true,
highlightDuration: 0,
persistentHighlightsInEmbed: true,
};

// Inspired by https://stackoverflow.com/a/50851710/13613783
Expand Down Expand Up @@ -114,23 +120,60 @@ export class PDFPlusSettingTab extends PluginSettingTab {
const { containerEl } = this;
containerEl.empty();

this.addDesc('Note: some of the settings below requires reopening tabs to take effect.')

this.addHeading('Opening links to PDF files');
this.addToggleSetting('openLinkCleverly')
.setName('Open PDF links cleverly')
.setDesc('When opening a link to a PDF file, a new tab will not be opened if the file is already opened. Useful for annotating PDFs using "Copy link to selection"');
new Setting(containerEl)
.setName('Clear highlights after a certain amount of time')
.addToggle((toggle) => {
toggle.setValue(this.plugin.settings.highlightDuration > 0)
.onChange((value) => {
this.plugin.settings.highlightDuration = value
? (this.plugin.settings.highlightDuration > 0
? this.plugin.settings.highlightDuration
: 1)
: 0;
this.display();
});
});
if (this.plugin.settings.highlightDuration > 0) {
this.addSliderSetting('highlightDuration', 0.1, 10, 0.1)
.setName('Highlight duration (sec)');
}

this.addHeading('Copying links to PDF files')
this.addToggleSetting('alias')
.setName('Copy link with alias');
this.addSliderSetting('padding', 0, 500, 1)
.setName('Padding for selection embeds (px)');
this.addToggleSetting('trimSelectionEmbed')

this.addHeading('Embedding PDF files');
this.addToggleSetting('trimSelectionEmbed', (value) => this.display())
.setName('Trim selection embeds');
if (this.plugin.settings.trimSelectionEmbed) {
this.addSliderSetting('padding', 0, 500, 1)
.setName('Padding for trimmed selection embeds (px)');
}
this.addToggleSetting('persistentHighlightsInEmbed')
.setName('Do not clear highlights in embeds');
this.addToggleSetting('embedUnscrollable')
.setName('Make PDF embeds unscrollable');
this.addSliderSetting('zoomInEmbed', 0, 5, 1)
.setName('Zoom level for PDF embeds');
.setName('Zoom level for PDF embeds (experimental)');

this.addDesc('You can find more options in Style Settings.')
this.addHeading('Style settings')
.setDesc('You can find more options in Style Settings > PDF++.')
.addButton((button) => {
button.setButtonText('Open')
.onClick(() => {
this.app.setting.openTabById('obsidian-style-settings');
const styleSettingsTab = this.app.setting.pluginTabs.find((tab) => tab.id === 'obsidian-style-settings');
if (styleSettingsTab) {
this.app.setting.openTab(styleSettingsTab);
} else {
open('obsidian://show-plugin?id=obsidian-style-settings');
}
});
})
});
}
}
13 changes: 12 additions & 1 deletion src/typings.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { App, Component, EditableFileView, Modal, Scope, TFile } from 'obsidian';
import { App, Component, EditableFileView, Modal, PluginSettingTab, Scope, SettingTab, TFile } from 'obsidian';

interface PDFView extends EditableFileView {
viewer: PDFViewer;
Expand Down Expand Up @@ -37,6 +37,8 @@ interface PDFViewerChild {
applySubpath(subpath?: string): any;
highlightText(page: number, range: [[number, number], [number, number]]): void;
highlightAnnotation(page: number, id: string): void;
clearTextHighlight(): void;
clearAnnotationHighlight(): void;
}

interface PDFHighlight {
Expand Down Expand Up @@ -69,11 +71,20 @@ interface ObsidianViewer {
}

interface AppSetting extends Modal {
openTab(tab: SettingTab): void;
openTabById(id: string): any;
pluginTabs: PluginSettingTab[];
}

declare module "obsidian" {
interface App {
setting: AppSetting;
plugins: {
manifests: Record<string, PluginManifest>;
}
}

interface PluginSettingTab {
id: string;
}
}
Loading

0 comments on commit 886adac

Please sign in to comment.