fix: PC(Electron) 위지윅 한글 IME 중복 → 에디터를 마크다운 모드로 전환
CI / build (push) Failing after 12m48s

- 진단: 제목(일반 입력)은 정상, 본문 위지윅(contenteditable)만 첫 자음 중복 → ProseMirror+Electron IME 버그
- MarkdownEditor: Electron 감지 시 initialEditType='markdown'(텍스트 기반 입력) → IME 정상. 웹/APK는 위지윅 유지
- 앞서의 불필요한 조합(composition) 처리 코드 제거(원인이 아니었음)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
ByungCheol
2026-06-07 17:50:43 +09:00
parent 561c56a8e0
commit 7f6a4c62e5
+12 -31
View File
@@ -14,32 +14,22 @@ const props = defineProps({
}) })
const emit = defineEmits(['update:modelValue']) const emit = defineEmits(['update:modelValue'])
// PC(Electron)는 위지윅(contenteditable)에서 한글 IME 첫 자음이 중복되는 Chromium 버그가 있어
// 마크다운(텍스트 기반) 모드로 입력받는다. 웹/모바일(APK)은 위지윅 유지.
const isElectron = typeof navigator !== 'undefined' && /electron/i.test(navigator.userAgent)
const editType = isElectron ? 'markdown' : props.initialEditType
const el = ref(null) const el = ref(null)
let editor = null let editor = null
// 에디터가 마지막으로 emit 한 값. 부모가 되돌려준 echo 를 구분해 한글 조합(IME)이 깨지지 않게 한다. // 에디터가 마지막으로 emit 한 값. 부모가 되돌려준 echo 를 구분해 입력 중 setMarkdown 으로 끊기지 않게.
let lastEmitted = props.modelValue || '' let lastEmitted = props.modelValue || ''
// IME(한글 등) 조합 중 여부. 조합 중에는 getMarkdown/emit 을 보류해 첫 자음이 중복되는 문제를 막는다.
let composing = false
function emitValue() {
if (!editor) return
lastEmitted = editor.getMarkdown()
emit('update:modelValue', lastEmitted)
}
function onCompStart() {
composing = true
}
function onCompEnd() {
composing = false
emitValue() // 조합 완료 시 한 번만 반영
}
onMounted(() => { onMounted(() => {
editor = new Editor({ editor = new Editor({
el: el.value, el: el.value,
height: props.height, height: props.height,
initialEditType: props.initialEditType, initialEditType: editType,
hideModeSwitch: true, // 마크다운/위지윅 전환 탭 숨김 (WYSIWYG 전용) hideModeSwitch: true,
plugins: [codeSyntaxHighlight], // 코드 구문 강조 plugins: [codeSyntaxHighlight], // 코드 구문 강조
initialValue: props.modelValue || '', initialValue: props.modelValue || '',
placeholder: props.placeholder, placeholder: props.placeholder,
@@ -47,24 +37,19 @@ onMounted(() => {
autofocus: false, autofocus: false,
events: { events: {
change: () => { change: () => {
// 조합 중에는 보류 — 조합 중 getMarkdown 호출이 IME 를 끊어 자음 중복을 유발한다. lastEmitted = editor.getMarkdown()
if (composing) return emit('update:modelValue', lastEmitted)
emitValue()
}, },
}, },
}) })
// 조합 이벤트는 contenteditable 에서 버블링되어 래퍼(el)에서 잡힌다.
el.value.addEventListener('compositionstart', onCompStart)
el.value.addEventListener('compositionend', onCompEnd)
}) })
// 외부 값 변경(수정 로드, 제출 후 초기화 등)만 에디터에 반영. // 외부 값 변경(수정 로드, 제출 후 초기화 등)만 에디터에 반영. echo 는 무시.
// 에디터 자신이 방금 emit 한 값(echo)이거나 조합 중이면 무시 → 입력 중 setMarkdown 으로 IME 조합이 끊기지 않게 한다.
watch( watch(
() => props.modelValue, () => props.modelValue,
(val) => { (val) => {
const v = val || '' const v = val || ''
if (composing || v === lastEmitted) return if (v === lastEmitted) return
if (editor && v !== editor.getMarkdown()) { if (editor && v !== editor.getMarkdown()) {
lastEmitted = v lastEmitted = v
editor.setMarkdown(v) editor.setMarkdown(v)
@@ -73,10 +58,6 @@ watch(
) )
onBeforeUnmount(() => { onBeforeUnmount(() => {
if (el.value) {
el.value.removeEventListener('compositionstart', onCompStart)
el.value.removeEventListener('compositionend', onCompEnd)
}
editor?.destroy() editor?.destroy()
editor = null editor = null
}) })