🎨 Modify code block logic
This commit is contained in:
53
frontend/src/views/editor/extensions/codeblock/annotation.ts
Normal file
53
frontend/src/views/editor/extensions/codeblock/annotation.ts
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
import { Annotation, Transaction } from "@codemirror/state";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 统一的 CodeBlock 注解,用于标记内部触发的事务。
|
||||||
|
*/
|
||||||
|
export const codeBlockEvent = Annotation.define<string>();
|
||||||
|
|
||||||
|
export const LANGUAGE_CHANGE = "codeblock-language-change";
|
||||||
|
export const ADD_NEW_BLOCK = "codeblock-add-new-block";
|
||||||
|
export const MOVE_BLOCK = "codeblock-move-block";
|
||||||
|
export const DELETE_BLOCK = "codeblock-delete-block";
|
||||||
|
export const CURRENCIES_LOADED = "codeblock-currencies-loaded";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 统一管理的 userEvent 常量,方便复用与检索。
|
||||||
|
*/
|
||||||
|
export const USER_EVENTS = {
|
||||||
|
INPUT: "input",
|
||||||
|
DELETE: "delete",
|
||||||
|
MOVE: "move",
|
||||||
|
SELECT: "select",
|
||||||
|
DELETE_LINE: "delete.line",
|
||||||
|
DELETE_CUT: "delete.cut",
|
||||||
|
INPUT_PASTE: "input.paste",
|
||||||
|
MOVE_LINE: "move.line",
|
||||||
|
MOVE_CHARACTER: "move.character",
|
||||||
|
SELECT_BLOCK_BOUNDARY: "select.block-boundary",
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断事务列表中是否包含指定注解。
|
||||||
|
*/
|
||||||
|
export function transactionsHasAnnotation(
|
||||||
|
transactions: readonly Transaction[],
|
||||||
|
annotation: string
|
||||||
|
) {
|
||||||
|
return transactions.some(
|
||||||
|
tr => tr.annotation(codeBlockEvent) === annotation
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断事务列表中是否包含任一注解。
|
||||||
|
*/
|
||||||
|
export function transactionsHasAnnotationsAny(
|
||||||
|
transactions: readonly Transaction[],
|
||||||
|
annotations: readonly string[]
|
||||||
|
) {
|
||||||
|
return transactions.some(tr => {
|
||||||
|
const value = tr.annotation(codeBlockEvent);
|
||||||
|
return value ? annotations.includes(value) : false;
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -2,11 +2,12 @@
|
|||||||
* Block 命令
|
* Block 命令
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { EditorSelection } from "@codemirror/state";
|
import { EditorSelection, Transaction } from "@codemirror/state";
|
||||||
import { Command } from "@codemirror/view";
|
import { Command } from "@codemirror/view";
|
||||||
import { blockState, getActiveNoteBlock, getFirstNoteBlock, getLastNoteBlock, getNoteBlockFromPos } from "./state";
|
import { blockState, getActiveNoteBlock, getFirstNoteBlock, getLastNoteBlock, getNoteBlockFromPos } from "./state";
|
||||||
import { Block, EditorOptions, DELIMITER_REGEX } from "./types";
|
import { Block, EditorOptions, DELIMITER_REGEX } from "./types";
|
||||||
import { formatBlockContent } from "./formatCode";
|
import { formatBlockContent } from "./formatCode";
|
||||||
|
import { codeBlockEvent, LANGUAGE_CHANGE, ADD_NEW_BLOCK, MOVE_BLOCK, DELETE_BLOCK, CURRENCIES_LOADED, USER_EVENTS } from "./annotation";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取块分隔符
|
* 获取块分隔符
|
||||||
@@ -32,7 +33,7 @@ export const insertNewBlockAtCursor = (options: EditorOptions): Command => ({ st
|
|||||||
|
|
||||||
dispatch(state.replaceSelection(delimText), {
|
dispatch(state.replaceSelection(delimText), {
|
||||||
scrollIntoView: true,
|
scrollIntoView: true,
|
||||||
userEvent: "input",
|
userEvent: USER_EVENTS.INPUT,
|
||||||
});
|
});
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -49,15 +50,16 @@ export const addNewBlockBeforeCurrent = (options: EditorOptions): Command => ({
|
|||||||
|
|
||||||
const delimText = getBlockDelimiter(options.defaultBlockToken, options.defaultBlockAutoDetect);
|
const delimText = getBlockDelimiter(options.defaultBlockToken, options.defaultBlockAutoDetect);
|
||||||
|
|
||||||
dispatch(state.update({
|
dispatch(state.update({
|
||||||
changes: {
|
changes: {
|
||||||
from: block.delimiter.from,
|
from: block.delimiter.from,
|
||||||
insert: delimText,
|
insert: delimText,
|
||||||
},
|
},
|
||||||
selection: EditorSelection.cursor(block.delimiter.from + delimText.length),
|
selection: EditorSelection.cursor(block.delimiter.from + delimText.length),
|
||||||
|
annotations: [codeBlockEvent.of(ADD_NEW_BLOCK)],
|
||||||
}, {
|
}, {
|
||||||
scrollIntoView: true,
|
scrollIntoView: true,
|
||||||
userEvent: "input",
|
userEvent: USER_EVENTS.INPUT,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -74,15 +76,16 @@ export const addNewBlockAfterCurrent = (options: EditorOptions): Command => ({ s
|
|||||||
|
|
||||||
const delimText = getBlockDelimiter(options.defaultBlockToken, options.defaultBlockAutoDetect);
|
const delimText = getBlockDelimiter(options.defaultBlockToken, options.defaultBlockAutoDetect);
|
||||||
|
|
||||||
dispatch(state.update({
|
dispatch(state.update({
|
||||||
changes: {
|
changes: {
|
||||||
from: block.content.to,
|
from: block.content.to,
|
||||||
insert: delimText,
|
insert: delimText,
|
||||||
},
|
},
|
||||||
selection: EditorSelection.cursor(block.content.to + delimText.length)
|
selection: EditorSelection.cursor(block.content.to + delimText.length),
|
||||||
|
annotations: [codeBlockEvent.of(ADD_NEW_BLOCK)],
|
||||||
}, {
|
}, {
|
||||||
scrollIntoView: true,
|
scrollIntoView: true,
|
||||||
userEvent: "input",
|
userEvent: USER_EVENTS.INPUT,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -99,15 +102,16 @@ export const addNewBlockBeforeFirst = (options: EditorOptions): Command => ({ st
|
|||||||
|
|
||||||
const delimText = getBlockDelimiter(options.defaultBlockToken, options.defaultBlockAutoDetect);
|
const delimText = getBlockDelimiter(options.defaultBlockToken, options.defaultBlockAutoDetect);
|
||||||
|
|
||||||
dispatch(state.update({
|
dispatch(state.update({
|
||||||
changes: {
|
changes: {
|
||||||
from: block.delimiter.from,
|
from: block.delimiter.from,
|
||||||
insert: delimText,
|
insert: delimText,
|
||||||
},
|
},
|
||||||
selection: EditorSelection.cursor(delimText.length),
|
selection: EditorSelection.cursor(delimText.length),
|
||||||
|
annotations: [codeBlockEvent.of(ADD_NEW_BLOCK)],
|
||||||
}, {
|
}, {
|
||||||
scrollIntoView: true,
|
scrollIntoView: true,
|
||||||
userEvent: "input",
|
userEvent: USER_EVENTS.INPUT,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -124,15 +128,16 @@ export const addNewBlockAfterLast = (options: EditorOptions): Command => ({ stat
|
|||||||
|
|
||||||
const delimText = getBlockDelimiter(options.defaultBlockToken, options.defaultBlockAutoDetect);
|
const delimText = getBlockDelimiter(options.defaultBlockToken, options.defaultBlockAutoDetect);
|
||||||
|
|
||||||
dispatch(state.update({
|
dispatch(state.update({
|
||||||
changes: {
|
changes: {
|
||||||
from: block.content.to,
|
from: block.content.to,
|
||||||
insert: delimText,
|
insert: delimText,
|
||||||
},
|
},
|
||||||
selection: EditorSelection.cursor(block.content.to + delimText.length)
|
selection: EditorSelection.cursor(block.content.to + delimText.length),
|
||||||
|
annotations: [codeBlockEvent.of(ADD_NEW_BLOCK)],
|
||||||
}, {
|
}, {
|
||||||
scrollIntoView: true,
|
scrollIntoView: true,
|
||||||
userEvent: "input",
|
userEvent: USER_EVENTS.INPUT,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -143,26 +148,19 @@ export const addNewBlockAfterLast = (options: EditorOptions): Command => ({ stat
|
|||||||
*/
|
*/
|
||||||
export function changeLanguageTo(state: any, dispatch: any, block: Block, language: string, auto: boolean) {
|
export function changeLanguageTo(state: any, dispatch: any, block: Block, language: string, auto: boolean) {
|
||||||
if (state.readOnly) return false;
|
if (state.readOnly) return false;
|
||||||
|
|
||||||
const currentDelimiter = state.doc.sliceString(block.delimiter.from, block.delimiter.to);
|
const newDelimiter = `\n∞∞∞${language}${auto ? '-a' : ''}\n`;
|
||||||
|
|
||||||
// 重置正则表达式的 lastIndex
|
dispatch({
|
||||||
DELIMITER_REGEX.lastIndex = 0;
|
changes: {
|
||||||
if (currentDelimiter.match(DELIMITER_REGEX)) {
|
from: block.delimiter.from,
|
||||||
const newDelimiter = `\n∞∞∞${language}${auto ? '-a' : ''}\n`;
|
to: block.delimiter.to,
|
||||||
|
insert: newDelimiter,
|
||||||
dispatch({
|
},
|
||||||
changes: {
|
annotations: [codeBlockEvent.of(LANGUAGE_CHANGE)],
|
||||||
from: block.delimiter.from,
|
});
|
||||||
to: block.delimiter.to,
|
|
||||||
insert: newDelimiter,
|
return true;
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -189,7 +187,7 @@ function updateSel(sel: EditorSelection, by: (range: any) => any): EditorSelecti
|
|||||||
}
|
}
|
||||||
|
|
||||||
function setSel(state: any, selection: EditorSelection) {
|
function setSel(state: any, selection: EditorSelection) {
|
||||||
return state.update({ selection, scrollIntoView: true, userEvent: "select" });
|
return state.update({ selection, scrollIntoView: true, userEvent: USER_EVENTS.SELECT });
|
||||||
}
|
}
|
||||||
|
|
||||||
function extendSel(state: any, dispatch: any, how: (range: any) => any) {
|
function extendSel(state: any, dispatch: any, how: (range: any) => any) {
|
||||||
@@ -303,10 +301,11 @@ export const deleteBlock = (_options: EditorOptions): Command => ({ state, dispa
|
|||||||
to: block.range.to,
|
to: block.range.to,
|
||||||
insert: ""
|
insert: ""
|
||||||
},
|
},
|
||||||
selection: EditorSelection.cursor(newCursorPos)
|
selection: EditorSelection.cursor(newCursorPos),
|
||||||
|
annotations: [codeBlockEvent.of(DELETE_BLOCK)]
|
||||||
}, {
|
}, {
|
||||||
scrollIntoView: true,
|
scrollIntoView: true,
|
||||||
userEvent: "delete"
|
userEvent: USER_EVENTS.DELETE
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -366,10 +365,11 @@ function moveCurrentBlock(state: any, dispatch: any, up: boolean) {
|
|||||||
|
|
||||||
dispatch(state.update({
|
dispatch(state.update({
|
||||||
changes,
|
changes,
|
||||||
selection: EditorSelection.cursor(newCursorPos)
|
selection: EditorSelection.cursor(newCursorPos),
|
||||||
|
annotations: [codeBlockEvent.of(MOVE_BLOCK)]
|
||||||
}, {
|
}, {
|
||||||
scrollIntoView: true,
|
scrollIntoView: true,
|
||||||
userEvent: "move"
|
userEvent: USER_EVENTS.MOVE
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -380,4 +380,21 @@ function moveCurrentBlock(state: any, dispatch: any, up: boolean) {
|
|||||||
*/
|
*/
|
||||||
export const formatCurrentBlock: Command = (view) => {
|
export const formatCurrentBlock: Command = (view) => {
|
||||||
return formatBlockContent(view);
|
return formatBlockContent(view);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 触发一次货币数据刷新,让数学块重新计算
|
||||||
|
*/
|
||||||
|
export function triggerCurrenciesLoaded({ state, dispatch }: { state: any; dispatch: any }) {
|
||||||
|
if (!dispatch || state.readOnly) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
dispatch(state.update({
|
||||||
|
changes: { from: 0, to: 0, insert: "" },
|
||||||
|
annotations: [
|
||||||
|
codeBlockEvent.of(CURRENCIES_LOADED),
|
||||||
|
Transaction.addToHistory.of(false)
|
||||||
|
],
|
||||||
|
}));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { EditorState, EditorSelection } from "@codemirror/state";
|
|||||||
import { EditorView } from "@codemirror/view";
|
import { EditorView } from "@codemirror/view";
|
||||||
import { Command } from "@codemirror/view";
|
import { Command } from "@codemirror/view";
|
||||||
import { LANGUAGES } from "./lang-parser/languages";
|
import { LANGUAGES } from "./lang-parser/languages";
|
||||||
|
import { USER_EVENTS } from "./annotation";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 构建块分隔符正则表达式
|
* 构建块分隔符正则表达式
|
||||||
@@ -89,7 +90,7 @@ export const codeBlockCopyCut = EditorView.domEventHandlers({
|
|||||||
view.dispatch({
|
view.dispatch({
|
||||||
changes: ranges,
|
changes: ranges,
|
||||||
scrollIntoView: true,
|
scrollIntoView: true,
|
||||||
userEvent: "delete.cut"
|
userEvent: USER_EVENTS.DELETE_CUT
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -111,7 +112,7 @@ const copyCut = (view: EditorView, cut: boolean): boolean => {
|
|||||||
view.dispatch({
|
view.dispatch({
|
||||||
changes: ranges,
|
changes: ranges,
|
||||||
scrollIntoView: true,
|
scrollIntoView: true,
|
||||||
userEvent: "delete.cut"
|
userEvent: USER_EVENTS.DELETE_CUT
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -142,7 +143,7 @@ function doPaste(view: EditorView, input: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
view.dispatch(changes, {
|
view.dispatch(changes, {
|
||||||
userEvent: "input.paste",
|
userEvent: USER_EVENTS.INPUT_PASTE,
|
||||||
scrollIntoView: true
|
scrollIntoView: true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -186,4 +187,4 @@ export function getCopyPasteExtensions() {
|
|||||||
return [
|
return [
|
||||||
codeBlockCopyCut,
|
codeBlockCopyCut,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { EditorView } from '@codemirror/view';
|
|||||||
import { EditorSelection } from '@codemirror/state';
|
import { EditorSelection } from '@codemirror/state';
|
||||||
import { blockState } from './state';
|
import { blockState } from './state';
|
||||||
import { Block } from './types';
|
import { Block } from './types';
|
||||||
|
import { USER_EVENTS } from './annotation';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 二分查找:找到包含指定位置的块
|
* 二分查找:找到包含指定位置的块
|
||||||
@@ -136,7 +137,7 @@ export function createCursorProtection() {
|
|||||||
view.dispatch({
|
view.dispatch({
|
||||||
selection: EditorSelection.cursor(adjustedPos),
|
selection: EditorSelection.cursor(adjustedPos),
|
||||||
scrollIntoView: true,
|
scrollIntoView: true,
|
||||||
userEvent: 'select'
|
userEvent: USER_EVENTS.SELECT
|
||||||
});
|
});
|
||||||
|
|
||||||
// 阻止默认行为
|
// 阻止默认行为
|
||||||
@@ -148,4 +149,3 @@ export function createCursorProtection() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
import { ViewPlugin, EditorView, Decoration, WidgetType, layer, RectangleMarker } from "@codemirror/view";
|
import { ViewPlugin, EditorView, Decoration, WidgetType, layer, RectangleMarker } from "@codemirror/view";
|
||||||
import { StateField, RangeSetBuilder, EditorState } from "@codemirror/state";
|
import { StateField, RangeSetBuilder, EditorState } from "@codemirror/state";
|
||||||
import { blockState } from "./state";
|
import { blockState } from "./state";
|
||||||
|
import { codeBlockEvent } from "./annotation";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 块开始装饰组件
|
* 块开始装饰组件
|
||||||
@@ -180,10 +181,11 @@ const blockLayer = layer({
|
|||||||
*/
|
*/
|
||||||
const preventFirstBlockFromBeingDeleted = EditorState.changeFilter.of((tr: any) => {
|
const preventFirstBlockFromBeingDeleted = EditorState.changeFilter.of((tr: any) => {
|
||||||
const protect: number[] = [];
|
const protect: number[] = [];
|
||||||
|
const internalEvent = tr.annotation(codeBlockEvent);
|
||||||
|
|
||||||
// 获取块状态并获取第一个块的分隔符大小
|
// 获取块状态并获取第一个块的分隔符大小
|
||||||
const blocks = tr.startState.field(blockState);
|
const blocks = tr.startState.field(blockState);
|
||||||
if (blocks && blocks.length > 0) {
|
if (!internalEvent && blocks && blocks.length > 0) {
|
||||||
const firstBlock = blocks[0];
|
const firstBlock = blocks[0];
|
||||||
const firstBlockDelimiterSize = firstBlock.delimiter.to;
|
const firstBlockDelimiterSize = firstBlock.delimiter.to;
|
||||||
|
|
||||||
@@ -195,22 +197,25 @@ const preventFirstBlockFromBeingDeleted = EditorState.changeFilter.of((tr: any)
|
|||||||
|
|
||||||
// 如果是搜索替换操作,保护所有块分隔符
|
// 如果是搜索替换操作,保护所有块分隔符
|
||||||
if (tr.annotations.some((a: any) => a.value === "input.replace" || a.value === "input.replace.all")) {
|
if (tr.annotations.some((a: any) => a.value === "input.replace" || a.value === "input.replace.all")) {
|
||||||
blocks.forEach((block: any) => {
|
blocks?.forEach((block: any) => {
|
||||||
if (block.delimiter) {
|
if (block.delimiter) {
|
||||||
protect.push(block.delimiter.from, block.delimiter.to);
|
protect.push(block.delimiter.from, block.delimiter.to);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 返回保护范围数组,如果没有需要保护的范围则返回 false
|
// 返回保护范围数组;若无需保护则返回 true 放行事务
|
||||||
return protect.length > 0 ? protect : false;
|
return protect.length > 0 ? protect : true;
|
||||||
});
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 防止选择在第一个块之前
|
* 防止选择在第一个块之前
|
||||||
* 使用 transactionFilter 来确保选择不会在第一个块之前
|
* 使用 transactionFilter 来确保选择不会在第一个块之前
|
||||||
*/
|
*/
|
||||||
const preventSelectionBeforeFirstBlock = EditorState.transactionFilter.of((tr: any) => {
|
const preventSelectionBeforeFirstBlock = EditorState.transactionFilter.of((tr: any) => {
|
||||||
|
if (tr.annotation(codeBlockEvent)) {
|
||||||
|
return tr;
|
||||||
|
}
|
||||||
// 获取块状态并获取第一个块的分隔符大小
|
// 获取块状态并获取第一个块的分隔符大小
|
||||||
const blocks = tr.startState.field(blockState);
|
const blocks = tr.startState.field(blockState);
|
||||||
if (!blocks || blocks.length === 0) {
|
if (!blocks || blocks.length === 0) {
|
||||||
@@ -261,4 +266,4 @@ export function getBlockDecorationExtensions(options: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return extensions;
|
return extensions;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
import { EditorSelection, SelectionRange } from "@codemirror/state";
|
import { EditorSelection, SelectionRange } from "@codemirror/state";
|
||||||
import { EditorView } from "@codemirror/view";
|
import { EditorView } from "@codemirror/view";
|
||||||
import { getNoteBlockFromPos } from "./state";
|
import { getNoteBlockFromPos } from "./state";
|
||||||
|
import { USER_EVENTS } from "./annotation";
|
||||||
|
|
||||||
interface LineBlock {
|
interface LineBlock {
|
||||||
from: number;
|
from: number;
|
||||||
@@ -87,7 +88,7 @@ export const deleteLine = (view: EditorView): boolean => {
|
|||||||
changes,
|
changes,
|
||||||
selection,
|
selection,
|
||||||
scrollIntoView: true,
|
scrollIntoView: true,
|
||||||
userEvent: "delete.line"
|
userEvent: USER_EVENTS.DELETE_LINE
|
||||||
});
|
});
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -127,8 +128,8 @@ export const deleteLineCommand = ({ state, dispatch }: { state: any; dispatch: a
|
|||||||
changes,
|
changes,
|
||||||
selection,
|
selection,
|
||||||
scrollIntoView: true,
|
scrollIntoView: true,
|
||||||
userEvent: "delete.line"
|
userEvent: USER_EVENTS.DELETE_LINE
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import * as prettier from "prettier/standalone";
|
|||||||
import { getActiveNoteBlock } from "./state";
|
import { getActiveNoteBlock } from "./state";
|
||||||
import { getLanguage } from "./lang-parser/languages";
|
import { getLanguage } from "./lang-parser/languages";
|
||||||
import { SupportedLanguage } from "./types";
|
import { SupportedLanguage } from "./types";
|
||||||
|
import { USER_EVENTS } from "./annotation";
|
||||||
|
|
||||||
export const formatBlockContent = (view) => {
|
export const formatBlockContent = (view) => {
|
||||||
if (!view || view.state.readOnly)
|
if (!view || view.state.readOnly)
|
||||||
@@ -87,7 +88,7 @@ export const formatBlockContent = (view) => {
|
|||||||
},
|
},
|
||||||
selection: EditorSelection.cursor(currentBlockFrom + Math.min(formattedContent.cursorOffset, formattedContent.formatted.length)),
|
selection: EditorSelection.cursor(currentBlockFrom + Math.min(formattedContent.cursorOffset, formattedContent.formatted.length)),
|
||||||
scrollIntoView: true,
|
scrollIntoView: true,
|
||||||
userEvent: "input"
|
userEvent: USER_EVENTS.INPUT
|
||||||
});
|
});
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -100,4 +101,4 @@ export const formatBlockContent = (view) => {
|
|||||||
// 执行异步格式化
|
// 执行异步格式化
|
||||||
performFormat();
|
performFormat();
|
||||||
return true; // 立即返回 true,表示命令已开始执行
|
return true; // 立即返回 true,表示命令已开始执行
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
import { ViewPlugin, Decoration, WidgetType } from "@codemirror/view";
|
import { ViewPlugin, Decoration, WidgetType } from "@codemirror/view";
|
||||||
import { RangeSetBuilder } from "@codemirror/state";
|
import { RangeSetBuilder } from "@codemirror/state";
|
||||||
import { getNoteBlockFromPos } from "./state";
|
import { getNoteBlockFromPos } from "./state";
|
||||||
|
import { transactionsHasAnnotation, CURRENCIES_LOADED } from "./annotation";
|
||||||
// 声明全局math对象
|
// 声明全局math对象
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
@@ -75,6 +76,11 @@ function mathDeco(view: any): any {
|
|||||||
// get math.js parser and cache it for this block
|
// get math.js parser and cache it for this block
|
||||||
let { parser, prev } = mathParsers.get(block) || {};
|
let { parser, prev } = mathParsers.get(block) || {};
|
||||||
if (!parser) {
|
if (!parser) {
|
||||||
|
// 如果当前可见行不是该 math 块的第一行,为了正确累计 prev,需要从块头开始重新扫描
|
||||||
|
if (line.from > block.content.from) {
|
||||||
|
pos = block.content.from;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if (typeof window.math !== 'undefined') {
|
if (typeof window.math !== 'undefined') {
|
||||||
parser = window.math.parser();
|
parser = window.math.parser();
|
||||||
mathParsers.set(block, { parser, prev });
|
mathParsers.set(block, { parser, prev });
|
||||||
@@ -148,8 +154,12 @@ export const mathBlock = ViewPlugin.fromClass(class {
|
|||||||
}
|
}
|
||||||
|
|
||||||
update(update: any) {
|
update(update: any) {
|
||||||
// If the document changed, the viewport changed, update the decorations
|
// 需要在文档/视口变化或收到 CURRENCIES_LOADED 注解时重新渲染
|
||||||
if (update.docChanged || update.viewportChanged) {
|
if (
|
||||||
|
update.docChanged ||
|
||||||
|
update.viewportChanged ||
|
||||||
|
transactionsHasAnnotation(update.transactions, CURRENCIES_LOADED)
|
||||||
|
) {
|
||||||
this.decorations = mathDeco(update.view);
|
this.decorations = mathDeco(update.view);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
import { EditorSelection, SelectionRange } from "@codemirror/state";
|
import { EditorSelection, SelectionRange } from "@codemirror/state";
|
||||||
import { blockState } from "./state";
|
import { blockState } from "./state";
|
||||||
import { LANGUAGES } from "./lang-parser/languages";
|
import { LANGUAGES } from "./lang-parser/languages";
|
||||||
|
import { USER_EVENTS } from "./annotation";
|
||||||
|
|
||||||
interface LineBlock {
|
interface LineBlock {
|
||||||
from: number;
|
from: number;
|
||||||
@@ -131,7 +132,7 @@ function moveLine(state: any, dispatch: any, forward: boolean): boolean {
|
|||||||
changes,
|
changes,
|
||||||
scrollIntoView: true,
|
scrollIntoView: true,
|
||||||
selection: EditorSelection.create(ranges, state.selection.mainIndex),
|
selection: EditorSelection.create(ranges, state.selection.mainIndex),
|
||||||
userEvent: "move.line"
|
userEvent: USER_EVENTS.MOVE_LINE
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -157,4 +158,4 @@ export const moveLineUp = ({ state, dispatch }: { state: any; dispatch: any }):
|
|||||||
*/
|
*/
|
||||||
export const moveLineDown = ({ state, dispatch }: { state: any; dispatch: any }): boolean => {
|
export const moveLineDown = ({ state, dispatch }: { state: any; dispatch: any }): boolean => {
|
||||||
return moveLine(state, dispatch, true);
|
return moveLine(state, dispatch, true);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { StateField, StateEffect, RangeSetBuilder, EditorSelection, EditorState,
|
|||||||
import { selectAll as defaultSelectAll } from "@codemirror/commands";
|
import { selectAll as defaultSelectAll } from "@codemirror/commands";
|
||||||
import { Command } from "@codemirror/view";
|
import { Command } from "@codemirror/view";
|
||||||
import { getActiveNoteBlock, blockState } from "./state";
|
import { getActiveNoteBlock, blockState } from "./state";
|
||||||
|
import { USER_EVENTS } from "./annotation";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 当用户按下 Ctrl+A 时,我们希望首先选择整个块。但如果整个块已经被选中,
|
* 当用户按下 Ctrl+A 时,我们希望首先选择整个块。但如果整个块已经被选中,
|
||||||
@@ -115,7 +116,7 @@ export const selectAll: Command = ({ state, dispatch }) => {
|
|||||||
// 选择当前块的所有内容
|
// 选择当前块的所有内容
|
||||||
dispatch(state.update({
|
dispatch(state.update({
|
||||||
selection: { anchor: block.content.from, head: block.content.to },
|
selection: { anchor: block.content.from, head: block.content.to },
|
||||||
userEvent: "select"
|
userEvent: USER_EVENTS.SELECT
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -127,7 +128,7 @@ export const selectAll: Command = ({ state, dispatch }) => {
|
|||||||
*/
|
*/
|
||||||
export const blockAwareSelection = EditorState.transactionFilter.of((tr: any) => {
|
export const blockAwareSelection = EditorState.transactionFilter.of((tr: any) => {
|
||||||
// 只处理选择变化的事务,并且忽略我们自己生成的事务
|
// 只处理选择变化的事务,并且忽略我们自己生成的事务
|
||||||
if (!tr.selection || !tr.selection.ranges || tr.annotation?.(Transaction.userEvent) === "select.block-boundary") {
|
if (!tr.selection || !tr.selection.ranges || tr.annotation?.(Transaction.userEvent) === USER_EVENTS.SELECT_BLOCK_BOUNDARY) {
|
||||||
return tr;
|
return tr;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -181,7 +182,7 @@ export const blockAwareSelection = EditorState.transactionFilter.of((tr: any) =>
|
|||||||
return {
|
return {
|
||||||
...tr,
|
...tr,
|
||||||
selection: EditorSelection.create(correctedRanges, tr.selection.mainIndex),
|
selection: EditorSelection.create(correctedRanges, tr.selection.mainIndex),
|
||||||
annotations: tr.annotations.concat(Transaction.userEvent.of("select.block-boundary"))
|
annotations: tr.annotations.concat(Transaction.userEvent.of(USER_EVENTS.SELECT_BLOCK_BOUNDARY))
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -219,4 +220,4 @@ export function getBlockSelectExtensions() {
|
|||||||
return [
|
return [
|
||||||
emptyBlockSelected,
|
emptyBlockSelected,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
import { EditorSelection, findClusterBreak } from "@codemirror/state";
|
import { EditorSelection, findClusterBreak } from "@codemirror/state";
|
||||||
import { getNoteBlockFromPos } from "./state";
|
import { getNoteBlockFromPos } from "./state";
|
||||||
|
import { USER_EVENTS } from "./annotation";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 交换光标前后的字符
|
* 交换光标前后的字符
|
||||||
@@ -46,8 +47,8 @@ export const transposeChars = ({ state, dispatch }: { state: any; dispatch: any
|
|||||||
|
|
||||||
dispatch(state.update(changes, {
|
dispatch(state.update(changes, {
|
||||||
scrollIntoView: true,
|
scrollIntoView: true,
|
||||||
userEvent: "move.character"
|
userEvent: USER_EVENTS.MOVE_CHARACTER
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user