Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

adds copy images (local and remote) #2

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
33 changes: 15 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,26 +1,23 @@
# Copy URL in preview mode
# Danger. Ugly code ahead 😱

This plugin for [Obsidian](https://obsidian.md/) is a copy URL context menu in preview mode that works like the built in one in edit mode.
- My fork of https://github.com/NomarCub/obsidian-copy-url-in-preview
- Adds a right-click → Copy image function

![desktop](https://user-images.githubusercontent.com/5298006/125515738-8fb2143d-6502-46d3-a1b8-57b025211c2f.gif)
![image](https://user-images.githubusercontent.com/1992842/126041872-da60dbaa-2bdc-4511-9d05-6ec77157ed46.png)

The plugin also works on mobile, but was only tested on Android.
# Installation instructions for testing

![android](https://user-images.githubusercontent.com/5298006/125515758-bdf77074-a58c-4a6d-affa-88d031991ab2.gif)
1. Open a Terminal

## Compatibility
```shell
$ git clone --branch copy-images https://github.com/luckman212/obsidian-copy-url-in-preview
$ cd obsidian-copy-url-in-preview
$ npm i; npm run build
```

The plugin was tested in Obsidian v0.11.13 and subsequent versions, but probably works with older versions.
2. Copy the files to your Obsidian plugins dir:

## Installation
- If you already have **copy-url-in-preview** installed: copy `main.js` to your `<vault>/plugins/copy-url-in-preview/` directory (overwriting the original).
- If you _don't_: create a new folder in your plugins directory, and put `main.js` and `manifest.json` in it.

You can install the plugin via the Community Plugins tab within Obsidian.
You can also manually copy from releases to your `.obsidian/plugins/copy-url-in-preview` folder.

## Credits

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.

## Support

If you like this plugin you can support me on PayPal here: [![Paypal](https://img.shields.io/badge/paypal-nomarcub-yellow?style=social&logo=paypal)](https://paypal.me/nomarcub)
3. (Re)launch Obsidian
213 changes: 141 additions & 72 deletions main.ts
Original file line number Diff line number Diff line change
@@ -1,72 +1,141 @@
import {
Menu, Plugin, Notice, MenuItem
} from "obsidian";

interface Listener {
(this: Document, ev: Event): any;
}

function onElement(
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 default class CopyUrlInPreview extends Plugin {
onload() {
this.register(
onElement(
document,
"contextmenu" as keyof HTMLElementEventMap,
"a.external-link",
this.onClick.bind(this)
)
);
}

// Android gives a PointerEvent, a child to MouseEvent.
// Positions are not accurate from PointerEvent.
// There's also TouchEvent
// The event has target, path, toEvent (null on Android) for finding the link
onClick(event: MouseEvent) {
if (!(event.target instanceof HTMLAnchorElement)) {
return;
}

event.preventDefault();

let link = event.target.href;

const menu = new Menu(this.app);
menu.addItem((item: MenuItem) =>
item.setIcon("link")
.setTitle("Copy url")
.onClick(() => {
navigator.clipboard.writeText(link);
new Notice("Url copied to your clipboard");
})
);
menu.register(
onElement(
document,
"keydown" as keyof HTMLElementEventMap,
"*",
(e: KeyboardEvent) => {
if (e.key === "Escape") {
e.preventDefault();
e.stopPropagation();
menu.hide();
}
}
)
);
menu.showAtPosition({ x: event.pageX, y: event.pageY });

this.app.workspace.trigger("copy-url-in-preview:contextmenu", menu);
}
}
import {
Menu, Plugin, Notice, MenuItem
} from "obsidian";

const { clipboard, nativeImage } = require('electron');
const http = require('http');
const https = require('https');

interface Listener {
(this: Document, ev: Event): any;
}

function onElement(
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 default class CopyUrlInPreview extends Plugin {
onload() {
this.register(
onElement(
document,
"contextmenu" as keyof HTMLElementEventMap,
"a.external-link",
this.onClick.bind(this)
)
);
this.register(
onElement(
document,
"contextmenu" as keyof HTMLElementEventMap,
"img",
this.onClick.bind(this)
)
);
}

// Android gives a PointerEvent, a child to MouseEvent.
// Positions are not accurate from PointerEvent.
// There's also TouchEvent
// The event has target, path, toEvent (null on Android) for finding the link
onClick(event: MouseEvent) {
event.preventDefault();
const target = (event.target as any);
const elType = <String>target.localName;

const menu = new Menu(this.app);
switch (elType) {
case 'img':
const image = target.currentSrc;
var theImage = nativeImage.createEmpty();
const thisURL = new URL(image);
const thisHREF = thisURL.href;
const Proto = <String>thisURL.protocol;
switch (Proto) {
case 'app:':
// console.log('local vault image');
var imPath = decodeURIComponent(image);
var imPath = imPath.replace(/^app:\/\/local\//, '');
var imPath = imPath.replace(/\?.*$/, '');
theImage = nativeImage.createFromPath(imPath);
break;
case 'http:':
// console.log('remote HTTP image: %s', thisHREF);
http.get(thisHREF, (resp: any) => {
resp.setEncoding('base64');
var body = "data:" + resp.headers["content-type"] + ";base64,";
resp.on('data', (data: any) => { body += data});
resp.on('end', () => {
theImage = nativeImage.createFromDataURL(body);
});
}).on('error', (e: Error) => {
console.log(`error: ${e.message}`);
});
break;
case 'https:':
// console.log('remote HTTPS image: %s', thisHREF);
https.get(thisHREF, (resp: any) => {
resp.setEncoding('base64');
var body = "data:" + resp.headers["content-type"] + ";base64,";
resp.on('data', (data: any) => { body += data});
resp.on('end', () => {
theImage = nativeImage.createFromDataURL(body);
});
}).on('error', (e: Error) => {
console.log(`error: ${e.message}`);
});
break;
default:
new Notice("no handler for `" + Proto +"` protocol");
return;
}
menu.addItem((item: MenuItem) =>
item.setIcon("link")
.setTitle("Copy image")
.onClick(() => {
clipboard.writeImage(theImage);
new Notice("Image copied to your clipboard");
})
);
break;
case 'a':
let link = target.href;
menu.addItem((item: MenuItem) =>
item.setIcon("link")
.setTitle("Copy url")
.onClick(() => {
navigator.clipboard.writeText(link);
new Notice("Url copied to your clipboard");
})
);
break;
default:
new Notice("no handler for this element type");
return;
}
menu.register(
onElement(
document,
"keydown" as keyof HTMLElementEventMap,
"*",
(e: KeyboardEvent) => {
if (e.key === "Escape") {
e.preventDefault();
e.stopPropagation();
menu.hide();
}
}
)
);
menu.showAtPosition({ x: event.pageX, y: event.pageY });

this.app.workspace.trigger("copy-url-in-preview:contextmenu", menu);
}
}