文件
mynode/packages/editor/CodeMirror/extensions/quickTimestampExtension.ts
chen 301194963b
一些检查失败了
automerge / automerge (push) Has been cancelled
react-native-android-build-apk / AssembleRelease (push) Has been cancelled
Build macOS M1 / Main (push) Has been cancelled
Joplin Continuous Integration / Main (macos-15-intel) (push) Has been cancelled
Joplin Continuous Integration / Main (ubuntu-22.04) (push) Has been cancelled
Joplin Continuous Integration / Main (ubuntu-22.04-arm) (push) Has been cancelled
Joplin Continuous Integration / Main (windows-11-vs2026-arm) (push) Has been cancelled
Joplin Continuous Integration / Main (windows-2025) (push) Has been cancelled
Joplin Continuous Integration / ServerDockerImage (ubuntu-22.04) (push) Has been cancelled
Joplin Continuous Integration / ServerDockerImage (ubuntu-22.04-arm) (push) Has been cancelled
Joplin UI tests / Main (ubuntu-22.04) (push) Has been cancelled
Joplin UI tests / Main (windows-2025) (push) Has been cancelled
Joplin UI tests / Mobile (push) Has been cancelled
Close stale issues / ProcessStaleIssues (push) Has been cancelled
feat: Initial clean release of CHM
2026-11-04 11:29:39 +08:00

117 行
3.2 KiB
TypeScript

此文件含有模棱两可的 Unicode 字符

此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。

import { EditorView, Decoration, DecorationSet, ViewPlugin, ViewUpdate } from '@codemirror/view';
import { RangeSetBuilder, Extension } from '@codemirror/state';
export const timestampChipClassName = 'cm-ext-timestamp-chip';
/**
* 格式化当前时间为 HH:mm(如 16:30)
*/
export const getFormattedCurrentTime = (date = new Date()): string => {
const hours = date.getHours().toString().padStart(2, '0');
const minutes = date.getMinutes().toString().padStart(2, '0');
return `${hours}:${minutes}`;
};
/**
* 检测光标前是否为 /now 或 /time 关键词,若是则瞬时展开为时间戳
*/
export const tryExpandQuickTimestamp = (
view: EditorView,
from: number,
to: number,
triggerChar: string,
): boolean => {
if (view.composing || view.state.readOnly) return false;
const doc = view.state.doc;
const line = doc.lineAt(from);
const lineTextBefore = doc.sliceString(line.from, from);
let matchLength = 0;
if (lineTextBefore.endsWith('/now')) {
matchLength = 4;
} else if (lineTextBefore.endsWith('/time')) {
matchLength = 5;
}
if (matchLength === 0) return false;
const matchFrom = from - matchLength;
const timeStr = getFormattedCurrentTime();
const replacement = `[${timeStr}]${triggerChar}`;
view.dispatch({
changes: { from: matchFrom, to, insert: replacement },
selection: { anchor: matchFrom + replacement.length },
userEvent: 'input.quickTimestamp',
});
return true;
};
const timestampDeco = Decoration.mark({ class: timestampChipClassName });
const buildTimestampDecorations = (view: EditorView): DecorationSet => {
const builder = new RangeSetBuilder<Decoration>();
const TIMESTAMP_REGEX = /\[\d{1,2}:\d{2}\]/g;
for (const { from, to } of view.visibleRanges) {
const text = view.state.doc.sliceString(from, to);
TIMESTAMP_REGEX.lastIndex = 0;
let match: RegExpExecArray | null;
while ((match = TIMESTAMP_REGEX.exec(text)) !== null) {
const start = from + match.index;
const end = start + match[0].length;
builder.add(start, end, timestampDeco);
}
}
return builder.finish();
};
const timestampChipPlugin = ViewPlugin.fromClass(
class {
public decorations: DecorationSet;
public constructor(view: EditorView) {
this.decorations = buildTimestampDecorations(view);
}
public update(update: ViewUpdate) {
if (update.docChanged || update.viewportChanged) {
this.decorations = buildTimestampDecorations(update.view);
}
}
},
{
decorations: v => v.decorations,
},
);
const quickTimestampExtension = (): Extension => [
EditorView.theme({
[`& .${timestampChipClassName}`]: {
fontVariantNumeric: 'tabular-nums',
backgroundColor: 'var(--joplin-background-color-hover3, rgba(0, 0, 0, 0.06))',
color: 'var(--joplin-color-accent, #007aff)',
fontSize: '0.88em',
fontWeight: '600',
padding: '1px 6px',
borderRadius: '4px',
marginRight: '3px',
letterSpacing: '0.02em',
display: 'inline-block',
verticalAlign: 'baseline',
},
}),
EditorView.inputHandler.of((view, from, to, text) => {
if (text === ' ' || text === '\t' || text === '\n') {
if (tryExpandQuickTimestamp(view, from, to, text)) {
return true;
}
}
return false;
}),
timestampChipPlugin,
];
export default quickTimestampExtension;