Accept image pasting functionality (#151)
* Accept image pasting functionality * Bump to 0.7.1 * Default alt text should be like github * sync modelValue changes to codemirror state * Refactor image uploading in paste * Fix error handling in image upload
This commit is contained in:
parent
cbcee037a7
commit
f6eff090e7
@ -263,7 +263,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { type Component, computed, ref, onMounted, onBeforeUnmount } from 'vue'
|
import { type Component, computed, ref, onMounted, onBeforeUnmount, toRef, watch } from 'vue'
|
||||||
|
|
||||||
import { EditorState } from '@codemirror/state'
|
import { EditorState } from '@codemirror/state'
|
||||||
import { EditorView, keymap, placeholder as cm_placeholder } from '@codemirror/view'
|
import { EditorView, keymap, placeholder as cm_placeholder } from '@codemirror/view'
|
||||||
@ -296,6 +296,7 @@ import {
|
|||||||
InfoIcon,
|
InfoIcon,
|
||||||
Chips,
|
Chips,
|
||||||
} from '@/components'
|
} from '@/components'
|
||||||
|
|
||||||
import { markdownCommands, modrinthMarkdownEditorKeymap } from '@/helpers/codemirror'
|
import { markdownCommands, modrinthMarkdownEditorKeymap } from '@/helpers/codemirror'
|
||||||
import { renderHighlightedString } from '@/helpers/highlight'
|
import { renderHighlightedString } from '@/helpers/highlight'
|
||||||
|
|
||||||
@ -332,8 +333,7 @@ const emit = defineEmits(['update:modelValue'])
|
|||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
const updateListener = EditorView.updateListener.of((update) => {
|
const updateListener = EditorView.updateListener.of((update) => {
|
||||||
if (update.docChanged) {
|
if (update.docChanged) {
|
||||||
currentValue.value = update.state.doc.toString()
|
updateCurrentValue(update.state.doc.toString())
|
||||||
emit('update:modelValue', currentValue.value)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -357,8 +357,30 @@ onMounted(() => {
|
|||||||
|
|
||||||
const eventHandlers = EditorView.domEventHandlers({
|
const eventHandlers = EditorView.domEventHandlers({
|
||||||
paste: (ev, view) => {
|
paste: (ev, view) => {
|
||||||
|
const { clipboardData } = ev
|
||||||
|
if (!clipboardData) return
|
||||||
|
|
||||||
|
if (clipboardData.files && clipboardData.files.length > 0 && props.onImageUpload) {
|
||||||
|
// If the user is pasting a file, upload it if there's an included handler and insert the link.
|
||||||
|
uploadImagesFromList(clipboardData.files)
|
||||||
|
.then(function (url) {
|
||||||
|
const selection = markdownCommands.yankSelection(view)
|
||||||
|
const altText = selection || 'Replace this with a description'
|
||||||
|
const linkMarkdown = ``
|
||||||
|
return markdownCommands.replaceSelection(view, linkMarkdown)
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
if (error instanceof Error) {
|
||||||
|
console.error('Problem with handling image.', error)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// If the user's pasting a url, automatically convert it to a link with the selection as the text or the url itself if no selection content.
|
// If the user's pasting a url, automatically convert it to a link with the selection as the text or the url itself if no selection content.
|
||||||
const url = ev.clipboardData?.getData('text/plain')
|
const url = ev.clipboardData?.getData('text/plain')
|
||||||
|
|
||||||
if (url) {
|
if (url) {
|
||||||
try {
|
try {
|
||||||
cleanUrl(url)
|
cleanUrl(url)
|
||||||
@ -374,6 +396,7 @@ onMounted(() => {
|
|||||||
const linkMarkdown = `[${linkText}](${url})`
|
const linkMarkdown = `[${linkText}](${url})`
|
||||||
return markdownCommands.replaceSelection(view, linkMarkdown)
|
return markdownCommands.replaceSelection(view, linkMarkdown)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the length of the document is greater than the max length. If it is, prevent the paste.
|
// Check if the length of the document is greater than the max length. If it is, prevent the paste.
|
||||||
if (props.maxLength && view.state.doc.length > props.maxLength) {
|
if (props.maxLength && view.state.doc.length > props.maxLength) {
|
||||||
ev.preventDefault()
|
ev.preventDefault()
|
||||||
@ -401,7 +424,6 @@ onMounted(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const editorState = EditorState.create({
|
const editorState = EditorState.create({
|
||||||
doc: props.modelValue,
|
|
||||||
extensions: [
|
extensions: [
|
||||||
theme,
|
theme,
|
||||||
eventHandlers,
|
eventHandlers,
|
||||||
@ -519,7 +541,23 @@ const BUTTONS: ButtonGroupMap = {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentValue = ref(props.modelValue)
|
const currentValue = toRef(props, 'modelValue')
|
||||||
|
watch(currentValue, (newValue) => {
|
||||||
|
if (editor) {
|
||||||
|
editor.dispatch({
|
||||||
|
changes: {
|
||||||
|
from: 0,
|
||||||
|
to: editor.state.doc.length,
|
||||||
|
insert: newValue,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const updateCurrentValue = (newValue: string) => {
|
||||||
|
emit('update:modelValue', newValue)
|
||||||
|
}
|
||||||
|
|
||||||
const previewMode = ref(false)
|
const previewMode = ref(false)
|
||||||
|
|
||||||
const linkText = ref('')
|
const linkText = ref('')
|
||||||
@ -586,20 +624,36 @@ const linkMarkdown = computed(() => {
|
|||||||
return ''
|
return ''
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const uploadImagesFromList = async (files: FileList): Promise<string> => {
|
||||||
|
const file = files[0]
|
||||||
|
if (!props.onImageUpload) {
|
||||||
|
throw new Error('No image upload handler provided')
|
||||||
|
}
|
||||||
|
if (file) {
|
||||||
|
try {
|
||||||
|
const url = await props.onImageUpload(file)
|
||||||
|
return url
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof Error) {
|
||||||
|
console.error('Unable to upload image using handler.', error.message)
|
||||||
|
throw new Error(error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new Error('No file provided')
|
||||||
|
}
|
||||||
|
|
||||||
const handleImageUpload = async (files: FileList) => {
|
const handleImageUpload = async (files: FileList) => {
|
||||||
if (props.onImageUpload) {
|
if (props.onImageUpload) {
|
||||||
const file = files[0]
|
try {
|
||||||
if (file) {
|
const uploadedURL = await uploadImagesFromList(files)
|
||||||
try {
|
linkUrl.value = uploadedURL
|
||||||
const url = await props.onImageUpload(file)
|
validateURL()
|
||||||
linkUrl.value = url
|
} catch (error) {
|
||||||
validateURL()
|
if (error instanceof Error) {
|
||||||
} catch (error) {
|
linkValidationErrorMessage.value = error.message
|
||||||
if (error instanceof Error) {
|
|
||||||
linkValidationErrorMessage.value = error.message
|
|
||||||
}
|
|
||||||
console.error(error)
|
|
||||||
}
|
}
|
||||||
|
console.error(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "omorphia",
|
"name": "omorphia",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"version": "0.7.0",
|
"version": "0.7.1",
|
||||||
"files": [
|
"files": [
|
||||||
"dist",
|
"dist",
|
||||||
"locales"
|
"locales"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user