UBERF-5021 Parse image size when dropping to editor (#4443)

Signed-off-by: Alexander Onnikov <alexander.onnikov@xored.com>
This commit is contained in:
Alexander Onnikov 2024-01-25 16:17:26 +07:00 committed by GitHub
parent 7879dffc3c
commit b8e2d28a20
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 128 additions and 19 deletions

View File

@ -821,6 +821,9 @@ dependencies:
'@types/pdfkit':
specifier: ~0.12.3
version: 0.12.12
'@types/png-chunks-extract':
specifier: ^1.0.2
version: 1.0.2
'@types/qs':
specifier: ~6.9.7
version: 6.9.8
@ -1037,6 +1040,9 @@ dependencies:
pdfkit:
specifier: ~0.13.0
version: 0.13.0
png-chunks-extract:
specifier: ^1.0.0
version: 1.0.0
postcss:
specifier: ^8.4.20
version: 8.4.31
@ -7295,6 +7301,10 @@ packages:
'@types/node': 16.11.68
dev: false
/@types/png-chunks-extract@1.0.2:
resolution: {integrity: sha512-z6djfFIbrrddtunoMJBOPlyZrnmeuG1kkvHUNi2QfpOb+JMMLuLliHHTmMyRi7k7LiTAut0HbdGCF6ibDtQAHQ==}
dev: false
/@types/pretty-hrtime@1.0.1:
resolution: {integrity: sha512-VjID5MJb1eGKthz2qUerWT8+R4b9N+CHvGCzg9fn4kWZgaF9AhdYikQio3R7wV8YY1NsQKPaCwKz1Yff+aHNUQ==}
dev: false
@ -9070,6 +9080,11 @@ packages:
typescript: 5.2.2
dev: false
/crc-32@0.3.0:
resolution: {integrity: sha512-kucVIjOmMc1f0tv53BJ/5WIX+MGLcKuoBhnGqQrgKJNqLByb/sVMWfW/Aw6hw0jgcqjJ2pi9E5y32zOIpaUlsA==}
engines: {node: '>=0.8'}
dev: false
/create-jest@29.7.0(@types/node@16.11.68)(ts-node@10.9.1):
resolution: {integrity: sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
@ -14053,6 +14068,12 @@ packages:
fsevents: 2.3.2
dev: false
/png-chunks-extract@1.0.0:
resolution: {integrity: sha512-ZiVwF5EJ0DNZyzAqld8BP1qyJBaGOFaq9zl579qfbkcmOwWLLO4I9L8i2O4j3HkI6/35i0nKG2n+dZplxiT89Q==}
dependencies:
crc-32: 0.3.0
dev: false
/png-js@1.0.0:
resolution: {integrity: sha512-k+YsbhpA9e+EFfKjTCH3VW6aoKlyNYI6NYdTfDL4CIvFnvsuO84ttonmZE7rc+v23SLTH8XX+5w/Ak9v0xGY4g==}
dev: false
@ -23684,7 +23705,7 @@ packages:
dev: false
file:projects/text-editor.tgz(@types/node@16.11.68)(bufferutil@4.0.7)(esbuild@0.16.17)(postcss-load-config@4.0.1)(postcss@8.4.31)(prosemirror-model@1.19.3)(ts-node@10.9.1):
resolution: {integrity: sha512-q4IthAiGXw8l3HkEitwxvXppLyB/mszif5wHdy0jcaz7Yc0moJOzS6ElJ3AgA/0IQHxKB7ladDAkU/wklo7icw==, tarball: file:projects/text-editor.tgz}
resolution: {integrity: sha512-iPBMTKEApdZq+f8GfFNZHYKFBWwfm0z5+MVzB4Tm3FU+kjd0dl4h11aaz0s6mtOHeEQetgrKE+82yGBStsfmbQ==, tarball: file:projects/text-editor.tgz}
id: file:projects/text-editor.tgz
name: '@rush-temp/text-editor'
version: 0.0.0
@ -23717,6 +23738,7 @@ packages:
'@tiptap/suggestion': 2.1.12(@tiptap/core@2.1.12)(@tiptap/pm@2.1.12)
'@types/diff': 5.0.5
'@types/jest': 29.5.5
'@types/png-chunks-extract': 1.0.2
'@typescript-eslint/eslint-plugin': 6.11.0(@typescript-eslint/parser@6.11.0)(eslint@8.54.0)(typescript@5.2.2)
'@typescript-eslint/parser': 6.11.0(eslint@8.54.0)(typescript@5.2.2)
diff: 5.1.0
@ -23728,6 +23750,7 @@ packages:
eslint-plugin-svelte: 2.35.0(eslint@8.54.0)(svelte@4.2.5)(ts-node@10.9.1)
jest: 29.7.0(@types/node@16.11.68)(ts-node@10.9.1)
lib0: 0.2.88
png-chunks-extract: 1.0.0
prettier: 3.1.0
prettier-plugin-svelte: 3.1.0(prettier@3.1.0)(svelte@4.2.5)
prosemirror-codemark: 0.4.2(prosemirror-model@1.19.3)

View File

@ -33,7 +33,8 @@
"jest": "^29.7.0",
"ts-jest": "^29.1.1",
"@types/jest": "^29.5.5",
"svelte-eslint-parser": "^0.33.1"
"svelte-eslint-parser": "^0.33.1",
"@types/png-chunks-extract": "^1.0.2"
},
"dependencies": {
"@hcengineering/presentation": "^0.6.2",
@ -77,6 +78,7 @@
"rfc6902": "^5.0.1",
"diff": "^5.1.0",
"slugify": "^1.6.6",
"lib0": "^0.2.88"
"lib0": "^0.2.88",
"png-chunks-extract": "^1.0.0"
}
}

View File

