글쓰기 되돌리기와 미디어 관리 보정
This commit is contained in:
@@ -114,9 +114,9 @@ const liveSlashSuppressedLineList = computed(() => [...liveSlashSuppressedLines.
|
||||
const visibleLiveSlashCommands = computed(() => filterSlashCommands(liveSlashQuery.value))
|
||||
/** 되돌리기 히스토리 최대 보관 개수 */
|
||||
const EDITOR_UNDO_LIMIT = 100
|
||||
/** @type {import('vue').Ref<string[]>} 본문 되돌리기 스택 */
|
||||
/** @type {import('vue').Ref<Array<string|{ value: string, selectionStart: number, selectionEnd: number }>>} 본문 되돌리기 스택 */
|
||||
const editorUndoStack = ref([])
|
||||
/** @type {import('vue').Ref<string[]>} 본문 다시 실행 스택 */
|
||||
/** @type {import('vue').Ref<Array<string|{ value: string, selectionStart: number, selectionEnd: number }>>} 본문 다시 실행 스택 */
|
||||
const editorRedoStack = ref([])
|
||||
/** 히스토리 복원 중 변경 기록을 막는다. */
|
||||
const isRestoringEditorHistory = ref(false)
|
||||
@@ -588,20 +588,89 @@ const isCodeFenceBodyLine = (lines, lineIndex) => {
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* 에디터 히스토리 항목을 만든다.
|
||||
* @param {string} value - 저장할 본문
|
||||
* @returns {{ value: string, selectionStart: number, selectionEnd: number }} 히스토리 항목
|
||||
*/
|
||||
const createEditorHistoryEntry = (value) => {
|
||||
const safeValue = String(value ?? '')
|
||||
|
||||
return {
|
||||
value: safeValue,
|
||||
selectionStart: Math.min(lastSelectionState.value.start, safeValue.length),
|
||||
selectionEnd: Math.min(lastSelectionState.value.end, safeValue.length)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 히스토리 항목의 본문 값을 반환한다.
|
||||
* @param {string|{ value?: string }} entry - 히스토리 항목
|
||||
* @returns {string|null} 본문 값
|
||||
*/
|
||||
const getEditorHistoryEntryValue = (entry) => {
|
||||
if (typeof entry === 'string') {
|
||||
return entry
|
||||
}
|
||||
|
||||
if (entry && typeof entry.value === 'string') {
|
||||
return entry.value
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* 히스토리 항목의 선택 위치를 반환한다.
|
||||
* @param {string|{ value?: string, selectionStart?: number, selectionEnd?: number }} entry - 히스토리 항목
|
||||
* @param {string} value - 복원된 본문
|
||||
* @returns {{ start: number, end: number }|null} 선택 위치
|
||||
*/
|
||||
const getEditorHistoryEntrySelection = (entry, value) => {
|
||||
if (!entry || typeof entry === 'string') {
|
||||
return null
|
||||
}
|
||||
|
||||
const length = String(value ?? '').length
|
||||
const start = Number(entry.selectionStart)
|
||||
const end = Number(entry.selectionEnd)
|
||||
|
||||
if (!Number.isFinite(start) || !Number.isFinite(end)) {
|
||||
return null
|
||||
}
|
||||
|
||||
return {
|
||||
start: Math.min(Math.max(0, start), length),
|
||||
end: Math.min(Math.max(0, end), length)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 현재 선택 위치에 맞춰 에디터 포커스를 복원한다.
|
||||
* @param {{ start: number, end: number }|null} selection - 복원할 선택 영역
|
||||
* @returns {void}
|
||||
*/
|
||||
const restoreEditorFocusAfterHistory = () => {
|
||||
const focusLine = activeLogicalLineIndex.value
|
||||
const restoreEditorFocusAfterHistory = (selection = null) => {
|
||||
const focusLine = selection
|
||||
? getLineIndexAtOffset(selection.start)
|
||||
: activeLogicalLineIndex.value
|
||||
|
||||
if (activeMode.value === 'write') {
|
||||
if (selection) {
|
||||
setTextareaSelection(selection.start, selection.end, { preserveScroll: true })
|
||||
return
|
||||
}
|
||||
|
||||
focusTextareaAtLine(focusLine)
|
||||
return
|
||||
}
|
||||
|
||||
nextTick(() => {
|
||||
previewRendererRef.value?.focusEditableAtLine(focusLine, 0, 'auto')
|
||||
const offset = selection
|
||||
? Math.max(0, selection.start - getLineStartOffset(focusLine))
|
||||
: 0
|
||||
|
||||
previewRendererRef.value?.focusEditableAtLine(focusLine, 0, 'auto', offset, 'center')
|
||||
})
|
||||
}
|
||||
|
||||
@@ -620,7 +689,7 @@ const recordEditorHistory = (previousValue) => {
|
||||
const shouldGroup = now - lastEditorHistoryRecordAt < 900
|
||||
|
||||
if (!shouldGroup || !editorUndoStack.value.length) {
|
||||
editorUndoStack.value = [...editorUndoStack.value, value].slice(-EDITOR_UNDO_LIMIT)
|
||||
editorUndoStack.value = [...editorUndoStack.value, createEditorHistoryEntry(value)].slice(-EDITOR_UNDO_LIMIT)
|
||||
}
|
||||
|
||||
editorRedoStack.value = []
|
||||
@@ -635,21 +704,22 @@ const recordEditorHistory = (previousValue) => {
|
||||
const restoreEditorHistory = (direction) => {
|
||||
const sourceStack = direction === 'undo' ? editorUndoStack : editorRedoStack
|
||||
const targetStack = direction === 'undo' ? editorRedoStack : editorUndoStack
|
||||
const nextValue = sourceStack.value[sourceStack.value.length - 1]
|
||||
const nextEntry = sourceStack.value[sourceStack.value.length - 1]
|
||||
const nextValue = getEditorHistoryEntryValue(nextEntry)
|
||||
|
||||
if (typeof nextValue !== 'string') {
|
||||
return
|
||||
}
|
||||
|
||||
sourceStack.value = sourceStack.value.slice(0, -1)
|
||||
targetStack.value = [...targetStack.value, markdownValue.value ?? ''].slice(-EDITOR_UNDO_LIMIT)
|
||||
targetStack.value = [...targetStack.value, createEditorHistoryEntry(markdownValue.value ?? '')].slice(-EDITOR_UNDO_LIMIT)
|
||||
isRestoringEditorHistory.value = true
|
||||
markdownValue.value = nextValue
|
||||
lastEditorHistoryRecordAt = 0
|
||||
|
||||
nextTick(() => {
|
||||
isRestoringEditorHistory.value = false
|
||||
restoreEditorFocusAfterHistory()
|
||||
restoreEditorFocusAfterHistory(getEditorHistoryEntrySelection(nextEntry, nextValue))
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user