Skip to content

Commit

Permalink
feat: upload existing local image with context menu
Browse files Browse the repository at this point in the history
Add possibility to upload existing local image from Markdown editor with right-click context menu

#32
  • Loading branch information
gavvvr committed May 31, 2024
1 parent b3c07ca commit d2baefe
Show file tree
Hide file tree
Showing 3 changed files with 107 additions and 5 deletions.
88 changes: 83 additions & 5 deletions src/ImgurPlugin.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,14 @@
import { CanvasView, Editor, MarkdownView, Notice, Plugin } from 'obsidian'
import {
CanvasView,
Editor,
EditorPosition,
MarkdownView,
Menu,
Notice,
Plugin,
TFile,
parseLinktext,
} from 'obsidian'
import ImageUploader from './uploader/ImageUploader'
import ImgurPluginSettingsTab from './ui/ImgurPluginSettingsTab'
import ApiError from './uploader/ApiError'
Expand All @@ -14,6 +24,8 @@ import ImgurAuthenticatedUploader from './uploader/imgur/ImgurAuthenticatedUploa
import { allFilesAreImages } from './utils/FileList'
import { fixImageTypeIfNeeded } from './utils/misc'
import { createImgurCanvasPasteHandler } from './Canvas'
import { IMGUR_POTENTIALLY_SUPPORTED_FILES_EXTENSIONS } from './imgur/constants'
import { localEmbeddedImageExpectedBoundaries } from './utils/editor'

declare module 'obsidian' {
interface MarkdownSubView {
Expand All @@ -23,6 +35,18 @@ declare module 'obsidian' {
interface CanvasView extends TextFileView {
handlePaste: (e: ClipboardEvent) => Promise<void>
}

interface Editor {
getClickableTokenAt(position: EditorPosition): ClickableToken | null
}

type ClickableToken = {
displayText: string
text: string
type: string
start: EditorPosition
end: EditorPosition
}
}

interface ClipboardManager {
Expand Down Expand Up @@ -167,6 +191,52 @@ export default class ImgurPlugin extends Plugin {
)
}

private imgurPluginRightClickHandler = (menu: Menu, editor: Editor, view: MarkdownView) => {
const clickable = editor.getClickableTokenAt(editor.getCursor())

if (!clickable) return
if (clickable.type !== 'internal-link') return

const [localImageExpectedStart, localImageExpectedEnd] =
localEmbeddedImageExpectedBoundaries(clickable)

const clickablePrefix = editor.getRange(localImageExpectedStart, clickable.start)
const clickableSuffix = editor.getRange(clickable.end, localImageExpectedEnd)
if (clickablePrefix !== '![[' || clickableSuffix !== ']]') return

const lt = parseLinktext(clickable.text)
const file = view.app.metadataCache.getFirstLinkpathDest(lt.path, view.file.path)

if (!IMGUR_POTENTIALLY_SUPPORTED_FILES_EXTENSIONS.includes(file.extension)) return

menu.addItem((item) => {
item
.setTitle('Upload to Imgur')
.setIcon('wand')
.onClick(() =>
this.uploadLocalImageFromEditor(
editor,
file,
localImageExpectedStart,
localImageExpectedEnd,
),
)
})
}

private async uploadLocalImageFromEditor(
editor: Editor,
file: TFile,
start: EditorPosition,
end: EditorPosition,
) {
const arrayBuffer = await this.app.vault.readBinary(file)
const fileToUpload = new File([arrayBuffer], file.name)
editor.replaceRange('\n', end, end)
await this.uploadFileAndEmbedImgurImage(fileToUpload, { ch: 0, line: end.line + 1 })
editor.replaceRange(`<!--${editor.getRange(start, end)}-->`, start, end)
}

get imgUploader(): ImageUploader {
return this.imgUploaderField
}
Expand Down Expand Up @@ -216,6 +286,8 @@ export default class ImgurPlugin extends Plugin {
}
}),
)

this.registerEvent(this.app.workspace.on('editor-menu', this.imgurPluginRightClickHandler))
}

private overridePasteHandlerForCanvasView(view: CanvasView) {
Expand Down Expand Up @@ -247,9 +319,9 @@ export default class ImgurPlugin extends Plugin {
new Notice('⚠️ Please configure Imgur plugin or disable it', fiveSecondsMillis)
}

private async uploadFileAndEmbedImgurImage(file: File) {
private async uploadFileAndEmbedImgurImage(file: File, atPos?: EditorPosition) {
const pasteId = (Math.random() + 1).toString(36).substring(2, 7)
this.insertTemporaryText(pasteId)
this.insertTemporaryText(pasteId, atPos)

let imgUrl: string
try {
Expand All @@ -269,9 +341,15 @@ export default class ImgurPlugin extends Plugin {
this.embedMarkDownImage(pasteId, imgUrl)
}

private insertTemporaryText(pasteId: string) {
private insertTemporaryText(pasteId: string, atPos?: EditorPosition) {
const progressText = ImgurPlugin.progressTextFor(pasteId)
this.getEditor().replaceSelection(`${progressText}\n`)
const replacement = `${progressText}\n`
const editor = this.getEditor()
if (atPos) {
editor.replaceRange(replacement, atPos, atPos)
} else {
this.getEditor().replaceSelection(replacement)
}
}

private static progressTextFor(id: string) {
Expand Down
14 changes: 14 additions & 0 deletions src/imgur/constants.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,17 @@
export const IMGUR_PLUGIN_CLIENT_ID = '5d3647b14ed585f'
export const IMGUR_API_BASE = 'https://api.imgur.com/3'
export const IMGUR_ACCESS_TOKEN_LOCALSTORAGE_KEY = 'imgur-access_token'

export const IMGUR_POTENTIALLY_SUPPORTED_FILES_EXTENSIONS = [
'jpeg',
'png',
'gif',
'apng',
'tiff',
'mp4',
'mpeg',
'avi',
'webm',
'mov',
'mkv',
]
10 changes: 10 additions & 0 deletions src/utils/editor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { ClickableToken, EditorPosition } from 'obsidian'

export function localEmbeddedImageExpectedBoundaries(
from: ClickableToken,
): [EditorPosition, EditorPosition] {
return [
{ ...from.start, ch: from.start.ch - 3 },
{ ...from.end, ch: from.end.ch + 2 },
]
}

0 comments on commit d2baefe

Please sign in to comment.