Skip to content

Commit

Permalink
Enable annotation deletion
Browse files Browse the repository at this point in the history
  • Loading branch information
RyotaUshio committed Feb 2, 2024
1 parent c6653bd commit 6985fe1
Show file tree
Hide file tree
Showing 14 changed files with 284 additions and 85 deletions.
5 changes: 1 addition & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -332,10 +332,7 @@ It is used by countless Obsidian plugins and has been helping the community as a
He's also the author of several popular Obsidian plugins including Tag Wrangler.

PDF++ offers two ways to highlight text in PDF: one that does not involve modifying the PDF file, and the other that writes highlight annotations directly into the PDF file.
The latter is powered by the following libraries:

- pdf-lib: A JavaScript library for creating and modifying PDF documents. The [original project](https://github.com/Hopding/pdf-lib) was created by Andrew Dillon. PDF++ uses a [forked version](https://github.com/cantoo-scribe/pdf-lib) maintained by Cantoo Scribe.
- [pdfAnnotate](https://github.com/highkite/pdfAnnotate): A JavaScript library for creating PDF annotations by Thomas Osterland.
The latter is powered by the pdf-lib, a JavaScript library for creating and modifying PDF documents. The [original project](https://github.com/Hopding/pdf-lib) was created by Andrew Dillon. PDF++ uses a [forked version](https://github.com/cantoo-scribe/pdf-lib) maintained by Cantoo Scribe.

## Compatibility

Expand Down
31 changes: 0 additions & 31 deletions package-lock.json

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

3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
"@types/node": "^16.11.6",
"@typescript-eslint/eslint-plugin": "5.29.0",
"@typescript-eslint/parser": "5.29.0",
"annotpdf": "^1.0.15",
"builtin-modules": "3.3.0",
"esbuild": "0.17.3",
"monkey-around": "^2.3.0",
Expand All @@ -24,4 +23,4 @@
"tslib": "2.4.0",
"typescript": "4.7.4"
}
}
}
87 changes: 87 additions & 0 deletions src/annotation-modals.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { PDFPlusAPI } from 'api';
import PDFPlus from 'main';
import { Component, Modal, Setting, TFile } from 'obsidian';


class PDFPlusModal extends Modal {
plugin: PDFPlus;
api: PDFPlusAPI;
component: Component;

constructor(plugin: PDFPlus) {
super(plugin.app);
this.plugin = plugin;
this.api = plugin.api;
this.component = new Component();
}

onOpen() {
this.component.load();
this.contentEl.empty();
}

onClose() {
this.contentEl.empty();
this.component.unload();
}
}

class PDFAnnotationModal extends PDFPlusModal {
file: TFile;
page: number;
id: string;

constructor(plugin: PDFPlus, file: TFile, page: number, id: string) {
super(plugin);
this.file = file;
this.page = page;
this.id = id;
}
}

export class PDFAnnotationEditModal extends PDFAnnotationModal { }

export class PDFAnnotationDeleteModal extends PDFAnnotationModal {
onOpen() {
super.onOpen();

this.contentEl.createEl('h3', { text: `${this.plugin.manifest.name}: delete annotation` });
this.contentEl.createEl('p', { text: 'Are you sure you want to delete this annotation?' });
this.contentEl.createEl('p', { cls: 'mod-warning', text: 'There are one or more links pointing to this annotation.' });

new Setting(this.contentEl)
.addButton((button) => {
button
.setButtonText('Delete')
.setWarning()
.onClick(() => {
this.deleteAnnotation();
this.close();
});
})
.addButton((button) => {
button
.setButtonText('Cancel')
.onClick(() => this.close());
})
.then((setting) => setting.setClass('no-border'));
}

openIfNeccessary() {
if (this.shouldOpen()) {
return this.open();
}
return this.deleteAnnotation();
}

shouldOpen() {
return this.plugin.settings.warnBacklinkedAnnotationDelete
&& this.api.isBacklinked(this.file, {
page: this.page, annotation: this.id
});
}

deleteAnnotation() {
this.api.highlight.writeFile.deleteAnnotation(this.file, this.page, this.id);
}
}
1 change: 1 addition & 0 deletions src/api/copy-link.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ export class copyLinkAPI extends PDFPlusAPISubmodule {
pageLabel: pageView.pageLabel ?? ('' + page),
pageCount: child.pdfViewer.pagesCount,
text,
colorName: '',
...this.getLinkTemplateVariables(child, child.file!, `#page=${page}&annotation=${id}`, page)
});

