一些检查失败了
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
109 行
3.3 KiB
TypeScript
109 行
3.3 KiB
TypeScript
import { NoteEntity } from '../database/types';
|
||
|
||
export interface NoteListItemEntity extends NoteEntity {
|
||
is_pinned?: number;
|
||
}
|
||
|
||
/**
|
||
* 清理 Markdown 正文并提取前 80 字符的纯文本摘要(对标 Apple 备忘录副标题摘要)
|
||
*
|
||
* @param body 笔记正文内容
|
||
* @param title 笔记标题(若首行与标题相同则自动过滤跳过)
|
||
*/
|
||
export const cleanSnippet = (body: string, title?: string): string => {
|
||
if (!body || !body.trim()) return '';
|
||
|
||
const lines = body.split('\n');
|
||
const cleanedSegments: string[] = [];
|
||
const normalizedTitle = title ? title.trim().toLowerCase() : '';
|
||
|
||
for (const line of lines) {
|
||
const trimmed = line.trim();
|
||
if (!trimmed) continue;
|
||
|
||
// 如果当前行与标题相同或为标题的 Markdown 形式(如 # 标题),予以跳过
|
||
const headingStripped = trimmed.replace(/^#{1,6}\s+/, '').trim().toLowerCase();
|
||
if (normalizedTitle && (trimmed.toLowerCase() === normalizedTitle || headingStripped === normalizedTitle)) {
|
||
continue;
|
||
}
|
||
|
||
// 移除各种 Markdown 格式控制符
|
||
const text = trimmed
|
||
.replace(/^#{1,6}\s+/, '')
|
||
.replace(/^[>*+-]\s+\[[ xX]\]\s*/, '')
|
||
.replace(/^[>*+-]\s+/, '')
|
||
.replace(/^\d+\.\s+\[[ xX]\]\s*/, '')
|
||
.replace(/^\d+\.\s+/, '')
|
||
.replace(/!\[([^\]]*)\]\([^)]*\)/g, '') // 移除图片
|
||
.replace(/\[([^\]]+)\]\([^)]*\)/g, '$1') // 链接仅保留描述文本
|
||
.replace(/```[\s\S]*?```/g, '') // 代码块标记
|
||
.replace(/[`*_~]/g, '') // 加粗、斜体、删除线、行内代码
|
||
.replace(/<[^>]+>/g, '') // HTML 标签
|
||
.trim();
|
||
|
||
if (text) {
|
||
cleanedSegments.push(text);
|
||
if (cleanedSegments.join(' ').length >= 90) break;
|
||
}
|
||
}
|
||
|
||
const combined = cleanedSegments.join(' ').replace(/\s+/g, ' ').trim();
|
||
if (combined.length > 70) {
|
||
return combined.slice(0, 70) + '…';
|
||
}
|
||
return combined;
|
||
};
|
||
|
||
/**
|
||
* 苹果备忘录风格的时间格式化:
|
||
* - 今天:仅展示时分(如 15:30)
|
||
* - 本年:展示月/日(如 10/25)
|
||
* - 往年:展示年/月/日(如 2025/10/25)
|
||
*/
|
||
export const formatNoteListDate = (updatedTimeMs: number): string => {
|
||
if (!updatedTimeMs) return '';
|
||
const now = new Date();
|
||
const date = new Date(updatedTimeMs);
|
||
|
||
const isToday =
|
||
date.getFullYear() === now.getFullYear() &&
|
||
date.getMonth() === now.getMonth() &&
|
||
date.getDate() === now.getDate();
|
||
|
||
if (isToday) {
|
||
const hours = date.getHours().toString().padStart(2, '0');
|
||
const minutes = date.getMinutes().toString().padStart(2, '0');
|
||
return `${hours}:${minutes}`;
|
||
}
|
||
|
||
const isThisYear = date.getFullYear() === now.getFullYear();
|
||
if (isThisYear) {
|
||
return `${date.getMonth() + 1}/${date.getDate()}`;
|
||
}
|
||
|
||
return `${date.getFullYear()}/${date.getMonth() + 1}/${date.getDate()}`;
|
||
};
|
||
|
||
/**
|
||
* 将置顶笔记重排至列表顶端,并注入 is_pinned 属性
|
||
*/
|
||
export const reorderNotesWithPinned = (notes: NoteListItemEntity[], pinnedNoteIds: string[] = []): NoteListItemEntity[] => {
|
||
if (!pinnedNoteIds || !pinnedNoteIds.length || !notes || !notes.length) {
|
||
return notes;
|
||
}
|
||
|
||
const pinnedSet = new Set(pinnedNoteIds);
|
||
const pinned: NoteListItemEntity[] = [];
|
||
const unpinned: NoteListItemEntity[] = [];
|
||
|
||
for (const note of notes) {
|
||
if (pinnedSet.has(note.id)) {
|
||
pinned.push({ ...note, is_pinned: 1 });
|
||
} else {
|
||
unpinned.push(note);
|
||
}
|
||
}
|
||
|
||
return [...pinned, ...unpinned];
|
||
};
|