feat(board): sticky-note board with backlinks
All checks were successful
CI / Windows build (push) Successful in 20m32s

Wire the F7 双链 + 无限便利贴 model (Board/LinkGraph) into a
reachable screen. Previously the model existed but had no UI and
no entry point.

board_screen.dart: an infinite InteractiveViewer canvas of
draggable, editable sticky cards. Card text renders [[links]] as
tappable chips that pan to the target card (dangling links styled
apart). A backlinks panel lists "linked from" via backlinksOf.
"Add card" FAB drops a card at the viewport center.

Persistence: a board_cards table (DB v7), one row per card,
debounced 800ms like the ink editors, loaded on open — boards
survive restart. Entry added to the home screen app bar
(dashboard_customize icon).

Ink-on-cards, multi-board management and link autocomplete are
deferred (TODO board-ink / board-multi / board-link-autocomplete).
analyze clean, 285 tests green.
This commit is contained in:
2026-06-24 17:15:39 +08:00
parent 0feca74278
commit f757701391
9 changed files with 1080 additions and 3 deletions

View File

@@ -74,5 +74,19 @@
"current": { "type": "int" },
"total": { "type": "int" }
}
}
},
"board": "Board",
"boardTitle": "Sticky Board",
"boardOpen": "Sticky note board",
"boardAddCard": "Add card",
"boardNewCardText": "New note",
"boardDeleteCard": "Delete card",
"boardDeleteCardTitle": "Delete this card?",
"boardBacklinks": "Linked from",
"boardNoBacklinks": "Nothing links here yet",
"boardDanglingLink": "No card named \"{target}\"",
"@boardDanglingLink": {
"placeholders": { "target": { "type": "String" } }
},
"close": "Close"
}

View File

@@ -439,6 +439,72 @@ abstract class AppLocalizations {
/// In en, this message translates to:
/// **'{current} / {total}'**
String pageOfPages(int current, int total);
/// No description provided for @board.
///
/// In en, this message translates to:
/// **'Board'**
String get board;
/// No description provided for @boardTitle.
///
/// In en, this message translates to:
/// **'Sticky Board'**
String get boardTitle;
/// No description provided for @boardOpen.
///
/// In en, this message translates to:
/// **'Sticky note board'**
String get boardOpen;
/// No description provided for @boardAddCard.
///
/// In en, this message translates to:
/// **'Add card'**
String get boardAddCard;
/// No description provided for @boardNewCardText.
///
/// In en, this message translates to:
/// **'New note'**
String get boardNewCardText;
/// No description provided for @boardDeleteCard.
///
/// In en, this message translates to:
/// **'Delete card'**
String get boardDeleteCard;
/// No description provided for @boardDeleteCardTitle.
///
/// In en, this message translates to:
/// **'Delete this card?'**
String get boardDeleteCardTitle;
/// No description provided for @boardBacklinks.
///
/// In en, this message translates to:
/// **'Linked from'**
String get boardBacklinks;
/// No description provided for @boardNoBacklinks.
///
/// In en, this message translates to:
/// **'Nothing links here yet'**
String get boardNoBacklinks;
/// No description provided for @boardDanglingLink.
///
/// In en, this message translates to:
/// **'No card named \"{target}\"'**
String boardDanglingLink(String target);
/// No description provided for @close.
///
/// In en, this message translates to:
/// **'Close'**
String get close;
}
class _AppLocalizationsDelegate

View File

@@ -188,4 +188,39 @@ class AppLocalizationsEn extends AppLocalizations {
String pageOfPages(int current, int total) {
return '$current / $total';
}
@override
String get board => 'Board';
@override
String get boardTitle => 'Sticky Board';
@override
String get boardOpen => 'Sticky note board';
@override
String get boardAddCard => 'Add card';
@override
String get boardNewCardText => 'New note';
@override
String get boardDeleteCard => 'Delete card';
@override
String get boardDeleteCardTitle => 'Delete this card?';
@override
String get boardBacklinks => 'Linked from';
@override
String get boardNoBacklinks => 'Nothing links here yet';
@override
String boardDanglingLink(String target) {
return 'No card named \"$target\"';
}
@override
String get close => 'Close';
}

View File

@@ -188,4 +188,39 @@ class AppLocalizationsZh extends AppLocalizations {
String pageOfPages(int current, int total) {
return '$current / $total';
}
@override
String get board => '便利贴板';
@override
String get boardTitle => '便利贴板';
@override
String get boardOpen => '便利贴板';
@override
String get boardAddCard => '添加便利贴';
@override
String get boardNewCardText => '新便利贴';
@override
String get boardDeleteCard => '删除便利贴';
@override
String get boardDeleteCardTitle => '删除这张便利贴?';
@override
String get boardBacklinks => '哪些链接到这里';
@override
String get boardNoBacklinks => '暂无其他便利贴链接到这里';
@override
String boardDanglingLink(String target) {
return '没有名为“$target”的便利贴';
}
@override
String get close => '关闭';
}

View File

@@ -56,5 +56,16 @@
"actionHighlightSelection": "高亮所选",
"failedToOpenPdf": "打开 PDF 失败:\n{error}",
"pdfNoPages": "PDF 没有任何页面。",
"pageOfPages": "{current} / {total}"
"pageOfPages": "{current} / {total}",
"board": "便利贴板",
"boardTitle": "便利贴板",
"boardOpen": "便利贴板",
"boardAddCard": "添加便利贴",
"boardNewCardText": "新便利贴",
"boardDeleteCard": "删除便利贴",
"boardDeleteCardTitle": "删除这张便利贴?",
"boardBacklinks": "哪些链接到这里",
"boardNoBacklinks": "暂无其他便利贴链接到这里",
"boardDanglingLink": "没有名为“{target}”的便利贴",
"close": "关闭"
}