import '../../../../core/utils/text.dart'; /// 侧栏导航节点。 /// /// `index3.html` 的侧栏是两层的:一级条目要么直接指向一个分区(如「常用推荐」), /// 要么是一个可以展开的分组(如「素材资源」),分组下面是若干二级条目。 /// 这里用同一棵树表达两种形态——[children] 为空即为直接跳转的叶子。 class NavNode { const NavNode({ required this.id, required this.title, this.children = const [], this.editable = false, }); /// 唯一标识;叶子节点同时也是内容区的锚点 id。 final String id; /// 显示文字。 final String title; /// 子节点;为空表示这是一个直接跳转的条目。 final List children; /// 这个分组是不是用户创建的(后端返回的 `editable`)。 /// /// 提交表单据此给出「已有的用户分组」建议:内置分组不可选, /// 因为提交的内容永远进不了内置分组。 final bool editable; /// 是否是可展开的分组。 bool get isGroup => children.isNotEmpty; /// 该节点覆盖的所有叶子(自身是叶子时返回自身,否则递归收集后代)。 List get leaves { if (children.isEmpty) { return [this]; } return children .expand((NavNode child) => child.leaves) .toList(growable: false); } /// 侧栏折叠时显示的短标签。 /// /// 取标题首字,与 logo 折叠成「S」是同一套规则; /// 用 `runes` 保证中文与 emoji 不会被截成半个字符。 String get shortLabel => firstChar(title); @override bool operator ==(Object other) { return identical(this, other) || other is NavNode && other.id == id && other.title == title; } @override int get hashCode => Object.hash(id, title); @override String toString() => 'NavNode(id: $id, title: $title, children: ${children.length})'; }