Files
voidraft/frontend/src/views/editor/extensions/codeblock/commands.ts

355 lines
10 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 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;
}