fix: support native clipboard image paste on mobile
This commit is contained in:
@@ -47,6 +47,7 @@
|
|||||||
"@capacitor/android": "^8.0.1",
|
"@capacitor/android": "^8.0.1",
|
||||||
"@capacitor/app": "^8.0.0",
|
"@capacitor/app": "^8.0.0",
|
||||||
"@capacitor/cli": "^8.0.1",
|
"@capacitor/cli": "^8.0.1",
|
||||||
|
"@capacitor/clipboard": "^8.0.1",
|
||||||
"@capacitor/core": "^8.0.1",
|
"@capacitor/core": "^8.0.1",
|
||||||
"@capacitor/filesystem": "^8.1.0",
|
"@capacitor/filesystem": "^8.1.0",
|
||||||
"@capacitor/ios": "^8.0.1",
|
"@capacitor/ios": "^8.0.1",
|
||||||
@@ -62,6 +63,7 @@
|
|||||||
"@poppanator/sveltekit-svg": "^4.2.1",
|
"@poppanator/sveltekit-svg": "^4.2.1",
|
||||||
"@sveltejs/adapter-static": "^3.0.10",
|
"@sveltejs/adapter-static": "^3.0.10",
|
||||||
"@tiptap/core": "^2.27.2",
|
"@tiptap/core": "^2.27.2",
|
||||||
|
"@tiptap/pm": "^2.27.2",
|
||||||
"@types/qrcode": "^1.5.6",
|
"@types/qrcode": "^1.5.6",
|
||||||
"@types/throttle-debounce": "^5.0.2",
|
"@types/throttle-debounce": "^5.0.2",
|
||||||
"@vite-pwa/assets-generator": "^0.2.6",
|
"@vite-pwa/assets-generator": "^0.2.6",
|
||||||
|
|||||||
Generated
+15
@@ -26,6 +26,9 @@ importers:
|
|||||||
'@capacitor/cli':
|
'@capacitor/cli':
|
||||||
specifier: ^8.0.1
|
specifier: ^8.0.1
|
||||||
version: 8.0.1
|
version: 8.0.1
|
||||||
|
'@capacitor/clipboard':
|
||||||
|
specifier: ^8.0.1
|
||||||
|
version: 8.0.1(@capacitor/core@8.0.1)
|
||||||
'@capacitor/core':
|
'@capacitor/core':
|
||||||
specifier: ^8.0.1
|
specifier: ^8.0.1
|
||||||
version: 8.0.1
|
version: 8.0.1
|
||||||
@@ -71,6 +74,9 @@ importers:
|
|||||||
'@tiptap/core':
|
'@tiptap/core':
|
||||||
specifier: ^2.27.2
|
specifier: ^2.27.2
|
||||||
version: 2.27.2(@tiptap/pm@2.27.2)
|
version: 2.27.2(@tiptap/pm@2.27.2)
|
||||||
|
'@tiptap/pm':
|
||||||
|
specifier: ^2.27.2
|
||||||
|
version: 2.27.2
|
||||||
'@types/qrcode':
|
'@types/qrcode':
|
||||||
specifier: ^1.5.6
|
specifier: ^1.5.6
|
||||||
version: 1.5.6
|
version: 1.5.6
|
||||||
@@ -788,6 +794,11 @@ packages:
|
|||||||
engines: {node: '>=22.0.0'}
|
engines: {node: '>=22.0.0'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
'@capacitor/clipboard@8.0.1':
|
||||||
|
resolution: {integrity: sha512-iOlbTi8MojKyLnYE+M27priXid7vHd0PlDwyHohPzkuQ8Rkp6q7ykwZmPEUD+OnU/Ink7Qw/pUOfKgraKmA6Eg==}
|
||||||
|
peerDependencies:
|
||||||
|
'@capacitor/core': '>=8.0.0'
|
||||||
|
|
||||||
'@capacitor/core@8.0.1':
|
'@capacitor/core@8.0.1':
|
||||||
resolution: {integrity: sha512-5UqSWxGMp/B8KhYu7rAijqNtYslhcLh+TrbfU48PfdMDsPfaU/VY48sMNzC22xL8BmoFoql/3SKyP+pavTOvOA==}
|
resolution: {integrity: sha512-5UqSWxGMp/B8KhYu7rAijqNtYslhcLh+TrbfU48PfdMDsPfaU/VY48sMNzC22xL8BmoFoql/3SKyP+pavTOvOA==}
|
||||||
|
|
||||||
@@ -5929,6 +5940,10 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
|
'@capacitor/clipboard@8.0.1(@capacitor/core@8.0.1)':
|
||||||
|
dependencies:
|
||||||
|
'@capacitor/core': 8.0.1
|
||||||
|
|
||||||
'@capacitor/core@8.0.1':
|
'@capacitor/core@8.0.1':
|
||||||
dependencies:
|
dependencies:
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
import {mount} from "svelte"
|
import {mount} from "svelte"
|
||||||
import type {Writable} from "svelte/store"
|
import type {Writable} from "svelte/store"
|
||||||
import {get, derived} from "svelte/store"
|
import {get, derived} from "svelte/store"
|
||||||
|
import {Clipboard} from "@capacitor/clipboard"
|
||||||
|
import {Capacitor} from "@capacitor/core"
|
||||||
|
import {Extension} from "@tiptap/core"
|
||||||
|
import {Plugin, PluginKey} from "@tiptap/pm/state"
|
||||||
import {Router} from "@welshman/router"
|
import {Router} from "@welshman/router"
|
||||||
import {dec, inc} from "@welshman/lib"
|
import {dec, inc} from "@welshman/lib"
|
||||||
import {throttled} from "@welshman/store"
|
import {throttled} from "@welshman/store"
|
||||||
@@ -22,6 +26,83 @@ import {uploadFile} from "@app/core/commands"
|
|||||||
import {deriveSpaceMembers} from "@app/core/state"
|
import {deriveSpaceMembers} from "@app/core/state"
|
||||||
import {pushToast} from "@app/util/toast"
|
import {pushToast} from "@app/util/toast"
|
||||||
|
|
||||||
|
const nativeClipboardAvailable = () =>
|
||||||
|
Capacitor.isNativePlatform() && Capacitor.isPluginAvailable("Clipboard")
|
||||||
|
|
||||||
|
const hasStandardPastePayload = (event: ClipboardEvent) => {
|
||||||
|
const clipboardData = event.clipboardData
|
||||||
|
|
||||||
|
if (!clipboardData) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.from(clipboardData.items).some(item => item.kind === "file")) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (clipboardData.types.includes("text/html")) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return clipboardData.getData("text/plain") !== ""
|
||||||
|
}
|
||||||
|
|
||||||
|
const getNativeClipboardImage = async () => {
|
||||||
|
try {
|
||||||
|
const {type, value} = await Clipboard.read()
|
||||||
|
|
||||||
|
if (!type.startsWith("image/") || value === "") {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
const imageData = value.startsWith("data:") ? value : `data:${type};base64,${value}`
|
||||||
|
const blob = await fetch(imageData).then(res => res.blob())
|
||||||
|
|
||||||
|
if (!blob.type.startsWith("image/")) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
const extension = type.split("/")[1]?.split("+")[0] || "png"
|
||||||
|
|
||||||
|
return new File([blob], `clipboard-image.${extension}`, {type: blob.type || type})
|
||||||
|
} catch {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const NativeClipboardPasteExtension = Extension.create({
|
||||||
|
name: "nativeClipboardPaste",
|
||||||
|
|
||||||
|
addProseMirrorPlugins() {
|
||||||
|
const editor = this.editor
|
||||||
|
|
||||||
|
return [
|
||||||
|
new Plugin({
|
||||||
|
key: new PluginKey("nativeClipboardPaste"),
|
||||||
|
props: {
|
||||||
|
handlePaste: (_view, event) => {
|
||||||
|
if (!nativeClipboardAvailable() || hasStandardPastePayload(event)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
event.preventDefault()
|
||||||
|
|
||||||
|
void getNativeClipboardImage().then(file => {
|
||||||
|
if (!file) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
editor.commands.addFile(file, editor.state.selection.from + 1)
|
||||||
|
})
|
||||||
|
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
]
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
export const makeEditor = async ({
|
export const makeEditor = async ({
|
||||||
encryptFiles = false,
|
encryptFiles = false,
|
||||||
aggressive = false,
|
aggressive = false,
|
||||||
@@ -138,6 +219,7 @@ export const makeEditor = async ({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
NativeClipboardPasteExtension,
|
||||||
],
|
],
|
||||||
onUpdate({editor}) {
|
onUpdate({editor}) {
|
||||||
wordCount?.set(editor.storage.wordCount.words)
|
wordCount?.set(editor.storage.wordCount.words)
|
||||||
|
|||||||
Reference in New Issue
Block a user