Skip to content

Commit 5cf5211

Browse files
authored
Merge pull request #37 from waterproofsodium/open-in-new-tab-feature
Open in new tab feature
2 parents b3f9b25 + 39dacfa commit 5cf5211

8 files changed

+157
-103
lines changed

.eslintrc .eslintrc.json

+9-3
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,22 @@
88
"extends": [
99
"eslint:recommended",
1010
"plugin:@typescript-eslint/eslint-recommended",
11-
"plugin:@typescript-eslint/recommended"
11+
"plugin:@typescript-eslint/recommended-type-checked"
1212
],
1313
"parserOptions": {
14-
"sourceType": "module"
14+
"sourceType": "module",
15+
"project": true
1516
},
1617
"rules": {
1718
"no-unused-vars": "off",
1819
"@typescript-eslint/no-unused-vars": ["error", { "args": "none" }],
1920
"@typescript-eslint/ban-ts-comment": "off",
2021
"no-prototype-builtins": "off",
21-
"@typescript-eslint/no-empty-function": "off"
22+
"@typescript-eslint/no-unsafe-assignment": "off",
23+
"@typescript-eslint/no-unsafe-member-access": "off",
24+
"@typescript-eslint/no-unsafe-argument": "off",
25+
"@typescript-eslint/no-empty-function": "off",
26+
"@typescript-eslint/no-floating-promises": "off",
27+
"@typescript-eslint/no-misused-promises": "off"
2228
}
2329
}

README.md

+7-4
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
# Image Context Menus
22

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

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

@@ -40,8 +42,9 @@ Contributions are welcome.
4042
Original plugin by [NomarCub](https://github.com/NomarCub).
4143
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).
4244

