yikoyu / vuetify-pro-tiptap

A Rich Text Editor (WYSIWYG) for Vue3 with tiptap & Vuetify.
https://yikoyu.github.io/vuetify-pro-tiptap/
MIT License
170 stars 23 forks source link

support uploading multiple image/files within current image extension #381

Open pangjianxin opened 4 days ago

pangjianxin commented 4 days ago

thanks for this awesome project . i want to upload multiple images at once-opening the image extension,how can i do this? i noticed the upload method can only accept File parameter rather than File[],can we add a overwriting method to do this?

yikoyu commented 3 days ago

You can use the dialogComponent.

ImageDialog.vue

<script setup lang="ts">
import { computed, ref, unref, watch } from 'vue'
import type { Editor } from '@tiptap/vue-3'

interface Props {
  value?: Record<string, any>
  editor: Editor
  destroy?: () => void
}

const props = withDefaults(defineProps<Props>(), {
  value: () => ({}),
  upload: undefined,
  imageTabs: () => [],
  hiddenTabs: () => [],
  destroy: undefined
})

const dialog = ref<boolean>(false)

const form = ref<Record<string, any>>({})

const disabledApply = computed<boolean>(() => {
  const { src } = unref(form)

  if (Array.isArray(src) && src.length > 0) {
    return false
  }

  if (typeof src === 'string' && src.length > 0) {
    return false
  }

  return true
})

async function apply() {
  const { src, lockAspectRatio, height } = unref(form)
  if (disabledApply.value) return

  console.log('apply :>> ', src)

  if (typeof src === 'string') {
    props.editor
      .chain()
      .focus()
      .setImage({
        ...unref(form),
        src,
        height: lockAspectRatio ? undefined : height
      })
      .run()

    close()
  }

  if (Array.isArray(src)) {
    props.editor
      .chain()
      .focus()
      .insertContent(src.map(k => ({
        type: 'image',
        attrs: {
          src: k
        }
      })))
      // .setImage({
      //   ...unref(form),
      //   src: item,
      //   height: lockAspectRatio ? undefined : height
      // })
      .run()

    close()
  }
}

async function onFileSelected(event: { isTrusted: boolean }) {
  const { file } = unref(form)
  if (!file || !event.isTrusted) return

  // Here is the upload method.
  const data = file.map((k: File) => URL.createObjectURL(k))
  if (!data) return

  form.value = {
    ...unref(form),
    src: data
  }
}

function close() {
  dialog.value = false
  form.value = {}

  setTimeout(() => props.destroy?.(), 300)
}

watch(
  () => props.value,
  val => {
    form.value = {
      ...unref(form),
      ...val
    }
  },
  { immediate: true, deep: true }
)
</script>

<template>
  <VDialog v-model="dialog" max-width="400" activator="parent" @click:outside="close">
    <VCard>
      <VToolbar class="px-6" density="compact">
        <span class="headline">Add Image</span>

        <VSpacer />

        <VBtn class="mx-0" icon @click="close">
          <VIcon icon="$close" />
        </VBtn>
      </VToolbar>

      <VFileInput
        v-model="form.file"
        label="Files"
        accept="image/*"
        multiple
        @change="onFileSelected"
        @click:clear="form.src = undefined"
      />

      <VCardActions>
        <VBtn :disabled="disabledApply" @click="apply">
          Apply
        </VBtn>
      </VCardActions>
    </VCard>
  </VDialog>
</template>
Image.configure({
  dialogComponent: () => ImageDialog
})