Files
2026-09-23 17:23:59 +08:00

194 lines
5.1 KiB
Dart

import 'json_util.dart';
/// 笔记列表项(不含正文)
///
/// 后端列表接口刻意不返回 content —— 一页 20 条正文可能有几 MB,
/// 而这个后端在公网服务器上,列表页只需要摘要。
class NoteListItem {
final int id;
final int? folderId;
final String title;
final String summary;
final List<String> tags;
final bool pinned;
final String updateTime;
const NoteListItem({
required this.id,
required this.folderId,
required this.title,
required this.summary,
required this.tags,
required this.pinned,
required this.updateTime,
});
factory NoteListItem.fromJson(Map<String, dynamic> json) {
return NoteListItem(
id: asInt(json['id']),
folderId: asNullableInt(json['folderId']),
title: asNonEmpty(json['title'], '无标题笔记'),
summary: asString(json['summary']),
tags: asStringList(json['tagList']),
pinned: asInt(json['isPinned']) == 1,
updateTime: formatDateTime(json['updateTime']),
);
}
}
/// 笔记详情(含正文)
class NoteDetail {
final int id;
final int? folderId;
final String title;
final String content;
final List<String> tags;
final bool pinned;
final String createTime;
final String updateTime;
const NoteDetail({
required this.id,
required this.folderId,
required this.title,
required this.content,
required this.tags,
required this.pinned,
required this.createTime,
required this.updateTime,
});
factory NoteDetail.fromJson(Map<String, dynamic> json) {
return NoteDetail(
id: asInt(json['id']),
folderId: asNullableInt(json['folderId']),
title: asNonEmpty(json['title'], '无标题笔记'),
content: asString(json['content']),
tags: asStringList(json['tagList']),
pinned: asInt(json['isPinned']) == 1,
createTime: formatDateTime(json['createTime']),
updateTime: formatDateTime(json['updateTime']),
);
}
}
/// 分页结果
class NotePage {
final List<NoteListItem> items;
final int total;
final bool hasMore;
const NotePage({required this.items, required this.total, required this.hasMore});
static const empty = NotePage(items: [], total: 0, hasMore: false);
factory NotePage.fromJson(Map<String, dynamic> json) {
return NotePage(
items: asMapList(json['list']).map(NoteListItem.fromJson).toList(),
total: asInt(json['total']),
hasMore: json['hasMore'] == true,
);
}
}
/// 目录树节点
class FolderNode {
final int id;
final String name;
final int? parentId;
/// 该目录下的笔记数(**含所有子目录**),和后端列表接口的口径一致
final int noteCount;
final List<FolderNode> children;
const FolderNode({
required this.id,
required this.name,
required this.parentId,
required this.noteCount,
required this.children,
});
factory FolderNode.fromJson(Map<String, dynamic> json) {
return FolderNode(
id: asInt(json['id']),
name: asNonEmpty(json['name'], '未命名'),
parentId: asNullableInt(json['parentId']),
noteCount: asInt(json['noteCount']),
children: asMapList(json['children']).map(FolderNode.fromJson).toList(),
);
}
}
/// 目录树接口的完整返回
class FolderTree {
final List<FolderNode> folders;
final int uncategorizedCount;
final int totalCount;
const FolderTree({
required this.folders,
required this.uncategorizedCount,
required this.totalCount,
});
static const empty = FolderTree(folders: [], uncategorizedCount: 0, totalCount: 0);
factory FolderTree.fromJson(Map<String, dynamic> json) {
return FolderTree(
folders: asMapList(json['folders']).map(FolderNode.fromJson).toList(),
uncategorizedCount: asInt(json['uncategorizedCount']),
totalCount: asInt(json['totalCount']),
);
}
/// 按 id 找节点,用于拼当前目录的面包屑路径
List<FolderNode>? findPath(int id) {
List<FolderNode>? walk(List<FolderNode> nodes, List<FolderNode> trail) {
for (final node in nodes) {
final next = [...trail, node];
if (node.id == id) return next;
final found = walk(node.children, next);
if (found != null) return found;
}
return null;
}
return walk(folders, []);
}
}
/// 当前浏览的位置
///
/// 用三个 scope 对应目录树上的三种节点,而不是用 `folderId == null`
/// 来表达「全部」—— 那样就没法区分「全部」和「未分类」了。
enum NoteScope { all, uncategorized, folder }
class NoteLocation {
final NoteScope scope;
final int? folderId;
final String folderName;
const NoteLocation({
required this.scope,
this.folderId,
this.folderName = '全部',
});
static const all = NoteLocation(scope: NoteScope.all, folderName: '全部');
static const uncategorized =
NoteLocation(scope: NoteScope.uncategorized, folderName: '未分类');
/// 传给后端 list 接口的 scope 参数
String get apiScope {
switch (scope) {
case NoteScope.all:
return 'all';
case NoteScope.uncategorized:
return 'uncategorized';
case NoteScope.folder:
return 'folder';
}
}
}