194 lines
5.1 KiB
Dart
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';
|
|
}
|
|
}
|
|
}
|