feat: Initial clean release of CHM
一些检查失败了
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
一些检查失败了
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
这个提交包含在:
@@ -0,0 +1,116 @@
|
||||
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;
|
||||
在新工单中引用
屏蔽一个用户