Skip to content

Commit

Permalink
release: 0.8.2
Browse files Browse the repository at this point in the history
  • Loading branch information
RyotaUshio committed Dec 23, 2023
1 parent fea515f commit 9ab968c
Show file tree
Hide file tree
Showing 8 changed files with 114 additions and 41 deletions.
13 changes: 12 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,13 @@ 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.
Transform a link to a PDF file into a highlighted annotation.

- **Highlight backlinks**: In the PDF viewer, any referenced text will be highlighted for easy identification.
- 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).
- **Easily navigate to backlinks by hovering over a highlighted text in PDF viewer**: you can choose what happens when you hover over a highlighted text between the followings:
- Open backlink
- Popover preview of backlink
- **Double click a piece of highlighted text to open the corresponding backlink**
- **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]]`
Expand Down Expand Up @@ -52,6 +57,12 @@ But you can install the latest release using [BRAT](https://github.com/TfTHacker
3. Open the following URL in browser: `obsidian://brat?plugin=RyotaUshio/obsidian-pdf-plus`.
4. Click the "Add Plugin" button.

## Remarks

The following plugin(s) alters Obsidian's internals in such a way that prevent some aspects of other plugins from working properly, so I don't recommend using it together with this plugin.

- [Close similar tabs](https://github.com/1C0D/Obsidian-Close-Similar-Tabs)

## Development principles

- Always stick around Obsidian's built-in PDF viewer.
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.8.1",
"version": "0.8.2",
"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.8.1",
"version": "0.8.2",
"description": "An Obsidian.md plugin to enhance PDF viewer & embeds.",
"scripts": {
"dev": "node esbuild.config.mjs",
Expand Down
31 changes: 19 additions & 12 deletions src/patch.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { BacklinkManager } from "backlinks";
import PDFPlus from "main";
import { around } from "monkey-around";
import { ColorComponent, EditableFileView, FileView, HoverParent, MarkdownView, OpenViewState, PaneType, TFile, Workspace, parseLinktext } from "obsidian";
import { ColorComponent, EditableFileView, FileView, HoverParent, MarkdownView, OpenViewState, PaneType, TFile, Workspace, WorkspaceLeaf, WorkspaceParent, WorkspaceSplit, parseLinktext } from "obsidian";
import { ObsidianViewer, PDFToolbar, PDFView, PDFViewer, PDFViewerChild } from "typings";
import { addColorPalette, highlightSubpath, isHexString, onTextLayerReady } from "utils";

Expand Down Expand Up @@ -176,21 +176,28 @@ export const patchPagePreview = (plugin: PDFPlus) => {
plugin.register(around(pagePreview.constructor.prototype, {
onLinkHover(old) {
return function (hoverParent: HoverParent, targetEl: HTMLElement | null, linktext: string, sourcePath: string, state: any): void {
if (plugin.settings.openOnHoverHighlight && hoverParent instanceof BacklinkManager) {
if (plugin.settings.hoverHighlightAction === 'open' && hoverParent instanceof BacklinkManager) {
// 1. If the target markdown file is already opened, open the link in the same leaf
// 2. If not, create a new leaf under the same parent split as the first existing markdown leaf
const file = app.metadataCache.getFirstLinkpathDest(linktext, sourcePath);
let leafFound = false;
app.workspace.iterateAllLeaves((leaf) => {
if (leafFound) return;

if (leaf.view instanceof MarkdownView && leaf.view.file === file) {
leaf.openLinkText(linktext, sourcePath, { eState: { line: state.scroll } });
leafFound = true;
let markdownLeaf: WorkspaceLeaf | null = null;
let markdownLeafParent: WorkspaceSplit | null = null;
app.workspace.iterateRootLeaves((leaf) => {
if (markdownLeaf) return;

if (leaf.view instanceof MarkdownView) {
markdownLeafParent = leaf.parentSplit;
if (leaf.view.file === file) {
markdownLeaf = leaf;
}
}
});
if (!leafFound) {
// seems like the third parameter is just ignored by Obsidian??
app.workspace.openLinkText(linktext, sourcePath, true, { eState: { line: state.scroll } });
if (!markdownLeaf) {
markdownLeaf = markdownLeafParent
? app.workspace.createLeafInParent(markdownLeafParent, -1)
: app.workspace.getLeaf('tab');
}
markdownLeaf.openLinkText(linktext, sourcePath, { eState: { line: state.scroll } });
return;
}
old.call(this, hoverParent, targetEl, linktext, sourcePath, state);
Expand Down
34 changes: 26 additions & 8 deletions src/settings.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { DropdownComponent, HexString, Notice, PluginSettingTab, Setting } from 'obsidian';
import PDFPlus from 'main';

const HOVER_HIGHLIGHT_ACTIONS = {
'open': 'Open backlink',
'preview': 'Popover preview of backlink',
} as const;

export interface PDFPlusSettings {
alias: boolean;
Expand All @@ -20,7 +24,7 @@ export interface PDFPlusSettings {
colorPaletteInToolbar: boolean;
highlightColorSpecifiedOnly: boolean;
doubleClickHighlightToOpenBacklink: boolean;
openOnHoverHighlight: boolean;
hoverHighlightAction: keyof typeof HOVER_HIGHLIGHT_ACTIONS;
}

export const DEFAULT_SETTINGS: PDFPlusSettings = {
Expand All @@ -45,7 +49,7 @@ export const DEFAULT_SETTINGS: PDFPlusSettings = {
colorPaletteInToolbar: true,
highlightColorSpecifiedOnly: false,
doubleClickHighlightToOpenBacklink: true,
openOnHoverHighlight: true,
hoverHighlightAction: 'open',
};

// Inspired by https://stackoverflow.com/a/50851710/13613783
Expand Down Expand Up @@ -104,12 +108,26 @@ export class PDFPlusSettingTab extends PluginSettingTab {
});
}

addDropdowenSetting(settingName: KeysOfType<PDFPlusSettings, string>, options: readonly string[], display?: (option: string) => string, extraOnChange?: (value: string) => void) {
addDropdowenSetting(settingName: KeysOfType<PDFPlusSettings, string>, options: readonly string[], display?: (option: string) => string, extraOnChange?: (value: string) => void): Setting;
addDropdowenSetting(settingName: KeysOfType<PDFPlusSettings, string>, options: Record<string, string>, extraOnChange?: (value: string) => void): Setting;
addDropdowenSetting(settingName: KeysOfType<PDFPlusSettings, string>, ...args: any[]) {
let options: string[] = [];
let display = (optionValue: string) => optionValue;
let extraOnChange = (value: string) => { };
if (Array.isArray(args[0])) {
options = args[0];
if (typeof args[1] === 'function') display = args[1];
if (typeof args[2] === 'function') extraOnChange = args[2];
} else {
options = Object.keys(args[0]);
display = (optionValue: string) => args[0][optionValue];
if (typeof args[1] === 'function') extraOnChange = args[1];
}
return this.addSetting()
.addDropdown((dropdown) => {
const displayNames = new Set<string>();
for (const option of options) {
const displayName = display?.(option) ?? option;
const displayName = display(option) ?? option;
if (!displayNames.has(displayName)) {
dropdown.addOption(option, displayName);
displayNames.add(displayName);
Expand Down Expand Up @@ -230,10 +248,10 @@ export class PDFPlusSettingTab extends PluginSettingTab {
.setDesc('Transform a link to a PDF file into a highlighted annotation.');
this.addToggleSetting('highlightBacklinks')
.setName('Highlight backlinks')
.setDesc('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. (Being a new feature, this may not work well in some cases. Please reopen the tab if you encounter any problem.)');
this.addToggleSetting('openOnHoverHighlight')
.setName('Actually open backlink rather than popover preview when hovering over a highlight')
.setDesc('When hovering over a highlight, actually open the corresponding backlink instead of displaying a popover preview')
.setDesc('In the PDF viewer, any referenced text will be highlighted for easy identification.');
this.addDropdowenSetting('hoverHighlightAction', HOVER_HIGHLIGHT_ACTIONS)
.setName('Action when hovering over highlighted text')
.setDesc('Easily open backlinks or display a popover preview of it by hovering over a highlighted text in PDF viewer.');
this.addToggleSetting('doubleClickHighlightToOpenBacklink')
.setName('Double click a piece of highlighted text to open the corresponding backlink');
this.addToggleSetting('highlightBacklinksPane')
Expand Down
1 change: 1 addition & 0 deletions src/typings.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,7 @@ declare module "obsidian" {

interface WorkspaceLeaf {
group: string | null;
readonly parentSplit: WorkspaceSplit;
openLinkText(linktext: string, sourcePath: string, openViewState?: OpenViewState): Promise<void>;
}
}
68 changes: 52 additions & 16 deletions src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { PDFAnnotationHighlight, PDFTextHighlight, PDFView } from 'typings';
import { App, ColorComponent, setTooltip } from 'obsidian';
import { App, ColorComponent, Keymap, Modifier, Platform, setTooltip } from 'obsidian';
import { ObsidianViewer, PDFViewerChild } from 'typings';
import PDFPlus from 'main';
export function getTextLayerNode(pageEl: HTMLElement, node: Node): HTMLElement | null {
Expand Down Expand Up @@ -79,41 +79,77 @@ export function isHexString(color: string) {
export function addColorPalette(plugin: PDFPlus, toolbarLeftEl: HTMLElement) {
const cls = 'pdf-plus-color-palette';
const palette = plugin.registerEl(toolbarLeftEl.createEl('div', { cls }));

for (const [name, color] of Object.entries(plugin.settings.colors)) {
if (!isHexString(color)) continue;
const containerEl = palette.createDiv({ cls: [cls + '-item'] });
const pickerEl = containerEl.createEl("input", { type: "color" });
pickerEl.value = color;
setTooltip(pickerEl, `Copy link to selection with ${name.toLowerCase()} highlight`);
setTooltip(pickerEl, `Copy link to selection with ${name.toLowerCase()} highlight (${getModifierNameInPlatform('Mod') + '+Click to copy as quote'})`);
plugin.elementManager.registerDomEvent(containerEl, 'click', (evt) => {
copyLinkToSelection(plugin, false, { color: name });
if (Keymap.isModifier(evt, 'Mod')) {
copyAsQuote(plugin, false, { color: name });
} else {
copyLinkToSelection(plugin, false, { color: name });
}
evt.preventDefault();
});
}
}

export const copyLinkToSelection = (plugin: PDFPlus, checking: boolean = false, params?: Record<string, string>): boolean => {
export const getLinkToSelection = (plugin: PDFPlus, params?: Record<string, string>): string | null => {
const selection = window.getSelection();
if (!selection) return false;
if (!selection) return null;
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;
if (!pageEl || !(pageEl.instanceOf(HTMLElement)) || pageEl.dataset.pageNumber === undefined) return null;

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

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

const page = pageEl.dataset.pageNumber;
params = {
page,
selection: child.getTextSelectionRangeStr(pageEl),
...params ?? {}
}
const linktext = child.getMarkdownLink('#' + Object.entries(params).map(([k, v]) => k && v ? `${k}=${v}` : '').join('&'), child.getPageLinkAlias(+page));
return linktext;
}


export const copyLinkToSelection = (plugin: PDFPlus, checking: boolean = false, params?: Record<string, string>): boolean => {
const linktext = getLinkToSelection(plugin, params);
if (linktext === null) return false;
if (!checking) navigator.clipboard.writeText(linktext);
return true;
}

export const copyAsQuote = (plugin: PDFPlus, checking: boolean = false, params?: Record<string, string>): boolean => {
const linktext = getLinkToSelection(plugin, params);
const selection = window.getSelection()?.toString().replace(/[\r\n]+/g, " ");
if (!linktext || !selection) return false;
if (!checking) {
const page = pageEl.dataset.pageNumber;
params = {
page,
selection: child.getTextSelectionRangeStr(pageEl),
...params ?? {}
}
const linktext = child.getMarkdownLink('#' + Object.entries(params).map(([k, v]) => k && v ? `${k}=${v}` : '').join('&'), child.getPageLinkAlias(+page));
navigator.clipboard.writeText(linktext);
navigator.clipboard.writeText("> ".concat(selection, "\n\n").concat(linktext));
}
return true;
}

export function getModifierNameInPlatform(mod: Modifier): string {
if (mod === "Mod") {
return Platform.isMacOS || Platform.isIosApp ? "Command" : "Ctrl";
}
if (mod === "Shift") {
return "Shift";
}
if (mod === "Alt") {
return Platform.isMacOS || Platform.isIosApp ? "Option" : "Alt";
}
if (mod === "Meta") {
return Platform.isMacOS || Platform.isIosApp ? "Command" : Platform.isWin ? "Win" : "Meta";
}
return "Ctrl";
}

0 comments on commit 9ab968c

Please sign in to comment.