Skip to content

Commit

Permalink
Merge pull request #37 from waterproofsodium/open-in-new-tab-feature
Browse files Browse the repository at this point in the history
Open in new tab feature
  • Loading branch information
NomarCub authored May 9, 2024
2 parents b3f9b25 + 39dacfa commit 5cf5211
Show file tree
Hide file tree
Showing 8 changed files with 157 additions and 103 deletions.
12 changes: 9 additions & 3 deletions .eslintrc → .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,22 @@
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended"
"plugin:@typescript-eslint/recommended-type-checked"
],
"parserOptions": {
"sourceType": "module"
"sourceType": "module",
"project": true
},
"rules": {
"no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": ["error", { "args": "none" }],
"@typescript-eslint/ban-ts-comment": "off",
"no-prototype-builtins": "off",
"@typescript-eslint/no-empty-function": "off"
"@typescript-eslint/no-unsafe-assignment": "off",
"@typescript-eslint/no-unsafe-member-access": "off",
"@typescript-eslint/no-unsafe-argument": "off",
"@typescript-eslint/no-empty-function": "off",
"@typescript-eslint/no-floating-promises": "off",
"@typescript-eslint/no-misused-promises": "off"
}
}
11 changes: 7 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
# Image Context Menus

This plugin provides the following context menus for images in [Obsidian](https://obsidian.md/):
- Copy Image
- Copy image to clipboard
- Open image in default app
- Show in system explorer
- Reveal file in navigation
- Open in new tab
- also available through middle mouse button click

It also has an `Open PDF externally` context menu for PDFs.

Expand Down Expand Up @@ -40,8 +42,9 @@ Contributions are welcome.
Original plugin by [NomarCub](https://github.com/NomarCub).
If you like this plugin you can sponsor me here on GitHub: [![Sponsor NomarCub](https://img.shields.io/static/v1?label=Sponsor%20NomarCub&message=%E2%9D%A4&logo=GitHub&color=%23fe8e86)](https://github.com/sponsors/NomarCub), on Ko-fi here: <a href='https://ko-fi.com/nomarcub' target='_blank'><img height='35' src='https://az743702.vo.msecnd.net/cdn/kofi3.png?v=0' alt='Buy Me a Coffee at ko-fi.com' /></a>, or on PayPal here: [![Paypal](https://img.shields.io/badge/paypal-nomarcub-yellow?style=social&logo=paypal)](https://paypal.me/nomarcub).

[Copying](https://github.com/NomarCub/obsidian-copy-url-in-preview/pull/2) [images](https://github.com/NomarCub/obsidian-copy-url-in-preview/pull/3) developed by [luckman212](https://github.com/luckman212).
[Android image sharing](https://github.com/NomarCub/obsidian-copy-url-in-preview/issues/5) developed by [mnaoumov](https://github.com/mnaoumov).
[Open PDF externally](https://github.com/NomarCub/obsidian-copy-url-in-preview/issues/9) feature developed by [mnaoumov](https://github.com/mnaoumov).
- [Open in new tab](https://github.com/NomarCub/obsidian-copy-url-in-preview/pull/37) developed by [waterproofsodium](https://github.com/waterproofsodium)
- [Copying](https://github.com/NomarCub/obsidian-copy-url-in-preview/pull/2) [images](https://github.com/NomarCub/obsidian-copy-url-in-preview/pull/3) developed by [luckman212](https://github.com/luckman212).
- [Android image sharing](https://github.com/NomarCub/obsidian-copy-url-in-preview/issues/5) developed by [mnaoumov](https://github.com/mnaoumov).
- [Open PDF externally](https://github.com/NomarCub/obsidian-copy-url-in-preview/issues/9) feature developed by [mnaoumov](https://github.com/mnaoumov).

Thank you to the makers of the [Tag Wrangler plugin](https://github.com/pjeby/tag-wrangler), as it was a great starting point for working with context menus in Obsidian.
2 changes: 1 addition & 1 deletion manifest.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"id": "copy-url-in-preview",
"name": "Image Context Menus",
"version": "1.6.0",
"version": "1.7.0",
"minAppVersion": "1.5.7",
"description": "Copy, open in default app, show in system explorer, reveal in navigation context menu for images. Also Open PDF externally context menu.",
"author": "NomarCub",
Expand Down
12 changes: 6 additions & 6 deletions package-lock.json

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

5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
{
"name": "copy-url-in-preview",
"version": "1.6.0",
"version": "1.7.0",
"description": "Copy Image, Copy URL and Open PDF externally context menu in reading view (formerly preview mode) for Obsidian (https://obsidian.md)",
"main": "main.js",
"scripts": {
"dev": "node esbuild.config.mjs",
"lint": "eslint src",
"build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production",
"version": "node version-bump.mjs && git add manifest.json versions.json"
},
Expand All @@ -19,7 +20,7 @@
"esbuild": "0.17.3",
"eslint": "8.46.0",
"obsidian": "~1.5.7-1",
"obsidian-typings": "^1.0.6",
"obsidian-typings": "^1.1.2",
"tslib": "2.4.0",
"typescript": "4.7.4"
}
Expand Down
108 changes: 74 additions & 34 deletions src/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { FileSystemAdapter } from "obsidian";
import { App, FileSystemAdapter, View } from "obsidian";

const loadImageBlobTimeout = 5_000;

Expand All @@ -15,60 +15,100 @@ export interface Listener {
}

export function withTimeout<T>(ms: number, promise: Promise<T>): Promise<T> {
const timeout = new Promise((resolve, reject) => {
const timeout = new Promise((_resolve, reject) => {
const id = setTimeout(() => {
clearTimeout(id);
reject(`timed out after ${ms} ms`)
}, ms)
})
}) as unknown as Promise<T>;
return Promise.race([
promise,
timeout
]) as Promise<T>
]);
}

// https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_enabled_image
// option?: https://www.npmjs.com/package/html-to-image
export async function loadImageBlob(imgSrc: string): Promise<Blob> {
const loadImageBlobCore = () => {
return new Promise<Blob>((resolve, reject) => {
const image = new Image();
image.crossOrigin = "anonymous";
image.onload = () => {
const canvas = document.createElement("canvas");
canvas.width = image.width;
canvas.height = image.height;
const ctx = canvas.getContext("2d")!;
ctx.drawImage(image, 0, 0);
canvas.toBlob((blob: Blob) => {
resolve(blob);
});
};
image.onerror = async () => {
try {
await fetch(image.src, { "mode": "no-cors" });
const loadImageBlobCore = () => new Promise<Blob>((resolve, reject) => {
const image = new Image();
image.crossOrigin = "anonymous";
image.onload = () => {
const canvas = document.createElement("canvas");
canvas.width = image.width;
canvas.height = image.height;
const ctx = canvas.getContext("2d")!;
ctx.drawImage(image, 0, 0);
canvas.toBlob((blob: Blob) => {
resolve(blob);
});
};
image.onerror = async () => {
try {
await fetch(image.src, { "mode": "no-cors" });

// console.log("possible CORS violation, falling back to allOrigins proxy");
// https://github.com/gnuns/allOrigins
const blob = await loadImageBlob(`https://api.allorigins.win/raw?url=${encodeURIComponent(imgSrc)}`);
resolve(blob);
} catch {
reject();
}
// console.log("possible CORS violation, falling back to allOrigins proxy");
// https://github.com/gnuns/allOrigins
const blob = await loadImageBlob(`https://api.allorigins.win/raw?url=${encodeURIComponent(imgSrc)}`);
resolve(blob);
} catch {
reject();
}
image.src = imgSrc;
});
};
}
image.src = imgSrc;
});
return withTimeout(loadImageBlobTimeout, loadImageBlobCore())
}

export function onElement(
el: Document,
event: keyof HTMLElementEventMap,
selector: string,
el: Document, event: keyof HTMLElementEventMap, selector: string,
listener: Listener,
options?: { capture?: boolean; }
) {
el.on(event, selector, listener, options);
return () => el.off(event, selector, listener, options);
}

export function imageElementFromMouseEvent(event: MouseEvent): HTMLImageElement | undefined {
const imageElement = event.target;
if (!(imageElement instanceof HTMLImageElement)) {
console.error("imageElement is supposed to be a HTMLImageElement. imageElement:");
console.error(imageElement);
}
else {
return imageElement;
}
}

export function getRelativePath(url: URL, app: App): string | undefined {
// getResourcePath("") also works for root path
const baseFileUrl = app.vault.adapter.getFilePath("");
const basePath = baseFileUrl.replace("file://", "");

const urlPathName: string = url.pathname;
if (urlPathName.startsWith(basePath)) {
const relativePath = urlPathName.substring(basePath.length + 1);
return decodeURI(relativePath);
}
}

export function openImageFromMouseEvent(event: MouseEvent, app: App) {
const image = imageElementFromMouseEvent(event);
if (!image) return;

const leaf = app.workspace.getLeaf(true);
app.workspace.setActiveLeaf(leaf, { focus: true });

const relativePath = getRelativePath(new URL(image.currentSrc), app);
if (relativePath) {
const titleContainerEl = (leaf.view as View & { titleContainerEl: Node }).titleContainerEl;
titleContainerEl.empty();
titleContainerEl.createEl("div", { text: relativePath })
}

const contentEl = (leaf.view as View & { contentEl: Node }).contentEl;
contentEl.empty();
const div = contentEl.createEl("div", {});
const img = div.appendChild(document.createElement("img"));
img.src = image.currentSrc;
}
Loading

0 comments on commit 5cf5211

Please sign in to comment.