/** * Block 命令 */ import { EditorSelection } from "@codemirror/state"; import { Command } from "@codemirror/view"; import { blockState, getActiveNoteBlock, getFirstNoteBlock, getLastNoteBlock, getNoteBlockFromPos } from "./state"; import { Block, EditorOptions } from "./types"; /** * 获取块分隔符 */ export function getBlockDelimiter(defaultToken: string, autoDetect: boolean): string { return `\n∞∞∞${autoDetect ? defaultToken + '-a' : defaultToken}\n`; } /** * 在光标处插入新块 */ export const insertNewBlockAtCursor = (options: EditorOptions): Command => ({ state, dispatch }) => { if (state.readOnly) return false; const currentBlock = getActiveNoteBlock(state); let delimText: string; if (currentBlock) { delimText = `\n∞∞∞${currentBlock.language.name}${currentBlock.language.auto ? "-a" : ""}\n`; } else { delimText = getBlockDelimiter(options.defaultBlockToken, options.defaultBlockAutoDetect); } dispatch(state.replaceSelection(delimText), { scrollIntoView: true, userEvent: "input", }); return true; }; /** * 在当前块之前添加新块 */ export const addNewBlockBeforeCurrent = (options: EditorOptions): Command => ({ state, dispatch }) => { if (state.readOnly) return false; const block = getActiveNoteBlock(state); if (!block) return false; const delimText = getBlockDelimiter(options.defaultBlockToken, options.defaultBlockAutoDetect); dispatch(state.update({ changes: { from: block.delimiter.from, insert: delimText, }, selection: EditorSelection.cursor(block.delimiter.from + delimText.length), }, { scrollIntoView: true, userEvent: "input", })); return true; }; /** * 在当前块之后添加新块 */ export const addNewBlockAfterCurrent = (options: EditorOptions): Command => ({ state, dispatch }) => { if (state.readOnly) return false; const block = getActiveNoteBlock(state); if (!block) return false; const delimText = getBlockDelimiter(options.defaultBlockToken, options.defaultBlockAutoDetect); dispatch(state.update({ changes: { from: block.content.to, insert: delimText, }, selection: EditorSelection.cursor(block.content.to + delimText.length) }, { scrollIntoView: true, userEvent: "input", })); return true; }; /** * 在第一个块之前添加新块 */ export const addNewBlockBeforeFirst = (options: EditorOptions): Command => ({ state, dispatch }) => { if (state.readOnly) return false; const block = getFirstNoteBlock(state); if (!block) return false; const delimText = getBlockDelimiter(options.defaultBlockToken, options.defaultBlockAutoDetect); dispatch(state.update({ changes: { from: block.delimiter.from, insert: delimText, }, selection: EditorSelection.cursor(delimText.length), }, { scrollIntoView: true, userEvent: "input", })); return true; }; /** * 在最后一个块之后添加新块 */ export const addNewBlockAfterLast = (options: EditorOptions): Command => ({ state, dispatch }) => { if (state.readOnly) return false; const block = getLastNoteBlock(state); if (!block) return false; const delimText = getBlockDelimiter(options.defaultBlockToken, options.defaultBlockAutoDetect); dispatch(state.update({ changes: { from: block.content.to, insert: delimText, }, selection: EditorSelection.cursor(block.content.to + delimText.length) }, { scrollIntoView: true, userEvent: "input", })); return true; }; /** * 更改块语言 */ export function changeLanguageTo(state: any, dispatch: any, block: Block, language: string, auto: boolean) { if (state.readOnly) return false; const delimRegex = /^\n∞∞∞[a-z]+?(-a)?\n/g; if (state.doc.sliceString(block.delimiter.from, block.delimiter.to).match(delimRegex)) { dispatch(state.update({ changes: { from: block.delimiter.from, to: block.delimiter.to, insert: `\n∞∞∞${language}${auto ? '-a' : ''}\n`, }, })); } else { throw new Error("Invalid delimiter: " + state.doc.sliceString(block.delimiter.from, block.delimiter.to)); } } /** * 更改当前块语言 */ export function changeCurrentBlockLanguage(state: any, dispatch: any, language: string | null, auto: boolean) { const block = getActiveNoteBlock(state); if (!block) return; // 如果 language 为 null,我们只想更改自动检测标志 if (language === null) { language = block.language.name; } changeLanguageTo(state, dispatch, block, language, auto); } // 选择和移动辅助函数 function updateSel(sel: EditorSelection, by: (range: any) => any): EditorSelection { return EditorSelection.create(sel.ranges.map(by), sel.mainIndex); } function setSel(state: any, selection: EditorSelection) { return state.update({ selection, scrollIntoView: true, userEvent: "select" }); } function extendSel(state: any, dispatch: any, how: (range: any) => any) { let selection = updateSel(state.selection, range => { let head = how(range); return EditorSelection.range(range.anchor, head.head, head.goalColumn, head.bidiLevel || undefined); }); if (selection.eq(state.selection)) return false; dispatch(setSel(state, selection)); return true; } function moveSel(state: any, dispatch: any, how: (range: any) => any) { let selection = updateSel(state.selection, how); if (selection.eq(state.selection)) return false; dispatch(setSel(state, selection)); return true; } function previousBlock(state: any, range: any) { const blocks = state.field(blockState); const block = getNoteBlockFromPos(state, range.head); if (!block) return EditorSelection.cursor(0); if (range.head === block.content.from) { const index = blocks.indexOf(block); const previousBlockIndex = index > 0 ? index - 1 : 0; return EditorSelection.cursor(blocks[previousBlockIndex].content.from); } else { return EditorSelection.cursor(block.content.from); } } function nextBlock(state: any, range: any) { const blocks = state.field(blockState); const block = getNoteBlockFromPos(state, range.head); if (!block) return EditorSelection.cursor(state.doc.length); if (range.head === block.content.to) { const index = blocks.indexOf(block); const nextBlockIndex = index < blocks.length - 1 ? index + 1 : index; return EditorSelection.cursor(blocks[nextBlockIndex].content.to); } else { return EditorSelection.cursor(block.content.to); } } /** * 跳转到下一个块 */ export function gotoNextBlock({ state, dispatch }: any) { return moveSel(state, dispatch, (range: any) => nextBlock(state, range)); } /** * 选择到下一个块 */ export function selectNextBlock({ state, dispatch }: any) { return extendSel(state, dispatch, (range: any) => nextBlock(state, range)); } /** * 跳转到上一个块 */ export function gotoPreviousBlock({ state, dispatch }: any) { return moveSel(state, dispatch, (range: any) => previousBlock(state, range)); } /** * 选择到上一个块 */ export function selectPreviousBlock({ state, dispatch }: any) { return extendSel(state, dispatch, (range: any) => previousBlock(state, range)); } /** * 删除块 */ export const deleteBlock = (options: EditorOptions): Command => ({ state, dispatch }) => { if (state.readOnly) return false; const block = getActiveNoteBlock(state); if (!block) return false; const blocks = state.field(blockState); if (blocks.length <= 1) return false; // 不能删除最后一个块 const blockIndex = blocks.indexOf(block); let newCursorPos: number; if (blockIndex === blocks.length - 1) { // 如果是最后一个块,将光标移到前一个块的末尾 newCursorPos = blocks[blockIndex - 1].content.to; } else { // 否则移到下一个块的开始 newCursorPos = blocks[blockIndex + 1].content.from; } dispatch(state.update({ changes: { from: block.range.from, to: block.range.to, insert: "" }, selection: EditorSelection.cursor(newCursorPos) }, { scrollIntoView: true, userEvent: "delete" })); return true; }; /** * 向上移动当前块 */ export function moveCurrentBlockUp({ state, dispatch }: any) { return moveCurrentBlock(state, dispatch, true); } /** * 向下移动当前块 */ export function moveCurrentBlockDown({ state, dispatch }: any) { return moveCurrentBlock(state, dispatch, false); } function moveCurrentBlock(state: any, dispatch: any, up: boolean) { if (state.readOnly) return false; const block = getActiveNoteBlock(state); if (!block) return false; const blocks = state.field(blockState); const blockIndex = blocks.indexOf(block); const targetIndex = up ? blockIndex - 1 : blockIndex + 1; if (targetIndex < 0 || targetIndex >= blocks.length) return false; const targetBlock = blocks[targetIndex]; // 获取两个块的完整内容 const currentBlockContent = state.doc.sliceString(block.range.from, block.range.to); const targetBlockContent = state.doc.sliceString(targetBlock.range.from, targetBlock.range.to); // 交换块的位置 const changes = up ? [ { from: targetBlock.range.from, to: block.range.to, insert: currentBlockContent + targetBlockContent } ] : [ { from: block.range.from, to: targetBlock.range.to, insert: targetBlockContent + currentBlockContent } ]; // 计算新的光标位置 const newCursorPos = up ? targetBlock.range.from + (block.range.to - block.range.from) + (block.content.from - block.range.from) : block.range.from + (targetBlock.range.to - targetBlock.range.from) + (block.content.from - block.range.from); dispatch(state.update({ changes, selection: EditorSelection.cursor(newCursorPos) }, { scrollIntoView: true, userEvent: "move" })); return true; }