mirror of
https://github.com/hcengineering/platform.git
synced 2024-11-22 21:50:34 +03:00
UBERF-5021 Parse image size when dropping to editor (#4443)
Signed-off-by: Alexander Onnikov <alexander.onnikov@xored.com>
This commit is contained in:
parent
7879dffc3c
commit
b8e2d28a20
@ -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)
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user