Skip to content

Commit

Permalink
release: 0.8.0
Browse files Browse the repository at this point in the history
  • Loading branch information
RyotaUshio committed Dec 22, 2023
1 parent c8cd45a commit ab152da
Show file tree
Hide file tree
Showing 11 changed files with 360 additions and 72 deletions.
9 changes: 7 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@ Each feature can be toggled on and off in the plugin settings.
### Backlinks to PDF files

- **Highlight backlinks**: In the PDF viewer, any referenced text will be highlighted for easy identification. Additionally, when you hover over the highlighted text, a popover will appear, displaying the corresponding backlink.
- **Backlink highlights background color**: Requires the [Style Settings](https://github.com/mgmeyers/obsidian-style-settings) plugin.
- By default, all backlinks are highlighted. But there is an option that allows you to highlight only backlinks with colors specified in the link text (see below).
- **Highlight hovered backlinks in the backlinks pane**: Hovering over highlighted backlinked text will also highlight the corresponding item in the [backlink pane]((https://help.obsidian.md/Plugins/Backlinks)). This feature is compatible with the [Better Search Views]((https://github.com/ivan-lednev/better-search-views)) plugin.
- **Custom highlight colors**: Append `&color={{COLOR NAME}}` to a link text to highlight the selection with a specified color, where `{{COLOR NAME}}` is one of the colors that you register in the plugin settings. e.g `[[file.pdf#page=1&selection=4,0,5,20&color=red]]`
- **Show color palette in the toolbar**: A color palette will be added to the toolbar of the PDF viewer. Clicking a color while selecting a range of text will copy a link to the selection with `&color=...` appended.

These features enrich Obsidian as a stand-alone PDF annotation tool. I recommend combining the core [Backlinks](https://help.obsidian.md/Plugins/Backlinks) plugin & the [Better Search View](https://github.com/ivan-lednev/better-search-views) plugin together with this plugin.

Expand Down Expand Up @@ -52,7 +54,10 @@ But you can install the latest release using [BRAT](https://github.com/TfTHacker
## Development principles

- Always stick around Obsidian's built-in PDF viewer.
- Never introduce plugin-dependent stuff.
- Don't introduce plugin-dependent stuff as much as possible.
- It can be tolerated only if
- it brings a massive benifit
- and it won't leave anything that becomes just a random mess without this plugin.

## Support development

Expand Down
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.7.1",
"version": "0.8.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.7.1",
"version": "0.8.0",
"description": "An Obsidian.md plugin to enhance PDF viewer & embeds.",
"scripts": {
"dev": "node esbuild.config.mjs",
Expand Down
27 changes: 19 additions & 8 deletions src/backlinks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,20 +59,26 @@ export class BacklinkManager extends Component implements HoverParent {
if (params.has('page') && params.has('selection')) {
const page = parseInt(params.get('page')!);
const selection = params.get('selection')!.split(',').map((s) => parseInt(s));
const color = params.get('color') ?? undefined;

if (!color && this.plugin.settings.highlightColorSpecifiedOnly) continue;

if (selection.length === 4) {
let backlinkItemEl: HTMLElement | null = null;
// @ts-ignore
this.viewer.pdfViewer._pagesCapability.promise.then(() => {
this.highlightText(
page,
...selection as [number, number, number, number],
(textDiv) => {
this.eventManager.registerDomEvent(textDiv, 'mouseover', (event) => {
color,
(highlightedEl) => {
this.eventManager.registerDomEvent(highlightedEl, 'mouseover', (event) => {

this.app.workspace.trigger('hover-link', {
event,
source: 'pdf-plus',
hoverParent: this,
targetEl: textDiv,
targetEl: highlightedEl,
linktext: sourcePath,
state: { scroll: link.position.start.line }
});
Expand Down Expand Up @@ -103,11 +109,11 @@ export class BacklinkManager extends Component implements HoverParent {
backlinkItemEl.addClass('hovered-backlink');
});

this.eventManager.registerDomEvent(textDiv, 'mouseout', (event) => {
this.eventManager.registerDomEvent(highlightedEl, 'mouseout', (event) => {
backlinkItemEl?.removeClass('hovered-backlink');
});

this.eventManager.registerDomEvent(textDiv, 'click', (event) => {
this.eventManager.registerDomEvent(highlightedEl, 'click', (event) => {
const paneType = Keymap.isModEvent(event);
if (paneType) {
this.app.workspace.openLinkText(sourcePath, "", paneType, {
Expand All @@ -128,7 +134,7 @@ export class BacklinkManager extends Component implements HoverParent {
}

// This is a modified version of PDFViewerChild.prototype.hightlightText from Obsidian's app.js
highlightText(pageNumber: number, beginIndex: number, beginOffset: number, endIndex: number, endOffset: number, onHighlight?: (textDiv: HTMLElement) => void) {
highlightText(pageNumber: number, beginIndex: number, beginOffset: number, endIndex: number, endOffset: number, colorName?: string, onHighlight?: (highlightedEl: HTMLElement) => void) {
if (!(pageNumber < 1 || pageNumber > this.viewer.pagesCount)) {
const pageView = this.viewer.pdfViewer.getPageView(pageNumber - 1);
if (pageView != null && pageView.div.dataset.loaded) {
Expand All @@ -152,8 +158,10 @@ export class BacklinkManager extends Component implements HoverParent {
const text = textContentItems[index].str.substring(from, to);
const textNode = document.createTextNode(text);
if (className) {
textDiv.createSpan(className + " appended").append(textNode);
onHighlight?.(textDiv);
const highlightWrapperEl = textDiv.createSpan(className + " appended");
if (colorName) highlightWrapperEl.dataset.highlightColor = colorName;
highlightWrapperEl.append(textNode);
onHighlight?.(highlightWrapperEl);
}
else textDiv.append(textNode);
}
Expand All @@ -167,6 +175,8 @@ export class BacklinkManager extends Component implements HoverParent {
for (let i = beginIndex + 1; i < endIndex; i++) {
this.highlightedTexts.push({ page: pageNumber, index: i });
textDivs[i].classList.add("mod-focused", "middle", "selected", cls);
if (colorName) textDivs[i].dataset.highlightColor = colorName;
onHighlight?.(textDivs[i]);
}
s(endIndex, endOffset, "mod-focused endselected " + cls);
}
Expand All @@ -175,6 +185,7 @@ export class BacklinkManager extends Component implements HoverParent {
}
}

// This is a modified version of PDFViewerChild.prototype.clearTextHighlight from Obsidian's app.js
clearTextHighlight() {
for (const { page, index } of this.highlightedTexts) {
const pageView = this.viewer.pdfViewer.getPageView(page - 1);
Expand Down
112 changes: 82 additions & 30 deletions src/main.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,25 @@
import { Notice, Plugin } from 'obsidian';
import { Component, Notice, Plugin } from 'obsidian';
import { DEFAULT_SETTINGS, PDFPlusSettings, PDFPlusSettingTab } from 'settings';
import { patchPDF, patchWorkspace } from 'patch';
import { PDFViewerChild } from 'typings';
import { iteratePDFViews } from 'utils';
import { PDFView, PDFViewerChild } from 'typings';
import { addColorPalette, copyLinkToSelection, isHexString, iteratePDFViews } from 'utils';
import { BacklinkManager } from 'backlinks';


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

async onload() {
await this.loadSettings();
await this.saveSettings();
this.addSettingTab(new PDFPlusSettingTab(this));

this.elementManager = this.addChild(new Component());

this.app.workspace.onLayoutReady(() => this.loadStyle());

patchWorkspace(this);

this.app.workspace.onLayoutReady(() => {
Expand Down Expand Up @@ -62,15 +67,17 @@ export default class PDFPlus extends Plugin {
this.app.workspace.onLayoutReady(() => {
iteratePDFViews(this.app, (view) => {
view.viewer.then((child) => {
if (!view.viewer.backlinkManager) {
view.viewer.backlinkManager = view.viewer.addChild(new BacklinkManager(this, child.pdfViewer));
}
if (!view.viewer.backlinkManager) {
view.viewer.backlinkManager = view.viewer.addChild(new BacklinkManager(this, child.pdfViewer));
}
if (!child.backlinkManager) {
child.backlinkManager = view.viewer.backlinkManager
}
view.viewer.backlinkManager.file = view.file;
view.viewer.backlinkManager.highlightBacklinks();
});
view.viewer.backlinkManager.file = view.file;
view.viewer.backlinkManager.highlightBacklinks();

if (child.toolbar) addColorPalette(this, child.toolbar.toolbarLeftEl);
});
});
});

Expand All @@ -92,31 +99,76 @@ export default class PDFPlus extends Plugin {
await this.saveData(this.settings);
}

registerEl<HTMLElementType extends HTMLElement>(el: HTMLElementType, component?: Component) {
component = component ?? this.elementManager;
component.register(() => el.remove());
return el;
}

loadStyle() {
this.elementManager.unload();
// reload only if parent is loaded
this.removeChild(this.elementManager);
this.addChild(this.elementManager);

for (const child of this.pdfViwerChildren.values()) {
if (child.toolbar) addColorPalette(this, child.toolbar.toolbarLeftEl);
}

const styleEl = this.registerEl(createEl('style', { attr: { id: 'pdf-plus-style' } }));
document.head.append(styleEl);

styleEl.textContent = Object.entries(this.settings.colors).map(([name, color]) => {
return isHexString(color) ? (
`.textLayer .mod-focused.pdf-plus-backlink[data-highlight-color="${name}"] {
background-color: ${color};
}`
) : '';
}).join('\n');
const defaultColor = this.settings.colors[this.settings.defaultColor];
if (defaultColor) {
styleEl.textContent += `
.textLayer .mod-focused.pdf-plus-backlink {
background-color: ${defaultColor};
}
`
}
this.app.workspace.trigger('css-change');
}

registerCommands() {
this.addCommand({
id: 'copy-link-to-selection',
name: 'Copy link to 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;
}
checkCallback: (checking: boolean) => copyLinkToSelection(this, checking)
});
}

// console utilities

getPDFView(): PDFView | undefined {
const leaf = this.app.workspace.activeLeaf;
if (leaf?.view.getViewType() === 'pdf') return leaf.view as PDFView;
return this.app.workspace.getLeavesOfType('pdf')[0]?.view as PDFView | undefined;
}

getPDFViewer() {
return this.getPDFView()?.viewer;
}

getPDFViewerChild() {
return this.getPDFViewer()?.child;
}

getObsidianViewer() {
return this.getPDFViewerChild()?.pdfViewer;
}

getRawPDFViewer() {
return this.getObsidianViewer()?.pdfViewer;
}

getToolbar() {
return this.getPDFViewerChild()?.toolbar;
}
}
18 changes: 15 additions & 3 deletions src/patch.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { BacklinkManager } from "backlinks";
import PDFPlus from "main";
import { around } from "monkey-around";
import { EditableFileView, OpenViewState, PaneType, TFile, Workspace, parseLinktext } from "obsidian";
import { ObsidianViewer, PDFView, PDFViewer, PDFViewerChild } from "typings";
import { highlightSubpath, onTextLayerReady } from "utils";
import { ColorComponent, EditableFileView, OpenViewState, PaneType, TFile, Workspace, parseLinktext } from "obsidian";
import { ObsidianViewer, PDFToolbar, PDFView, PDFViewer, PDFViewerChild } from "typings";
import { addColorPalette, highlightSubpath, isHexString, onTextLayerReady } from "utils";

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

plugin.register(around(pdfView.viewer.constructor.prototype, {
onload(old) {
Expand Down Expand Up @@ -119,6 +121,16 @@ export const patchPDF = (plugin: PDFPlus): boolean => {
}
}));

plugin.register(around(toolbar.constructor.prototype, {
reset(old) {
return function () {
const self = this as PDFToolbar;
addColorPalette(plugin, self.toolbarLeftEl);
old.call(this);
}
}
}));

return true;
}

Expand Down

0 comments on commit ab152da

Please sign in to comment.