Expand Down
28 changes: 21 additions & 7 deletions src/api/highlights/write-file/index.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,28 @@
import { PDFRef } from '@cantoo/pdf-lib';
import { Notice, TFile } from 'obsidian';

import PDFPlus from 'main';
import { PdfAnnotateIO } from './pdfAnnotate';
// import { PdfAnnotateIO } from './pdfAnnotate';
import { PdfLibIO } from './pdf-lib';
import { PDFPlusAPISubmodule } from 'api/submodule';
import { parsePDFSubpath } from 'utils';
import { formatAnnotationID, parsePDFSubpath } from 'utils';
import { PDFViewerChild, Rect } from 'typings';


export class AnnotationWriteFileAPI extends PDFPlusAPISubmodule {
pdflib: PdfLibIO;
pdfAnnotate: PdfAnnotateIO;
// pdfAnnotate: PdfAnnotateIO;

constructor(plugin: PDFPlus) {
super(plugin);
this.pdflib = new PdfLibIO(plugin);
this.pdfAnnotate = new PdfAnnotateIO(plugin);
// this.pdfAnnotate = new PdfAnnotateIO(plugin);
}

private getPdfIo(): IPdfIo {
// if (this.plugin.settings.writeFileLibrary === 'pdfAnnotate') return this.pdfAnnotate;
// else
return this.pdflib;
}

async highlightSelection(colorName?: string) {
Expand Down Expand Up @@ -58,9 +65,16 @@ export class AnnotationWriteFileAPI extends PDFPlusAPISubmodule {
}
}

getPdfIo(): IPdfIo {
if (this.plugin.settings.writeFileLibrary === 'pdfAnnotate') return this.pdfAnnotate;
else return this.pdflib;
deleteAnnotation(file: TFile, pageNumber: number, id: string) {
const io = this.pdflib;
io.process(file, (pdfDoc) => {
const page = pdfDoc.getPage(pageNumber - 1);
const ref = page.node.Annots()?.asArray().find((ref): ref is PDFRef => {
return ref instanceof PDFRef
&& formatAnnotationID(ref.objectNumber, ref.generationNumber) === id;
});
if (ref) page.node.removeAnnot(ref);
});
}
}

Expand Down
5 changes: 2 additions & 3 deletions src/api/highlights/write-file/pdf-lib.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { TFile } from 'obsidian';
import { PDFDocument, PDFPage, PDFRef, PDFString } from '@cantoo/pdf-lib';
import { Util } from 'annotpdf';

import { PDFPlusAPISubmodule } from 'api/submodule';
import { formatAnnotationID, getBorderRadius } from 'utils';
import { convertDateToPDFDate, formatAnnotationID, getBorderRadius } from 'utils';
import { Rect } from 'typings';
import { IPdfIo } from '.';

Expand Down Expand Up @@ -31,7 +30,7 @@ export class PdfLibIO extends PDFPlusAPISubmodule implements IPdfIo {
// For Contents & T, make sure to pass a PDFString, not a raw string!!
// https://github.com/Hopding/pdf-lib/issues/555#issuecomment-670243166
Contents: PDFString.of(contents ?? ''),
M: PDFString.of(Util.convertDateToPDFDate(new Date())),
M: PDFString.of(convertDateToPDFDate(new Date())),
T: PDFString.of(this.plugin.settings.author),
CA: this.plugin.settings.writeHighlightToFileOpacity,
Border: [borderRadius, borderRadius, 0],
Expand Down
60 changes: 54 additions & 6 deletions src/api/index.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { App, Component, TFile } from 'obsidian';
import { App, Component, TFile, parseLinktext } from 'obsidian';
import { PDFDocumentProxy } from 'pdfjs-dist';

import PDFPlus from 'main';
import { ColorPalette } from 'color-palette';
import { copyLinkAPI } from './copy-link';
import { HighlightAPI } from './highlights';
import { WorkspaceAPI } from './workspace-api';
import { encodeLinktext } from 'utils';
import { encodeLinktext, parsePDFSubpath } from 'utils';
import { AnnotationElement, EventBus, ObsidianViewer, PDFPageView, PDFViewerChild } from 'typings';


Expand Down Expand Up @@ -132,10 +132,10 @@ export class PDFPlusAPI {
}

getPDFViewerChildAssociatedWithNode(node: Node) {
for (const [viewerEl, child] of this.plugin.pdfViwerChildren) {
if (viewerEl.contains(node)) return child;
}
}
for (const [viewerEl, child] of this.plugin.pdfViwerChildren) {
if (viewerEl.contains(node)) return child;
}
}

async destIdToSubpath(destId: string, doc: PDFDocumentProxy) {
const dest = await doc.getDestination(destId);
Expand Down Expand Up @@ -232,4 +232,52 @@ export class PDFPlusAPI {

return 'md' !== file.extension ? '!' + nonEmbedLink : nonEmbedLink;
}

isBacklinked(file: TFile, subpathParams?: { page: number, selection?: [number, number, number, number], annotation?: string }): boolean {
// validate parameters
if (subpathParams) {
const { page, selection, annotation } = subpathParams;
if (isNaN(page) || page < 1) throw new Error('Invalid page number');
if (selection && (selection.length !== 4 || selection.some((pos) => isNaN(pos)))) throw new Error('Invalid selection');
if (selection && typeof annotation === 'string') throw new Error('Selection and annotation cannot be used together');
}

// query type
const isFileQuery = !subpathParams;
const isPageQuery = subpathParams && !subpathParams.selection && !subpathParams.annotation;
const isSelectionQuery = subpathParams && !!(subpathParams.selection);
const isAnnotationQuery = typeof subpathParams?.annotation === 'string';

const backlinkDict = this.app.metadataCache.getBacklinksForFile(file);

if (isFileQuery) return backlinkDict.count() > 0;

for (const sourcePath of backlinkDict.keys()) {
const backlinks = backlinkDict.get(sourcePath);
if (!backlinks) continue;

for (const backlink of backlinks) {
const { subpath } = parseLinktext(backlink.link);
const result = parsePDFSubpath(subpath);
if (!result) continue;

if (isPageQuery && result.page === subpathParams.page) return true;
if (isSelectionQuery
&& 'beginIndex' in result
&& result.page === subpathParams.page
&& result.beginIndex === subpathParams.selection![0]
&& result.beginOffset === subpathParams.selection![1]
&& result.endIndex === subpathParams.selection![2]
&& result.endOffset === subpathParams.selection![3]
) return true;
if (isAnnotationQuery
&& 'annotation' in result
&& result.page === subpathParams.page
&& result.annotation === subpathParams.annotation
) return true;
}
}

return false;
}
}
4 changes: 2 additions & 2 deletions src/main.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { EditableFileView, EventRef, Events, Keymap, Notice, PaneType, Platform, Plugin, TFile, loadPdfJs, requireApiVersion, setIcon } from 'obsidian';
import * as pdflib from '@cantoo/pdf-lib';
import * as pdfAnnotate from 'annotpdf';
// import * as pdfAnnotate from 'annotpdf';

import { patchPDF } from 'patchers/pdf';
import { patchBacklink } from 'patchers/backlink';
Expand Down Expand Up @@ -230,7 +230,7 @@ export default class PDFPlus extends Plugin {
private registerGlobalVariables() {
this.registerGlobalVariable('pdfPlus', this, false);
this.registerGlobalVariable('pdflib', pdflib, false);
this.registerGlobalVariable('pdfAnnotate', pdfAnnotate, false);
// this.registerGlobalVariable('pdfAnnotate', pdfAnnotate, false);
}

registerGlobalDomEvent<K extends keyof DocumentEventMap>(type: K, callback: (this: HTMLElement, ev: DocumentEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void {
Expand Down
Loading

0 comments on commit 6985fe1

Please sign in to comment.