- 이미지 버튼: 파일 선택 → imageToBlob(1280px·JPEG0.85)로 압축 →  삽입 - 웹 에디터처럼 본문에 임베드(별도 서버 저장 없음), 단 압축으로 용량 절감 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -6,6 +6,7 @@ import codeSyntaxHighlight from '@toast-ui/editor-plugin-code-syntax-highlight/d
|
|||||||
import 'prismjs/themes/prism-tomorrow.css'
|
import 'prismjs/themes/prism-tomorrow.css'
|
||||||
import '@toast-ui/editor-plugin-code-syntax-highlight/dist/toastui-editor-plugin-code-syntax-highlight.css'
|
import '@toast-ui/editor-plugin-code-syntax-highlight/dist/toastui-editor-plugin-code-syntax-highlight.css'
|
||||||
import MarkdownViewer from '@/components/editor/MarkdownViewer.vue'
|
import MarkdownViewer from '@/components/editor/MarkdownViewer.vue'
|
||||||
|
import { imageToBlob } from '@/utils/receiptOcr'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
modelValue: { type: String, default: '' },
|
modelValue: { type: String, default: '' },
|
||||||
@@ -103,6 +104,46 @@ function insertLink() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 이미지: 파일 선택 → 리사이즈/압축(JPEG) → base64 임베드 (웹 에디터와 동일하게 본문에 포함, 별도 서버 저장 없음)
|
||||||
|
const imgInput = ref(null)
|
||||||
|
const imgBusy = ref(false)
|
||||||
|
function pickImage() {
|
||||||
|
imgInput.value?.click()
|
||||||
|
}
|
||||||
|
function blobToDataUrl(blob) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const r = new FileReader()
|
||||||
|
r.onload = () => resolve(r.result)
|
||||||
|
r.onerror = reject
|
||||||
|
r.readAsDataURL(blob)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
async function onImagePick(e) {
|
||||||
|
const file = e.target.files?.[0]
|
||||||
|
e.target.value = ''
|
||||||
|
if (!file) return
|
||||||
|
imgBusy.value = true
|
||||||
|
try {
|
||||||
|
const blob = await imageToBlob(file, 1280) // 긴 변 1280px, JPEG 0.85 압축
|
||||||
|
const dataUrl = await blobToDataUrl(blob)
|
||||||
|
const ta = taRef.value
|
||||||
|
const s = ta ? ta.selectionStart : text.value.length
|
||||||
|
const alt = file.name.replace(/\.[^.]+$/, '')
|
||||||
|
const md = ``
|
||||||
|
text.value = text.value.slice(0, s) + md + text.value.slice(s)
|
||||||
|
nextTick(() => {
|
||||||
|
if (!ta) return
|
||||||
|
ta.focus()
|
||||||
|
const pos = s + md.length
|
||||||
|
ta.setSelectionRange(pos, pos)
|
||||||
|
})
|
||||||
|
} catch {
|
||||||
|
/* 이미지 처리 실패 무시 */
|
||||||
|
} finally {
|
||||||
|
imgBusy.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* ===================== 웹/APK: Toast UI 위지윅 ===================== */
|
/* ===================== 웹/APK: Toast UI 위지윅 ===================== */
|
||||||
const el = ref(null)
|
const el = ref(null)
|
||||||
let editor = null
|
let editor = null
|
||||||
@@ -169,9 +210,11 @@ onBeforeUnmount(() => {
|
|||||||
<button type="button" title="체크리스트" @click="linePrefix('- [ ] ')">☑</button>
|
<button type="button" title="체크리스트" @click="linePrefix('- [ ] ')">☑</button>
|
||||||
<span class="mde-div"></span>
|
<span class="mde-div"></span>
|
||||||
<button type="button" title="링크" @click="insertLink()">🔗</button>
|
<button type="button" title="링크" @click="insertLink()">🔗</button>
|
||||||
|
<button type="button" :title="imgBusy ? '이미지 처리 중…' : '이미지'" :disabled="imgBusy" @click="pickImage()">🖼</button>
|
||||||
<button type="button" title="인라인 코드" @click="surround('`', '`', '코드')"></></button>
|
<button type="button" title="인라인 코드" @click="surround('`', '`', '코드')"></></button>
|
||||||
<button type="button" title="코드 블록" @click="insertBlock('```\n코드\n```')">{ }</button>
|
<button type="button" title="코드 블록" @click="insertBlock('```\n코드\n```')">{ }</button>
|
||||||
<button type="button" title="구분선" @click="insertBlock('---')">―</button>
|
<button type="button" title="구분선" @click="insertBlock('---')">―</button>
|
||||||
|
<input ref="imgInput" type="file" accept="image/*" hidden @change="onImagePick" />
|
||||||
</div>
|
</div>
|
||||||
<textarea
|
<textarea
|
||||||
ref="taRef"
|
ref="taRef"
|
||||||
|
|||||||
Reference in New Issue
Block a user