Skip to content

Commit

Permalink
Fixes #4738 - Inline images are not resized during the usage of the e…
Browse files Browse the repository at this point in the history
…ditor
  • Loading branch information
sheremet-va committed Aug 3, 2023
1 parent fcbff0e commit d813613
Show file tree
Hide file tree
Showing 11 changed files with 340 additions and 26 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,7 @@ useHeader({
const loadAvatar = async (input?: HTMLInputElement) => {
const files = input?.files
if (!files) return
const [avatar] = await convertFileList(files)
avatarImage.value = avatar
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

<script setup lang="ts">
import type { FormFieldContext } from '#shared/components/Form/types/field.ts'
import { convertFileList } from '#shared/utils/files.ts'
import type { Editor } from '@tiptap/vue-3'
import { useEditor, EditorContent } from '@tiptap/vue-3'
import { useEventListener } from '@vueuse/core'
Expand All @@ -25,6 +24,9 @@ import type {
import FieldEditorActionBar from './FieldEditorActionBar.vue'
import FieldEditorFooter from './FieldEditorFooter.vue'
import { PLUGIN_NAME as userMentionPluginName } from './suggestions/UserMention.ts'
import { getNodeByName } from '../../utils.ts'
import type { FieldFileContext } from '../FieldFile/types.ts'
import { convertInlineImages } from './utils.ts'
interface Props {
context: FormFieldContext<FieldEditorProps>
Expand Down Expand Up @@ -61,9 +63,58 @@ getCustomExtensions(reactiveContext).forEach((extension) => {
}
})
const hasImageExtension = editorExtensions.some(
(extension) => extension.name === 'image',
)
const showActionBar = ref(false)
const editorValue = ref<string>(VITE_TEST_MODE ? props.context._value : '')
interface LoadImagesOptions {
attachNonInlineFiles: boolean
}
// there is also a gif, but desktop only inlines these two for now
const imagesMimeType = ['image/png', 'image/jpeg']
const loadFiles = (
files: FileList | null | undefined,
editor: Editor | undefined,
options: LoadImagesOptions,
) => {
if (!files) {
return false
}
const inlineImages: File[] = []
const otherFiles: File[] = []
for (const file of files) {
if (imagesMimeType.includes(file.type)) {
inlineImages.push(file)
} else {
otherFiles.push(file)
}
}
if (inlineImages.length && editor) {
convertInlineImages(inlineImages, editor.view.dom).then((urls) => {
if (editor?.isDestroyed) return
editor?.commands.setImages(urls)
})
}
if (options.attachNonInlineFiles && otherFiles.length) {
const attachmentsContext = getNodeByName(
props.context.formId,
'attachments',
)?.context as unknown as FieldFileContext | undefined
attachmentsContext?.uploadFiles(otherFiles)
}
return Boolean(
inlineImages.length || (options.attachNonInlineFiles && otherFiles.length),
)
}
const editor = useEditor({
extensions: editorExtensions,
editorProps: {
Expand All @@ -76,31 +127,30 @@ const editor = useEditor({
},
// add inlined files
handlePaste(view, event) {
if (!editorExtensions.some((n) => n.name === 'image')) {
if (!hasImageExtension) {
return
}
const files = event.clipboardData?.files || null
convertFileList(files).then((urls) => {
editor.value?.commands.setImages(urls)
const loaded = loadFiles(event.clipboardData?.files, editor.value, {
attachNonInlineFiles: false,
})
if (files && files.length) {
if (loaded) {
event.preventDefault()
return true
}
return false
},
handleDrop(view, event) {
if (!editorExtensions.some((n) => n.name === 'image')) {
if (!hasImageExtension) {
return
}
const e = event as unknown as InputEvent
const files = e.dataTransfer?.files || null
convertFileList(files).then((urls) => {
editor.value?.commands.setImages(urls)
const loaded = loadFiles(files, editor.value, {
attachNonInlineFiles: true,
})
if (files && files.length) {
if (loaded) {
event.preventDefault()
return true
}
Expand Down Expand Up @@ -274,6 +324,16 @@ onMounted(() => {
) => void)[]
onLoad.forEach((fn) => fn(editorCustomContext))
onLoad.length = 0
if (VITE_TEST_MODE) {
if (!('editors' in globalThis))
Object.defineProperty(globalThis, 'editors', { value: {} })
Object.defineProperty(
Reflect.get(globalThis, 'editors'),
props.context.node.name,
{ value: editor.value, configurable: true },
)
}
})
</script>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,11 @@ const imageLoaded = ref(false)
const isDraggable = computed(() => props.node.attrs.isDraggable)
const src = computed(() => props.node.attrs.src)
if (needBase64Convert(src.value)) {
loadImageIntoBase64(src.value, props.node.attrs.alt).then((base64) => {
loadImageIntoBase64(
src.value,
props.node.attrs.type,
props.node.attrs.alt,
).then((base64) => {
if (base64) {
props.updateAttributes({ src: base64 })
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ export default Image.extend({
return {}
},
},

type: {
default: null,
renderHTML: () => ({}),
},
}
},
addNodeView() {
Expand All @@ -51,6 +56,7 @@ export default Image.extend({
attrs: {
src: image.content,
alt: image.name,
type: image.type,
},
})),
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// Copyright (C) 2012-2023 Zammad Foundation, https://zammad-foundation.org/

import { i18n } from '#shared/i18n.ts'
import { convertFileList } from '#shared/utils/files.ts'
import type { ChainedCommands } from '@tiptap/core'
import type { Editor } from '@tiptap/vue-3'
import { computed, onUnmounted } from 'vue'
Expand All @@ -11,6 +10,7 @@ import { PLUGIN_NAME as KnowledgeBaseMentionName } from './suggestions/Knowledge
import { PLUGIN_NAME as TextModuleMentionName } from './suggestions/TextModuleSuggestion.ts'
import { PLUGIN_NAME as UserMentionName } from './suggestions/UserMention.ts'
import type { EditorContentType } from './types.ts'
import { convertInlineImages } from './utils.ts'

export interface EditorButton {
name: string
Expand Down Expand Up @@ -62,8 +62,10 @@ export default function useEditorActions(

onUnmounted(() => {
fileInput?.remove()
fileInput = null
})

// eslint-disable-next-line sonarjs/cognitive-complexity
const getActionsList = (): EditorButton[] => {
return [
{
Expand Down Expand Up @@ -102,12 +104,15 @@ export default function useEditorActions(
command: focused((c) => {
const input = getInputForImage()
input.onchange = async () => {
if (!input.files?.length) return
const files = await convertFileList(input.files)
if (!input.files?.length || !editor.value) return
const files = await convertInlineImages(
input.files,
editor.value.view.dom,
)
c.setImages(files).run()
input.value = ''
}
input.click()
if (!VITE_TEST_MODE) input.click()
}),
},
{
Expand Down
20 changes: 20 additions & 0 deletions app/frontend/shared/components/Form/fields/FieldEditor/utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
// Copyright (C) 2012-2023 Zammad Foundation, https://zammad-foundation.org/

import { convertFileList } from '#shared/utils/files.ts'

export const populateEditorNewLines = (htmlContent: string): string => {
const body = document.createElement('div')
body.innerHTML = htmlContent
Expand All @@ -17,3 +19,21 @@ export const populateEditorNewLines = (htmlContent: string): string => {
})
return body.innerHTML
}

export const convertInlineImages = (
inlineImages: FileList | File[],
editorElement: HTMLElement,
) => {
return convertFileList(inlineImages, {
compress: true,
onCompress: () => {
const editorWidth = editorElement.clientWidth
const maxWidth = editorWidth > 1000 ? editorWidth : 1000
return {
x: maxWidth,
scale: 2,
type: 'image/jpeg',
}
},
})
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,16 +50,15 @@ const canInteract = computed(
const fileInput = ref<HTMLInputElement>()
const reset = (input: HTMLInputElement) => {
const reset = () => {
loadingFiles.value = []
const input = fileInput.value
if (!input) return
input.value = ''
input.files = null
}
const onFileChanged = async ($event: Event) => {
const input = $event.target as HTMLInputElement
const { files } = input
const loadFiles = async (files: FileList | File[]) => {
loadingFiles.value = Array.from(files || []).map((file) => ({
name: file.name,
size: file.size,
Expand All @@ -76,7 +75,7 @@ const onFileChanged = async ($event: Event) => {
const uploadedFiles = data?.formUploadCacheAdd?.uploadedFiles
if (!uploadedFiles) {
reset(input)
reset()
return
}
Expand All @@ -86,7 +85,17 @@ const onFileChanged = async ($event: Event) => {
}))
uploadFiles.value = [...uploadFiles.value, ...previewableFile]
reset(input)
reset()
}
Object.assign(props.context, {
uploadFiles: loadFiles,
})
const onFileChanged = async ($event: Event) => {
const input = $event.target as HTMLInputElement
const { files } = input
if (files) await loadFiles(files)
}
const { waitForConfirmation } = useConfirmationDialog()
Expand Down
4 changes: 4 additions & 0 deletions app/frontend/shared/components/Form/fields/FieldFile/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,7 @@ export type FileUploaded = Pick<StoredFile, 'name' | 'size' | 'type'> & {
content?: string
preview?: string
}

export interface FieldFileContext {
uploadFiles(files: FileList | File[]): Promise<void>
}

0 comments on commit d813613

Please sign in to comment.