Merge branch 'dev'
This commit is contained in:
@@ -17,6 +17,7 @@ const props = defineProps({
|
|||||||
const emit = defineEmits(['update:modelValue'])
|
const emit = defineEmits(['update:modelValue'])
|
||||||
|
|
||||||
const el = ref(null)
|
const el = ref(null)
|
||||||
|
const imgInput = ref(null)
|
||||||
let editor = null
|
let editor = null
|
||||||
// 에디터가 마지막으로 emit 한 값. 부모가 되돌려준 echo 를 구분해 입력 중 setMarkdown 으로 끊기지 않게.
|
// 에디터가 마지막으로 emit 한 값. 부모가 되돌려준 echo 를 구분해 입력 중 setMarkdown 으로 끊기지 않게.
|
||||||
let lastEmitted = props.modelValue || ''
|
let lastEmitted = props.modelValue || ''
|
||||||
@@ -28,6 +29,34 @@ async function uploadCompressed(fileOrBlob) {
|
|||||||
return url
|
return url
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 커스텀 이미지 버튼 클릭 → 파일 선택 → 업로드 → 에디터에 삽입
|
||||||
|
// (Toast UI 기본 이미지 팝업이 작은 댓글창에서 잘려 OK 를 못 누르는 문제 회피)
|
||||||
|
async function onImagePick(e) {
|
||||||
|
const file = e.target.files?.[0]
|
||||||
|
e.target.value = ''
|
||||||
|
if (!file || !editor) return
|
||||||
|
try {
|
||||||
|
const url = await uploadCompressed(file)
|
||||||
|
const alt = file.name.replace(/\.[^.]+$/, '')
|
||||||
|
editor.exec('addImage', { imageUrl: url, altText: alt })
|
||||||
|
} catch {
|
||||||
|
/* 업로드 실패 시 미삽입 */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeImageButton() {
|
||||||
|
const btn = document.createElement('button')
|
||||||
|
btn.type = 'button'
|
||||||
|
btn.className = 'toastui-editor-toolbar-icons image'
|
||||||
|
btn.style.margin = '0'
|
||||||
|
btn.setAttribute('aria-label', '이미지')
|
||||||
|
btn.addEventListener('click', (ev) => {
|
||||||
|
ev.preventDefault()
|
||||||
|
imgInput.value?.click()
|
||||||
|
})
|
||||||
|
return btn
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
editor = new Editor({
|
editor = new Editor({
|
||||||
el: el.value,
|
el: el.value,
|
||||||
@@ -39,24 +68,20 @@ onMounted(() => {
|
|||||||
placeholder: props.placeholder,
|
placeholder: props.placeholder,
|
||||||
usageStatistics: false,
|
usageStatistics: false,
|
||||||
autofocus: false,
|
autofocus: false,
|
||||||
|
// 기본 이미지 버튼(팝업)을 커스텀(바로 업로드)으로 교체
|
||||||
|
toolbarItems: [
|
||||||
|
['heading', 'bold', 'italic', 'strike'],
|
||||||
|
['hr', 'quote'],
|
||||||
|
['ul', 'ol', 'task', 'indent', 'outdent'],
|
||||||
|
['table', { name: 'image', tooltip: '이미지', el: makeImageButton() }, 'link'],
|
||||||
|
['code', 'codeblock'],
|
||||||
|
],
|
||||||
events: {
|
events: {
|
||||||
change: () => {
|
change: () => {
|
||||||
lastEmitted = editor.getMarkdown()
|
lastEmitted = editor.getMarkdown()
|
||||||
emit('update:modelValue', lastEmitted)
|
emit('update:modelValue', lastEmitted)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
hooks: {
|
|
||||||
// 이미지 추가 시 base64 대신 서버 업로드 → URL 삽입
|
|
||||||
addImageBlobHook: async (blob, callback) => {
|
|
||||||
try {
|
|
||||||
const url = await uploadCompressed(blob)
|
|
||||||
callback(url, '')
|
|
||||||
} catch {
|
|
||||||
/* 업로드 실패 시 미삽입 */
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -80,5 +105,8 @@ onBeforeUnmount(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
<div>
|
||||||
<div ref="el"></div>
|
<div ref="el"></div>
|
||||||
|
<input ref="imgInput" type="file" accept="image/*" hidden @change="onImagePick" />
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
Reference in New Issue
Block a user