@ -63,7 +63,8 @@
return {
id: `#imageWidth${it}`,
label: it === plugin.string.Unset ? it : getEmbeddedLabel(it),
action: () => textEditor.commands.setImageSize({ width: it === plugin.string.Unset ? undefined : it }),
action: () =>
textEditor.commands.setImageSize({ width: it === plugin.string.Unset ? undefined : it, height: undefined }),
category: {
label: plugin.string.Width
}

View File

@ -19,6 +19,7 @@ import { mergeAttributes, nodeInputRule } from '@tiptap/core'
import { type Node as ProseMirrorNode } from '@tiptap/pm/model'
import { Plugin, PluginKey } from '@tiptap/pm/state'
import { type EditorView } from '@tiptap/pm/view'
import extract from 'png-chunks-extract'
/**
* @public
@ -205,7 +206,9 @@ export const ImageExtension = ImageNode.extend<ImageOptions>({
},
addProseMirrorPlugins () {
const opt = this.options
const attachFile = this.options.attachFile
const uploadUrl = this.options.uploadUrl
function handleDrop (
view: EditorView,
pos: { pos: number, inside: number } | null,
@ -216,7 +219,7 @@ export const ImageExtension = ImageNode.extend<ImageOptions>({
for (const uri of uris) {
if (uri !== '') {
const url = new URL(uri)
if (opt.uploadUrl === undefined || !url.href.includes(opt.uploadUrl)) {
if (uploadUrl === undefined || !url.href.includes(uploadUrl)) {
continue
}
@ -242,25 +245,18 @@ export const ImageExtension = ImageNode.extend<ImageOptions>({
}
const files = dataTransfer?.files
if (files !== undefined && opt.attachFile !== undefined) {
if (files !== undefined && attachFile !== undefined) {
for (let i = 0; i < files.length; i++) {
const file = files.item(i)
if (file != null) {
result = true
void opt.attachFile(file).then((id) => {
if (id !== undefined) {
if (id.type.includes('image')) {
const node = view.state.schema.nodes.image.create({ 'file-id': id.file })
const transaction = view.state.tr.insert(pos?.pos ?? 0, node)
view.dispatch(transaction)
}
}
})
void handleImageUpload(file, view, pos, attachFile, uploadUrl)
}
}
}
return result
}
return [
new Plugin({
key: new PluginKey('handle-image-paste'),
@ -283,7 +279,12 @@ export const ImageExtension = ImageNode.extend<ImageOptions>({
if (dataTransfer !== null) {
return handleDrop(view, view.posAtCoords({ left: event.x, top: event.y }), dataTransfer)
}
},
}
}
}),
new Plugin({
key: new PluginKey('handle-image-open'),
props: {
handleDoubleClickOn (view, pos, node, nodePos, event) {
if (node.type.name !== 'image') {
return
@ -309,3 +310,84 @@ export const ImageExtension = ImageNode.extend<ImageOptions>({
]
}
})
async function handleImageUpload (
file: File,
view: EditorView,
pos: { pos: number, inside: number } | null,
attachFile: FileAttachFunction,
uploadUrl: string
): Promise<void> {
const size = await getImageSize(file)
const attached = await attachFile(file)
if (attached !== undefined) {
if (attached.type.includes('image')) {
const image = new Image()
image.onload = () => {
const node = view.state.schema.nodes.image.create({
'file-id': attached.file,
width: size?.width ?? image.naturalWidth
})
const transaction = view.state.tr.insert(pos?.pos ?? 0, node)
view.dispatch(transaction)
}
image.src = getFileUrl(attached.file, 'full', uploadUrl)
}
}
}
async function getImageSize (file: File): Promise<{ width: number, height: number } | undefined> {
if (file.type !== 'image/png') {
return undefined
}
try {
const buffer = await file.arrayBuffer()
const chunks = extract(new Uint8Array(buffer))
const pHYsChunk = chunks.find((chunk) => chunk.name === 'pHYs')
const iHDRChunk = chunks.find((chunk) => chunk.name === 'IHDR')
if (pHYsChunk === undefined || iHDRChunk === undefined) {
return undefined
}
// See http://www.libpng.org/pub/png/spec/1.2/PNG-Chunks.html
// Section 4.1.1. IHDR Image header
// Section 4.2.4.2. pHYs Physical pixel dimensions
const idhrData = parseIHDR(new DataView(iHDRChunk.data.buffer))
const physData = parsePhys(new DataView(pHYsChunk.data.buffer))
if (physData.unit === 0 && physData.ppux === physData.ppuy) {
const pixelRatio = Math.round(physData.ppux / 2834.5)
return {
width: Math.round(idhrData.width / pixelRatio),
height: Math.round(idhrData.height / pixelRatio)
}
}
} catch (err) {
console.error(err)
return undefined
}
return undefined
}
// See http://www.libpng.org/pub/png/spec/1.2/PNG-Chunks.html
// Section 4.1.1. IHDR Image header
function parseIHDR (view: DataView): { width: number, height: number } {
return {
width: view.getUint32(0),
height: view.getUint32(4)
}
}
// See http://www.libpng.org/pub/png/spec/1.2/PNG-Chunks.html
// Section 4.2.4.2. pHYs Physical pixel dimensions
function parsePhys (view: DataView): { ppux: number, ppuy: number, unit: number } {
return {
ppux: view.getUint32(0),
ppuy: view.getUint32(4),
unit: view.getUint8(4)
}
}

View File

@ -223,8 +223,9 @@
.ProseMirror-selectednode {
img {
box-shadow: 0 0 0 2px var(--text-editor-selected-node-color);
border-radius: 0.125rem;
outline: 2px solid var(--primary-button-outline);
outline-offset: 2px;
border-radius: 2px;
}
}