43-
[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).
44-
[Android image sharing](https://github.com/NomarCub/obsidian-copy-url-in-preview/issues/5) developed by [mnaoumov](https://github.com/mnaoumov).
45-
[Open PDF externally](https://github.com/NomarCub/obsidian-copy-url-in-preview/issues/9) feature developed by [mnaoumov](https://github.com/mnaoumov).
45+
- [Open in new tab](https://github.com/NomarCub/obsidian-copy-url-in-preview/pull/37) developed by [waterproofsodium](https://github.com/waterproofsodium)
46+
- [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).
47+
- [Android image sharing](https://github.com/NomarCub/obsidian-copy-url-in-preview/issues/5) developed by [mnaoumov](https://github.com/mnaoumov).
48+
- [Open PDF externally](https://github.com/NomarCub/obsidian-copy-url-in-preview/issues/9) feature developed by [mnaoumov](https://github.com/mnaoumov).
4649

4750
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.

manifest.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"id": "copy-url-in-preview",
33
"name": "Image Context Menus",
4-
"version": "1.6.0",
4+
"version": "1.7.0",
55
"minAppVersion": "1.5.7",
66
"description": "Copy, open in default app, show in system explorer, reveal in navigation context menu for images. Also Open PDF externally context menu.",
77
"author": "NomarCub",

package-lock.json

+6-6
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
{
22
"name": "copy-url-in-preview",
3-
"version": "1.6.0",
3+
"version": "1.7.0",
44
"description": "Copy Image, Copy URL and Open PDF externally context menu in reading view (formerly preview mode) for Obsidian (https://obsidian.md)",
55
"main": "main.js",
66
"scripts": {
77
"dev": "node esbuild.config.mjs",
8+
"lint": "eslint src",
89
"build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production",
910
"version": "node version-bump.mjs && git add manifest.json versions.json"
1011
},
@@ -19,7 +20,7 @@
1920
"esbuild": "0.17.3",
2021
"eslint": "8.46.0",
2122
"obsidian": "~1.5.7-1",
22-
"obsidian-typings": "^1.0.6",
23+
"obsidian-typings": "^1.1.2",
2324
"tslib": "2.4.0",
2425
"typescript": "4.7.4"
2526
}

src/helpers.ts

+74-34
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { FileSystemAdapter } from "obsidian";
1+
import { App, FileSystemAdapter, View } from "obsidian";
22

33
const loadImageBlobTimeout = 5_000;
44

@@ -15,60 +15,100 @@ export interface Listener {
1515
}
1616

1717
export function withTimeout<T>(ms: number, promise: Promise<T>): Promise<T> {
18-
const timeout = new Promise((resolve, reject) => {
18+
const timeout = new Promise((_resolve, reject) => {
1919
const id = setTimeout(() => {
2020
clearTimeout(id);
2121
reject(`timed out after ${ms} ms`)
2222
}, ms)
23-
})
23+
}) as unknown as Promise<T>;
2424
return Promise.race([
2525
promise,
2626
timeout
27-
]) as Promise<T>
27+
]);
2828
}
2929

3030
// https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_enabled_image
3131
// option?: https://www.npmjs.com/package/html-to-image
3232
export async function loadImageBlob(imgSrc: string): Promise<Blob> {
33-
const loadImageBlobCore = () => {
34-
return new Promise<Blob>((resolve, reject) => {
35-
const image = new Image();
36-
image.crossOrigin = "anonymous";
37-
image.onload = () => {
38-
const canvas = document.createElement("canvas");
39-
canvas.width = image.width;
40-
canvas.height = image.height;
41-
const ctx = canvas.getContext("2d")!;
42-
ctx.drawImage(image, 0, 0);
43-
canvas.toBlob((blob: Blob) => {
44-
resolve(blob);
45-
});
46-
};
47-
image.onerror = async () => {
48-
try {
49-
await fetch(image.src, { "mode": "no-cors" });
33+
const loadImageBlobCore = () => new Promise<Blob>((resolve, reject) => {
34+
const image = new Image();
35+
image.crossOrigin = "anonymous";
36+
image.onload = () => {
37+
const canvas = document.createElement("canvas");
38+
canvas.width = image.width;
39+
canvas.height = image.height;
40+
const ctx = canvas.getContext("2d")!;
41+
ctx.drawImage(image, 0, 0);
42+
canvas.toBlob((blob: Blob) => {
43+
resolve(blob);
44+
});
45+
};
46+
image.onerror = async () => {
47+
try {
48+
await fetch(image.src, { "mode": "no-cors" });
5049

51-
// console.log("possible CORS violation, falling back to allOrigins proxy");
52-
// https://github.com/gnuns/allOrigins
53-
const blob = await loadImageBlob(`https://api.allorigins.win/raw?url=${encodeURIComponent(imgSrc)}`);
54-
resolve(blob);
55-
} catch {
56-
reject();
57-
}
50+
// console.log("possible CORS violation, falling back to allOrigins proxy");
51+
// https://github.com/gnuns/allOrigins
52+
const blob = await loadImageBlob(`https://api.allorigins.win/raw?url=${encodeURIComponent(imgSrc)}`);
53+
resolve(blob);
54+
} catch {
55+
reject();
5856
}
59-
image.src = imgSrc;
60-
});
61-
};
57+
}
58+
image.src = imgSrc;
59+
});
6260
return withTimeout(loadImageBlobTimeout, loadImageBlobCore())
6361
}
6462

6563
export function onElement(
66-
el: Document,
67-
event: keyof HTMLElementEventMap,
68-
selector: string,
64+
el: Document, event: keyof HTMLElementEventMap, selector: string,
6965
listener: Listener,
7066
options?: { capture?: boolean; }
7167
) {
7268
el.on(event, selector, listener, options);
7369
return () => el.off(event, selector, listener, options);
7470
}
71+
72+
export function imageElementFromMouseEvent(event: MouseEvent): HTMLImageElement | undefined {
73+
const imageElement = event.target;
74+
if (!(imageElement instanceof HTMLImageElement)) {
75+
console.error("imageElement is supposed to be a HTMLImageElement. imageElement:");
76+
console.error(imageElement);
77+
}
78+
else {
79+
return imageElement;
80+
}
81+
}
82+
83+
export function getRelativePath(url: URL, app: App): string | undefined {
84+
// getResourcePath("") also works for root path
85+
const baseFileUrl = app.vault.adapter.getFilePath("");
86+
const basePath = baseFileUrl.replace("file://", "");
87+
88+
const urlPathName: string = url.pathname;
89+
if (urlPathName.startsWith(basePath)) {
90+
const relativePath = urlPathName.substring(basePath.length + 1);
91+
return decodeURI(relativePath);
92+
}
93+
}
94+
95+
export function openImageFromMouseEvent(event: MouseEvent, app: App) {
96+
const image = imageElementFromMouseEvent(event);
97+
if (!image) return;
98+
99+
const leaf = app.workspace.getLeaf(true);
100+
app.workspace.setActiveLeaf(leaf, { focus: true });
101+
102+
const relativePath = getRelativePath(new URL(image.currentSrc), app);
103+
if (relativePath) {
104+
const titleContainerEl = (leaf.view as View & { titleContainerEl: Node }).titleContainerEl;
105+
titleContainerEl.empty();
106+
titleContainerEl.createEl("div", { text: relativePath })
107+
}
108+
109+
const contentEl = (leaf.view as View & { contentEl: Node }).contentEl;
110+
contentEl.empty();
111+
const div = contentEl.createEl("div", {});
112+
const img = div.appendChild(document.createElement("img"));
113+
img.src = image.currentSrc;
114+
}

0 commit comments

Comments
 (0)