에디터 슬래시 상태와 단축키 판별 분리
This commit is contained in:
@@ -21,6 +21,11 @@ import {
|
||||
getEditorHistoryEntrySelection,
|
||||
getEditorHistoryEntryValue
|
||||
} from '../../lib/markdown-editor-history.js'
|
||||
import {
|
||||
getEditorHistoryKeyboardAction,
|
||||
isEditorModeToggleShortcut,
|
||||
isMediaPickerCloseShortcut
|
||||
} from '../../lib/markdown-editor-keyboard.js'
|
||||
import {
|
||||
buildMediaPickerApplyState,
|
||||
buildMediaPickerEmptyMessage,
|
||||
@@ -44,9 +49,6 @@ import {
|
||||
toggleMediaSelectionState
|
||||
} from '../../lib/markdown-editor-media.js'
|
||||
import {
|
||||
addSlashSuppressedLine,
|
||||
clampSlashHighlightedIndex,
|
||||
filterSlashCommands,
|
||||
getMediaPickerKindFromSlashAction,
|
||||
getNextSlashHighlightedIndex,
|
||||
getSlashCommandFocusTarget,
|
||||
@@ -55,7 +57,6 @@ import {
|
||||
insertMarkdownLinesAtSlashTarget,
|
||||
isMediaSlashAction,
|
||||
parseSlashInput,
|
||||
removeSlashSuppressedLine,
|
||||
resolveSlashCommand,
|
||||
shouldIgnorePendingMediaSlashCommit
|
||||
} from '../../lib/markdown-slash-commands.js'
|
||||
@@ -69,6 +70,7 @@ import { buildCalloutOpenerLine, CALLOUT_BACKGROUND_OPTIONS, QUOTE_BACKGROUND_OP
|
||||
import { buildCodeFenceOpener } from '../../lib/markdown-code-block.js'
|
||||
import { buildToggleOpenerLine } from '../../lib/markdown-toggle.js'
|
||||
import { getTextareaCaretCoordinates } from '../../lib/textarea-caret-coordinates.js'
|
||||
import { useAdminMarkdownSlashMenu } from '../../composables/useAdminMarkdownSlashMenu.js'
|
||||
import {
|
||||
buildDefaultUploadSizeLimits,
|
||||
DEFAULT_UPLOAD_MAX_FILE_COUNT,
|
||||
@@ -147,44 +149,32 @@ const lastSelectionState = ref({
|
||||
start: 0,
|
||||
end: 0
|
||||
})
|
||||
/** @type {import('vue').Ref<number|null>} 슬래시 명령이 입력된 줄 */
|
||||
const liveSlashSourceLine = ref(null)
|
||||
/** @type {import('vue').Ref<string>} 슬래시 검색어 */
|
||||
const liveSlashQuery = ref('')
|
||||
/** @type {import('vue').Ref<number>} 슬래시 메뉴 하이라이트 인덱스 */
|
||||
const liveSlashHighlightedIndex = ref(0)
|
||||
/** @type {import('vue').Ref<{ top: number, left: number }>} 슬래시 메뉴 위치 */
|
||||
const liveSlashMenuPosition = ref({ top: 0, left: 0 })
|
||||
/** @type {import('vue').Ref<HTMLElement|null>} 슬래시 메뉴 DOM */
|
||||
const liveSlashMenuRef = ref(null)
|
||||
|
||||
/** @type {import('vue').Ref<number|null>} 슬래시 후 미디어 삽입 줄 */
|
||||
const liveSlashInsertLine = ref(null)
|
||||
/** @type {import('vue').Ref<Set<number>>} ESC로 슬래시 메뉴만 닫은 줄(0-based) */
|
||||
const liveSlashSuppressedLines = ref(new Set())
|
||||
/** 키보드로 슬래시 메뉴를 탐색 중일 때 mouseenter 하이라이트 무시 */
|
||||
const liveSlashKeyboardNav = ref(false)
|
||||
let liveSlashKeyboardNavTimer = null
|
||||
const {
|
||||
liveSlashSourceLine,
|
||||
liveSlashQuery,
|
||||
liveSlashHighlightedIndex,
|
||||
liveSlashMenuPosition,
|
||||
liveSlashMenuRef,
|
||||
liveSlashInsertLine,
|
||||
liveSlashKeyboardNav,
|
||||
liveSlashVisible,
|
||||
liveSlashSuppressedLineList,
|
||||
visibleLiveSlashCommands,
|
||||
clearLiveSlashState,
|
||||
openLiveSlashMenu,
|
||||
isLiveSlashLineSuppressed,
|
||||
suppressLiveSlashLine,
|
||||
releaseLiveSlashLine,
|
||||
resetLiveSlashHighlightedIndex,
|
||||
clampLiveSlashHighlightedIndex,
|
||||
startLiveSlashKeyboardNavigation,
|
||||
stopLiveSlashKeyboardNavigation
|
||||
} = useAdminMarkdownSlashMenu()
|
||||
/** @type {HTMLElement|null} 슬래시 메뉴 위치 갱신을 위해 구독한 미리보기 스크롤 영역 */
|
||||
let liveSlashPreviewScrollElement = null
|
||||
/** @type {HTMLElement|null} 슬래시 메뉴 위치 갱신을 위해 구독한 작성 textarea */
|
||||
let liveSlashTextareaScrollElement = null
|
||||
|
||||
const liveSlashVisible = computed(() => liveSlashSourceLine.value !== null)
|
||||
|
||||
const liveSlashSuppressedLineList = computed(() => [...liveSlashSuppressedLines.value])
|
||||
|
||||
const visibleLiveSlashCommands = computed(() => filterSlashCommands(liveSlashQuery.value))
|
||||
|
||||
/**
|
||||
* 슬래시 메뉴 입력 상태를 초기화한다.
|
||||
* @returns {void}
|
||||
*/
|
||||
const clearLiveSlashState = () => {
|
||||
liveSlashSourceLine.value = null
|
||||
liveSlashQuery.value = ''
|
||||
liveSlashHighlightedIndex.value = 0
|
||||
}
|
||||
/** 되돌리기 히스토리 최대 보관 개수 */
|
||||
const EDITOR_UNDO_LIMIT = 100
|
||||
/** @type {import('vue').Ref<Array<string|{ value: string, selectionStart: number, selectionEnd: number }>>} 본문 되돌리기 스택 */
|
||||
@@ -1054,7 +1044,7 @@ const syncWriteSlashState = () => {
|
||||
|
||||
const line = activeLogicalLineIndex.value
|
||||
|
||||
if (liveSlashSuppressedLines.value.has(line)) {
|
||||
if (isLiveSlashLineSuppressed(line)) {
|
||||
if (liveSlashSourceLine.value !== null) {
|
||||
clearLiveSlashState()
|
||||
}
|
||||
@@ -1076,8 +1066,7 @@ const syncWriteSlashState = () => {
|
||||
return
|
||||
}
|
||||
|
||||
liveSlashSourceLine.value = line
|
||||
liveSlashQuery.value = parsed.query
|
||||
openLiveSlashMenu(line, parsed.query)
|
||||
nextTick(updateLiveSlashMenuPosition)
|
||||
}
|
||||
|
||||
@@ -1423,7 +1412,7 @@ onMounted(() => {
|
||||
* @returns {void}
|
||||
*/
|
||||
const onDocumentKeydown = (event) => {
|
||||
if (event.key === 'Escape' && isMediaPickerOpen.value) {
|
||||
if (isMediaPickerCloseShortcut(event) && isMediaPickerOpen.value) {
|
||||
event.preventDefault()
|
||||
closeMediaPicker()
|
||||
return
|
||||
@@ -1435,19 +1424,12 @@ onMounted(() => {
|
||||
return
|
||||
}
|
||||
|
||||
const hasCommandModifier = event.metaKey || event.ctrlKey
|
||||
const isUndoShortcut = hasCommandModifier
|
||||
&& !event.altKey
|
||||
&& !event.shiftKey
|
||||
&& event.key.toLowerCase() === 'z'
|
||||
const isRedoShortcut = hasCommandModifier
|
||||
&& !event.altKey
|
||||
&& (event.shiftKey && event.key.toLowerCase() === 'z' || event.key.toLowerCase() === 'y')
|
||||
const historyKeyboardAction = getEditorHistoryKeyboardAction(event)
|
||||
|
||||
if (isUndoShortcut || isRedoShortcut) {
|
||||
if (historyKeyboardAction) {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
restoreEditorHistory(isUndoShortcut ? 'undo' : 'redo')
|
||||
restoreEditorHistory(historyKeyboardAction)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1459,11 +1441,7 @@ onMounted(() => {
|
||||
const count = visibleLiveSlashCommands.value.length
|
||||
|
||||
if (count) {
|
||||
liveSlashKeyboardNav.value = true
|
||||
window.clearTimeout(liveSlashKeyboardNavTimer)
|
||||
liveSlashKeyboardNavTimer = window.setTimeout(() => {
|
||||
liveSlashKeyboardNav.value = false
|
||||
}, 200)
|
||||
startLiveSlashKeyboardNavigation()
|
||||
|
||||
liveSlashHighlightedIndex.value = getNextSlashHighlightedIndex({
|
||||
currentIndex: liveSlashHighlightedIndex.value,
|
||||
@@ -1493,7 +1471,7 @@ onMounted(() => {
|
||||
}
|
||||
}
|
||||
|
||||
if (!(event.metaKey || event.ctrlKey) || event.key.toLowerCase() !== 'e') {
|
||||
if (!isEditorModeToggleShortcut(event)) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1533,7 +1511,7 @@ onMounted(() => {
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.clearTimeout(blockPanelFocusTimer)
|
||||
window.clearTimeout(liveSlashKeyboardNavTimer)
|
||||
stopLiveSlashKeyboardNavigation()
|
||||
removeLiveSlashRepositionListeners()
|
||||
document.removeEventListener('selectionchange', onSelectionChange)
|
||||
document.removeEventListener('keydown', onDocumentKeydown, true)
|
||||
@@ -2547,7 +2525,7 @@ const onLiveSlashUpdate = (payload) => {
|
||||
return
|
||||
}
|
||||
|
||||
if (liveSlashSuppressedLines.value.has(payload.sourceLine)) {
|
||||
if (isLiveSlashLineSuppressed(payload.sourceLine)) {
|
||||
if (liveSlashSourceLine.value !== null) {
|
||||
clearLiveSlashState()
|
||||
}
|
||||
@@ -2562,17 +2540,16 @@ const onLiveSlashUpdate = (payload) => {
|
||||
return
|
||||
}
|
||||
|
||||
liveSlashSourceLine.value = payload.sourceLine
|
||||
liveSlashQuery.value = String(payload.query ?? '')
|
||||
openLiveSlashMenu(payload.sourceLine, payload.query)
|
||||
nextTick(updateLiveSlashMenuPosition)
|
||||
}
|
||||
|
||||
watch(liveSlashQuery, () => {
|
||||
liveSlashHighlightedIndex.value = 0
|
||||
resetLiveSlashHighlightedIndex()
|
||||
})
|
||||
|
||||
watch(visibleLiveSlashCommands, (commands) => {
|
||||
liveSlashHighlightedIndex.value = clampSlashHighlightedIndex(liveSlashHighlightedIndex.value, commands.length)
|
||||
clampLiveSlashHighlightedIndex(commands.length)
|
||||
|
||||
updateLiveSlashMenuPosition()
|
||||
})
|
||||
@@ -2602,7 +2579,7 @@ watch(liveSlashVisible, (visible) => {
|
||||
*/
|
||||
const onLiveSlashEnd = (payload) => {
|
||||
if (typeof payload?.sourceLine === 'number') {
|
||||
liveSlashSuppressedLines.value = removeSlashSuppressedLine(liveSlashSuppressedLines.value, payload.sourceLine)
|
||||
releaseLiveSlashLine(payload.sourceLine)
|
||||
}
|
||||
|
||||
clearLiveSlashState()
|
||||
@@ -2616,7 +2593,7 @@ const cancelLiveSlashCommand = () => {
|
||||
const line = liveSlashSourceLine.value
|
||||
|
||||
if (line !== null) {
|
||||
liveSlashSuppressedLines.value = addSlashSuppressedLine(liveSlashSuppressedLines.value, line)
|
||||
suppressLiveSlashLine(line)
|
||||
}
|
||||
|
||||
clearLiveSlashState()
|
||||
@@ -2689,7 +2666,7 @@ const applyLiveSlashCommand = (command) => {
|
||||
|
||||
if (command.action === 'lines' && Array.isArray(command.lines)) {
|
||||
const replacementLines = command.lines
|
||||
liveSlashSuppressedLines.value = removeSlashSuppressedLine(liveSlashSuppressedLines.value, line)
|
||||
releaseLiveSlashLine(line)
|
||||
replaceLineRange(line, line, replacementLines, false)
|
||||
clearLiveSlashState()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user