98 lines
3.7 KiB
JavaScript
98 lines
3.7 KiB
JavaScript
import { nextTick, ref } from 'vue'
|
|
import {
|
|
createEditorHistoryEntry,
|
|
getEditorHistoryEntrySelection,
|
|
getEditorHistoryEntryValue
|
|
} from '../lib/markdown-editor-history.js'
|
|
|
|
const DEFAULT_EDITOR_UNDO_LIMIT = 100
|
|
const DEFAULT_EDITOR_HISTORY_GROUP_DELAY_MS = 900
|
|
|
|
/**
|
|
* 관리자 Markdown 에디터 히스토리 상태를 만든다.
|
|
* @param {Object} options - 히스토리 옵션
|
|
* @param {import('vue').Ref<string>} options.value - Markdown 본문 ref
|
|
* @param {import('vue').Ref<{ start?: number, end?: number }>} options.lastSelectionState - 마지막 선택 위치
|
|
* @param {(selection: { start: number, end: number }|null) => void} options.restoreFocusAfterHistory - 복원 후 포커스 콜백
|
|
* @param {number} [options.undoLimit=100] - 히스토리 최대 개수
|
|
* @param {number} [options.groupDelayMs=900] - 연속 입력 그룹 지연 시간
|
|
* @returns {Object} 히스토리 상태와 이벤트
|
|
*/
|
|
export const useAdminMarkdownHistory = (options) => {
|
|
const rawUndoLimit = Number(options.undoLimit)
|
|
const rawGroupDelayMs = Number(options.groupDelayMs)
|
|
const undoLimit = Math.max(1, Number.isFinite(rawUndoLimit) ? rawUndoLimit : DEFAULT_EDITOR_UNDO_LIMIT)
|
|
const groupDelayMs = Math.max(0, Number.isFinite(rawGroupDelayMs) ? rawGroupDelayMs : DEFAULT_EDITOR_HISTORY_GROUP_DELAY_MS)
|
|
/** @type {import('vue').Ref<Array<string|{ value: string, selectionStart: number, selectionEnd: number }>>} 본문 되돌리기 스택 */
|
|
const editorUndoStack = ref([])
|
|
/** @type {import('vue').Ref<Array<string|{ value: string, selectionStart: number, selectionEnd: number }>>} 본문 다시 실행 스택 */
|
|
const editorRedoStack = ref([])
|
|
/** 히스토리 복원 중 변경 기록을 막는다. */
|
|
const isRestoringEditorHistory = ref(false)
|
|
/** 마지막 히스토리 기록 시각 */
|
|
let lastEditorHistoryRecordAt = 0
|
|
|
|
/**
|
|
* 에디터 히스토리 항목을 기록한다.
|
|
* @param {string} previousValue - 변경 전 본문
|
|
* @returns {void}
|
|
*/
|
|
const recordEditorHistory = (previousValue) => {
|
|
if (isRestoringEditorHistory.value) {
|
|
return
|
|
}
|
|
|
|
const value = String(previousValue ?? '')
|
|
const now = Date.now()
|
|
const shouldGroup = now - lastEditorHistoryRecordAt < groupDelayMs
|
|
|
|
if (!shouldGroup || !editorUndoStack.value.length) {
|
|
editorUndoStack.value = [
|
|
...editorUndoStack.value,
|
|
createEditorHistoryEntry(value, options.lastSelectionState.value)
|
|
].slice(-undoLimit)
|
|
}
|
|
|
|
editorRedoStack.value = []
|
|
lastEditorHistoryRecordAt = now
|
|
}
|
|
|
|
/**
|
|
* 에디터 본문 히스토리를 복원한다.
|
|
* @param {'undo'|'redo'} direction - 복원 방향
|
|
* @returns {void}
|
|
*/
|
|
const restoreEditorHistory = (direction) => {
|
|
const sourceStack = direction === 'undo' ? editorUndoStack : editorRedoStack
|
|
const targetStack = direction === 'undo' ? editorRedoStack : editorUndoStack
|
|
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,
|
|
createEditorHistoryEntry(options.value.value ?? '', options.lastSelectionState.value)
|
|
].slice(-undoLimit)
|
|
isRestoringEditorHistory.value = true
|
|
options.value.value = nextValue
|
|
lastEditorHistoryRecordAt = 0
|
|
|
|
nextTick(() => {
|
|
isRestoringEditorHistory.value = false
|
|
options.restoreFocusAfterHistory(getEditorHistoryEntrySelection(nextEntry, nextValue))
|
|
})
|
|
}
|
|
|
|
return {
|
|
editorUndoStack,
|
|
editorRedoStack,
|
|
isRestoringEditorHistory,
|
|
recordEditorHistory,
|
|
restoreEditorHistory
|
|
}
|
|
}
|