@gavin-hu/my-pi-agent 0.18.0 → 0.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +66 -52
- package/README.zh-CN.md +56 -46
- package/docs/extensions/README.md +34 -0
- package/docs/extensions/board.md +507 -0
- package/{extensions/bytefly/README.md → docs/extensions/bytefly.md} +774 -574
- package/{extensions/check/README.md → docs/extensions/check.md} +83 -68
- package/{extensions/context/README.md → docs/extensions/context.md} +40 -23
- package/docs/extensions/file-browse.md +561 -0
- package/{extensions/file-open/README.md → docs/extensions/file-open.md} +31 -23
- package/{extensions/file-read/README.md → docs/extensions/file-read.md} +47 -55
- package/{extensions/goal/README.md → docs/extensions/goal.md} +115 -131
- package/{extensions/job/README.md → docs/extensions/job.md} +62 -52
- package/{extensions/memory/README.md → docs/extensions/memory.md} +71 -60
- package/{extensions/plan/README.md → docs/extensions/plan.md} +440 -322
- package/{extensions/question/README.md → docs/extensions/question.md} +54 -31
- package/{extensions/rewind/README.md → docs/extensions/rewind.md} +115 -122
- package/docs/extensions/session-bus.md +298 -0
- package/{extensions/subagent/README.md → docs/extensions/subagent.md} +165 -118
- package/docs/extensions/team.md +254 -0
- package/{extensions/todo/README.md → docs/extensions/todo.md} +85 -88
- package/{extensions/watch/README.md → docs/extensions/watch.md} +54 -36
- package/{extensions/web-access/README.md → docs/extensions/web-access.md} +93 -50
- package/{extensions/wechat/README.md → docs/extensions/wechat.md} +260 -163
- package/{extensions/worktree/README.md → docs/extensions/worktree.md} +137 -79
- package/extensions/board/commands.ts +105 -0
- package/extensions/board/config.ts +36 -0
- package/extensions/board/format.ts +170 -0
- package/extensions/board/index.ts +40 -0
- package/extensions/board/input.ts +54 -0
- package/extensions/board/join.ts +42 -0
- package/extensions/board/page.ts +388 -0
- package/extensions/board/result.ts +108 -0
- package/extensions/board/rows.ts +48 -0
- package/extensions/board/schema.ts +90 -0
- package/extensions/board/seat.ts +28 -0
- package/extensions/board/snapshot.ts +42 -0
- package/extensions/board/tools.ts +444 -0
- package/extensions/board/transport/router.ts +91 -0
- package/extensions/board/transport/server.ts +138 -0
- package/extensions/board/types.ts +121 -0
- package/extensions/board/view/client.ts +51 -0
- package/extensions/board/view/dialog.ts +43 -0
- package/extensions/board/view/keys.ts +125 -0
- package/extensions/board/view/live.ts +231 -0
- package/extensions/board/view/names.ts +91 -0
- package/extensions/board/view/page.ts +118 -0
- package/extensions/board/view/render.ts +519 -0
- package/extensions/board/view/styles.ts +228 -0
- package/extensions/board/view/team.ts +327 -0
- package/extensions/bytefly/footer/budget.ts +6 -5
- package/extensions/bytefly/footer/collect/entries.ts +12 -4
- package/extensions/bytefly/footer/collect/snapshot.ts +8 -17
- package/extensions/bytefly/footer/config.ts +19 -8
- package/extensions/bytefly/footer/price/price.ts +30 -8
- package/extensions/bytefly/footer/register.ts +5 -5
- package/extensions/bytefly/footer/render/format.ts +18 -0
- package/extensions/bytefly/footer/render/lines.ts +23 -7
- package/extensions/bytefly/footer/render/segments.ts +117 -31
- package/extensions/bytefly/footer/types.ts +29 -5
- package/extensions/bytefly/footer/unpriced.ts +42 -17
- package/extensions/bytefly/panel/commands.ts +67 -7
- package/extensions/bytefly/panel/filter.ts +68 -0
- package/extensions/bytefly/panel/format.ts +16 -15
- package/extensions/bytefly/panel/join.ts +56 -13
- package/extensions/bytefly/panel/prices.ts +43 -0
- package/extensions/bytefly/panel/settings-runtime.ts +0 -6
- package/extensions/bytefly/panel/settings.ts +75 -43
- package/extensions/bytefly/panel/tui/entries.ts +26 -8
- package/extensions/bytefly/panel/tui/frame.ts +130 -93
- package/extensions/bytefly/panel/tui/keys.ts +352 -0
- package/extensions/bytefly/panel/tui/list-view.ts +25 -46
- package/extensions/bytefly/panel/tui/panes.ts +76 -0
- package/extensions/bytefly/panel/tui/section-view.ts +126 -0
- package/extensions/bytefly/panel/tui/settings-section.ts +54 -125
- package/extensions/bytefly/panel/tui/settings-view.ts +30 -131
- package/extensions/bytefly/panel/tui/side-pane.ts +224 -0
- package/extensions/bytefly/panel/tui.ts +306 -259
- package/extensions/bytefly/picker/discovery.ts +0 -2
- package/extensions/bytefly/picker/types.ts +0 -2
- package/extensions/bytefly/update/check.ts +39 -0
- package/extensions/bytefly/update/format.ts +15 -0
- package/extensions/bytefly/update/register.ts +73 -34
- package/extensions/bytefly/update/state.ts +6 -3
- package/extensions/check/commands.ts +2 -1
- package/extensions/context/tui.ts +1 -1
- package/extensions/file-browse/{config.ts → config/config.ts} +1 -1
- package/extensions/file-browse/files/classify.ts +136 -0
- package/extensions/file-browse/files/files.ts +12 -0
- package/extensions/file-browse/files/index.ts +90 -0
- package/extensions/file-browse/files/list.ts +261 -0
- package/extensions/file-browse/{paths.ts → files/paths.ts} +1 -1
- package/extensions/file-browse/files/tree.ts +101 -0
- package/extensions/file-browse/{git.ts → git/git.ts} +10 -3
- package/extensions/file-browse/index.ts +4 -4
- package/extensions/file-browse/{live.ts → live/live.ts} +20 -69
- package/extensions/file-browse/{router.ts → transport/router.ts} +21 -17
- package/extensions/file-browse/transport/server.ts +110 -0
- package/extensions/file-browse/{chrome.ts → view/chrome.ts} +4 -4
- package/extensions/file-browse/{client.ts → view/client.ts} +1 -1
- package/extensions/file-browse/{diff-html.ts → view/diff-html.ts} +3 -3
- package/extensions/file-browse/view/emit.ts +51 -0
- package/extensions/file-browse/{html.ts → view/html.ts} +19 -18
- package/extensions/file-browse/{icons.ts → view/icons.ts} +1 -1
- package/extensions/file-browse/{markdown.ts → view/markdown.ts} +18 -20
- package/extensions/file-browse/{raw-html.ts → view/raw-html.ts} +19 -14
- package/extensions/file-browse/{styles.ts → view/styles.ts} +29 -5
- package/extensions/file-browse/{tree.ts → view/tree.ts} +1 -1
- package/extensions/file-browse/{urls.ts → view/urls.ts} +1 -1
- package/extensions/file-read/schema.ts +2 -1
- package/extensions/job/runtime.ts +4 -4
- package/extensions/memory/store.ts +12 -5
- package/extensions/plan/blocks.ts +23 -2
- package/extensions/plan/commands.ts +21 -16
- package/extensions/plan/feedback.ts +185 -52
- package/extensions/plan/format.ts +10 -3
- package/extensions/plan/index.ts +23 -5
- package/extensions/plan/plan-files.ts +88 -0
- package/extensions/plan/plan-index.ts +43 -9
- package/extensions/plan/plan-text.ts +33 -23
- package/extensions/plan/plans.ts +27 -34
- package/extensions/plan/policy.ts +30 -17
- package/extensions/plan/review.ts +3 -27
- package/extensions/plan/runtime.ts +52 -11
- package/extensions/plan/sections.ts +112 -0
- package/extensions/plan/steps.ts +3 -22
- package/extensions/plan/todos.ts +51 -3
- package/extensions/plan/tools/exit.ts +20 -95
- package/extensions/plan/tools/todo-seed.ts +4 -2
- package/extensions/plan/tools/write.ts +12 -1
- package/extensions/plan/tui.ts +5 -34
- package/extensions/plan/types.ts +15 -8
- package/extensions/plan/web-blocks.ts +89 -0
- package/extensions/plan/web-chrome.ts +3 -5
- package/extensions/plan/web-client.ts +179 -330
- package/extensions/plan/web-collab.ts +3 -3
- package/extensions/plan/web-comments.ts +18 -8
- package/extensions/plan/web-composer.ts +80 -0
- package/extensions/plan/web-draft.ts +40 -0
- package/extensions/plan/web-keys.ts +134 -0
- package/extensions/plan/web-outline.ts +192 -0
- package/extensions/plan/web-page.ts +4 -8
- package/extensions/plan/web-plan.ts +150 -0
- package/extensions/plan/web-router.ts +3 -58
- package/extensions/plan/web-settle.ts +194 -0
- package/extensions/plan/web-share-session.ts +24 -30
- package/extensions/plan/web-streams.ts +14 -14
- package/extensions/plan/web-tokens.ts +2 -2
- package/extensions/plan/web.ts +66 -120
- package/extensions/rewind/{commands.ts → commands/commands.ts} +5 -5
- package/extensions/rewind/{review.ts → commands/review.ts} +15 -14
- package/extensions/rewind/{rewind.ts → commands/rewind.ts} +6 -6
- package/extensions/rewind/{config.ts → config/config.ts} +1 -1
- package/extensions/rewind/index.ts +3 -3
- package/extensions/rewind/{policy.ts → state/policy.ts} +4 -4
- package/extensions/rewind/{runtime.ts → state/runtime.ts} +11 -11
- package/extensions/rewind/{snapshot-run.ts → state/snapshot-run.ts} +1 -1
- package/extensions/rewind/{timeline.ts → state/timeline.ts} +2 -2
- package/extensions/rewind/{format.ts → text/format.ts} +3 -3
- package/extensions/rewind/{git.ts → transport/git.ts} +2 -2
- package/extensions/rewind/{restore.ts → transport/restore.ts} +2 -2
- package/extensions/rewind/{snapshot.ts → transport/snapshot.ts} +1 -1
- package/extensions/rewind/{store.ts → transport/store.ts} +1 -1
- package/extensions/rewind/{checklist.ts → view/checklist.ts} +6 -6
- package/extensions/rewind/{hunk-picker.ts → view/hunk-picker.ts} +8 -8
- package/extensions/rewind/{path-picker.ts → view/path-picker.ts} +7 -7
- package/extensions/rewind/{review-tui.ts → view/review-tui.ts} +11 -11
- package/extensions/rewind/{rewind-tui.ts → view/rewind-tui.ts} +17 -11
- package/extensions/rewind/{status.ts → view/status.ts} +4 -4
- package/extensions/session-bus/commands.ts +17 -69
- package/extensions/session-bus/format.ts +22 -45
- package/extensions/session-bus/index.ts +13 -7
- package/extensions/session-bus/join-request.ts +40 -0
- package/extensions/session-bus/mail-watch.ts +134 -0
- package/extensions/session-bus/notice.ts +32 -4
- package/{lib/claims → extensions/session-bus}/overlap.ts +11 -18
- package/extensions/session-bus/protocol.ts +19 -49
- package/extensions/session-bus/resolve.ts +67 -0
- package/extensions/session-bus/result.ts +16 -51
- package/extensions/session-bus/rows.ts +11 -5
- package/extensions/session-bus/runtime.ts +220 -267
- package/extensions/session-bus/store.ts +55 -7
- package/extensions/session-bus/tools.ts +31 -66
- package/extensions/session-bus/tui.ts +5 -5
- package/extensions/session-bus/types.ts +23 -14
- package/extensions/subagent/agents.ts +9 -8
- package/extensions/subagent/commands.ts +2 -1
- package/extensions/subagent/invocation.ts +4 -2
- package/extensions/team/commands.ts +145 -0
- package/extensions/team/dispatch.ts +133 -0
- package/extensions/team/format.ts +119 -0
- package/extensions/team/index.ts +34 -0
- package/extensions/team/schema.ts +29 -0
- package/extensions/team/tools.ts +267 -0
- package/extensions/team/types.ts +65 -0
- package/extensions/todo/index.ts +4 -1
- package/extensions/todo/nudge.ts +8 -2
- package/extensions/web-access/fetch/page.ts +4 -1
- package/extensions/web-access/fetch/structured.ts +447 -0
- package/extensions/web-access/fetch/tool.ts +2 -1
- package/extensions/web-access/search/tool.ts +2 -1
- package/extensions/wechat/bridge.ts +106 -251
- package/extensions/wechat/{commands.ts → commands/commands.ts} +26 -18
- package/extensions/wechat/{doctor.ts → commands/doctor.ts} +5 -5
- package/extensions/wechat/{history.ts → commands/history.ts} +1 -1
- package/extensions/wechat/{login.ts → commands/login.ts} +3 -3
- package/extensions/wechat/{slash.ts → commands/slash.ts} +38 -2
- package/extensions/wechat/dialog/channels.ts +136 -0
- package/extensions/wechat/{interaction.ts → dialog/interaction.ts} +37 -37
- package/extensions/wechat/{prompt.ts → dialog/prompt.ts} +2 -2
- package/extensions/wechat/{remote-ui.ts → dialog/remote-ui.ts} +1 -1
- package/extensions/wechat/index.ts +10 -10
- package/extensions/wechat/intake.ts +245 -0
- package/extensions/wechat/{admission.ts → peer/admission.ts} +2 -2
- package/extensions/wechat/{peer-session.ts → peer/peer-session.ts} +2 -2
- package/extensions/wechat/{peer-worker.ts → peer/peer-worker.ts} +100 -22
- package/extensions/wechat/{presence.ts → peer/presence.ts} +8 -7
- package/extensions/wechat/{reply-pipeline.ts → peer/reply-pipeline.ts} +1 -1
- package/extensions/wechat/{credentials.ts → state/credentials.ts} +2 -2
- package/extensions/wechat/{state.ts → state/state.ts} +14 -2
- package/extensions/wechat/{format.ts → text/format.ts} +3 -3
- package/extensions/wechat/{notices.ts → text/notices.ts} +8 -0
- package/extensions/wechat/tools.ts +29 -12
- package/extensions/wechat/{client.ts → transport/client.ts} +26 -2
- package/extensions/wechat/{deliver.ts → transport/deliver.ts} +72 -28
- package/extensions/wechat/{inbound.ts → transport/inbound.ts} +5 -5
- package/extensions/wechat/{media.ts → transport/media.ts} +11 -5
- package/extensions/wechat/transport/outbound.ts +142 -0
- package/extensions/wechat/{poll.ts → transport/poll.ts} +3 -3
- package/extensions/wechat/types.ts +10 -1
- package/extensions/wechat/{login-screen.ts → view/login-screen.ts} +4 -4
- package/extensions/wechat/{status-screen.ts → view/status-screen.ts} +31 -26
- package/extensions/worktree/commands.ts +24 -17
- package/extensions/worktree/exit.ts +12 -6
- package/extensions/worktree/git.ts +22 -14
- package/extensions/worktree/guard.ts +18 -290
- package/extensions/worktree/handoff.ts +10 -2
- package/extensions/worktree/index.ts +2 -1
- package/extensions/worktree/lifecycle.ts +2 -0
- package/extensions/worktree/registry.ts +20 -11
- package/extensions/worktree/root-tools.ts +13 -3
- package/extensions/worktree/runtime.ts +8 -2
- package/extensions/worktree/screen.ts +15 -8
- package/extensions/worktree/shell.ts +303 -0
- package/extensions/worktree/state.ts +1 -1
- package/extensions/worktree/status.ts +42 -9
- package/extensions/worktree/tools.ts +19 -13
- package/lib/README.md +11 -8
- package/lib/board/card.ts +479 -0
- package/lib/board/links.ts +141 -0
- package/lib/board/page-pointer.ts +310 -0
- package/lib/board/round-text.ts +81 -0
- package/lib/board/round.ts +94 -0
- package/lib/board/rules.ts +437 -0
- package/lib/board/session.ts +105 -0
- package/lib/board/store.ts +497 -0
- package/lib/board/view.ts +234 -0
- package/lib/errors.ts +12 -4
- package/lib/format.ts +35 -0
- package/lib/http/guard.ts +112 -0
- package/lib/http/index.ts +16 -5
- package/lib/http/serve.ts +237 -0
- package/lib/http/server.ts +12 -10
- package/lib/http/sink.ts +135 -0
- package/lib/http/token.ts +38 -0
- package/lib/plan-entry.ts +11 -0
- package/lib/policy.ts +25 -2
- package/lib/requirements.ts +6 -7
- package/lib/session-label.ts +64 -0
- package/lib/session-roster.ts +201 -0
- package/lib/session-tool.ts +184 -0
- package/lib/spawn.ts +37 -3
- package/lib/stray-fields.ts +45 -0
- package/lib/tool-names.ts +38 -2
- package/lib/tui/glyphs.ts +3 -2
- package/lib/web/README.md +57 -4
- package/lib/web/client-runtime.ts +123 -0
- package/lib/web/components.ts +5 -0
- package/lib/web/index.ts +4 -2
- package/lib/web/markup.ts +88 -1
- package/lib/web/names.ts +42 -0
- package/lib/web/runtime.ts +7 -1
- package/lib/web/tokens.ts +25 -2
- package/package.json +26 -14
- package/skills/next/SKILL.md +4 -3
- package/skills/project-docs/SKILL.md +133 -0
- package/skills/project-docs/references/audit.md +131 -0
- package/skills/project-docs/references/changelog.md +105 -0
- package/skills/project-docs/references/discovery.md +127 -0
- package/skills/project-docs/references/readme.md +80 -0
- package/skills/project-docs/references/release.md +84 -0
- package/skills/project-docs/references/roadmap.md +93 -0
- package/skills/project-docs/scripts/state.ts +316 -0
- package/skills/refactor/SKILL.md +100 -125
- package/skills/refactor/references/handoff.md +35 -0
- package/skills/refactor/references/signals.md +54 -0
- package/skills/refactor/scripts/inventory.ts +587 -0
- package/extensions/bytefly/panel/tui/extensions-view.ts +0 -40
- package/extensions/bytefly/panel/tui/skills-view.ts +0 -50
- package/extensions/file-browse/README.md +0 -506
- package/extensions/file-browse/files.ts +0 -564
- package/extensions/file-browse/server.ts +0 -141
- package/extensions/job/process.ts +0 -35
- package/extensions/plan/split.ts +0 -87
- package/extensions/session-bus/README.md +0 -247
- package/extensions/session-bus/team.ts +0 -102
- package/extensions/wechat/outbound.ts +0 -100
- package/lib/plans/sections.ts +0 -172
- package/lib/plans/setting.ts +0 -52
- package/lib/plans/split.ts +0 -215
- package/lib/plans/store.ts +0 -19
- package/lib/plans/team.ts +0 -151
- package/skills/changelog/SKILL.md +0 -148
- package/skills/parallel-dev/SKILL.md +0 -150
- /package/extensions/file-browse/{rev.ts → live/rev.ts} +0 -0
- /package/extensions/file-browse/{diff.ts → text/diff.ts} +0 -0
- /package/extensions/file-browse/{fuzzy.ts → text/fuzzy.ts} +0 -0
- /package/extensions/file-browse/{live-client.ts → view/live-client.ts} +0 -0
- /package/extensions/file-browse/{palette.ts → view/palette.ts} +0 -0
- /package/extensions/rewind/{patch.ts → text/patch.ts} +0 -0
- /package/extensions/rewind/{paths.ts → text/paths.ts} +0 -0
- /package/extensions/rewind/{queue.ts → transport/queue.ts} +0 -0
- /package/extensions/rewind/{temp-index.ts → transport/temp-index.ts} +0 -0
- /package/extensions/wechat/{lock.ts → state/lock.ts} +0 -0
- /package/extensions/wechat/{channel.ts → text/channel.ts} +0 -0
|
@@ -0,0 +1,519 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The client's renderer: snapshot in, DOM out — the toolbar, the lanes, the cards,
|
|
3
|
+
* and the card dialog's contents.
|
|
4
|
+
*
|
|
5
|
+
* This file runs in the browser, where no type checker sees it, so it holds to one
|
|
6
|
+
* rule: **every value from the snapshot is set as `textContent`** — never as
|
|
7
|
+
* `innerHTML`, never into a template string. A card title is model-written and a note
|
|
8
|
+
* can be anything a person typed, so the only safe renderer is one that never parses
|
|
9
|
+
* them.
|
|
10
|
+
*
|
|
11
|
+
* A frame replaces what changed and leaves the rest alone: a lane is rebuilt only
|
|
12
|
+
* when its cards' revisions moved, so the filter input, a collapsed lane, the scroll
|
|
13
|
+
* offset, and an open dialog all survive a peer's write.
|
|
14
|
+
*
|
|
15
|
+
* The client modules share one namespace, `window.bfBoard`: this one paints,
|
|
16
|
+
* `team.ts` draws the round, `dialog.ts` opens a card, `keys.ts` binds, and `live.ts`
|
|
17
|
+
* owns the frame stream and the wiring.
|
|
18
|
+
*
|
|
19
|
+
* `plural`, `tabTitle`, `matchesQuery`, `el`, `typing`, and `paintStatus` are not written
|
|
20
|
+
* here. Their one bodies live in `lib/` beside the rules they express, and the assembler
|
|
21
|
+
* hands the source in — a second copy in the bundle is a second wording for `1 card`, a
|
|
22
|
+
* second shape for the tab, a filter that finds a different card than the tool does, or an
|
|
23
|
+
* element builder and a typing test that drift from the review page's.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* The renderer, with what it needs spliced in: the counts' wording, the tab title's
|
|
28
|
+
* shape, what a query matches, and the browser runtime's element builder and typing test —
|
|
29
|
+
* each written once in `lib/` and handed in as source.
|
|
30
|
+
*/
|
|
31
|
+
export function renderJs(rules: {
|
|
32
|
+
plural: string;
|
|
33
|
+
tabTitle: string;
|
|
34
|
+
matches: string;
|
|
35
|
+
age: string;
|
|
36
|
+
runtime: string;
|
|
37
|
+
}): string {
|
|
38
|
+
return renderTemplate
|
|
39
|
+
.replace("__PLURAL__", rules.plural)
|
|
40
|
+
.replace("__TAB_TITLE__", rules.tabTitle)
|
|
41
|
+
.replace("__MATCHES__", rules.matches)
|
|
42
|
+
.replace("__AGE__", rules.age)
|
|
43
|
+
.replace("__RUNTIME__", rules.runtime);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const renderTemplate = String.raw`(function () {
|
|
47
|
+
"use strict";
|
|
48
|
+
var board = window.bfBoard || (window.bfBoard = {});
|
|
49
|
+
|
|
50
|
+
/** Lanes in the order the server sends them, with the words a reader expects. */
|
|
51
|
+
var LANE_LABEL = { todo: "To do", doing: "Doing", blocked: "Blocked", done: "Done" };
|
|
52
|
+
var LANE_EMPTY = { todo: "nothing waiting", doing: "nothing in flight", blocked: "nothing blocked", done: "nothing finished yet" };
|
|
53
|
+
/** Path chips a lane card shows before it counts the rest. */
|
|
54
|
+
var PATH_CHIPS = 3;
|
|
55
|
+
/**
|
|
56
|
+
* Finished cards a lane may hold before it starts folded.
|
|
57
|
+
*
|
|
58
|
+
* A board of 26 cards is 19 of them finished, and an open Done lane is four times the
|
|
59
|
+
* height of the three that carry work. Three or four is work a reader still checks;
|
|
60
|
+
* past this it is history, and the count in the head says how much.
|
|
61
|
+
*/
|
|
62
|
+
var DONE_FOLD_AT = 6;
|
|
63
|
+
/** The two tones a reason line can take, by the kind reasonOf reports. */
|
|
64
|
+
var REASON_TONE = { blocked: N.cardReasonBlocked, unplanned: N.cardReasonUnplanned };
|
|
65
|
+
|
|
66
|
+
__RUNTIME__
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* One button that calls back, so nothing on the page is a click handler in markup.
|
|
70
|
+
*
|
|
71
|
+
* 'plain' is the bordered kit button, 'ghost' the text one: a card's primary
|
|
72
|
+
* action (Claim, Move) is bordered and the rest are text, which is what makes one
|
|
73
|
+
* of three controls on a card the obvious one.
|
|
74
|
+
*/
|
|
75
|
+
function actionButton(label, onClick, variant) {
|
|
76
|
+
var kind = variant === "plain" ? "" : variant ? " bf-btn--" + variant : " bf-btn--ghost";
|
|
77
|
+
var node = el("button", "bf-btn" + kind, label);
|
|
78
|
+
node.type = "button";
|
|
79
|
+
node.addEventListener("click", onClick);
|
|
80
|
+
return node;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** A kit chip, with a tone when the meaning is a state. */
|
|
84
|
+
function chip(text, tone) {
|
|
85
|
+
return el("span", "bf-chip" + (tone ? " bf-chip--" + tone : ""), text);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* "12m", "3h", "2d" — an age, compact enough for a meta line.
|
|
90
|
+
*
|
|
91
|
+
* The transcript prints the same rule for the same fact, so the body is spliced in from
|
|
92
|
+
* lib/format.ts rather than written twice. No backticks in here: this is a template.
|
|
93
|
+
*/
|
|
94
|
+
var ageText = __AGE__;
|
|
95
|
+
function ago(at) { return ageText(Date.now(), at); }
|
|
96
|
+
|
|
97
|
+
/** Bytes, the way the footer says them. */
|
|
98
|
+
function size(bytes) {
|
|
99
|
+
if (bytes < 1024) return bytes + " B";
|
|
100
|
+
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + " kB";
|
|
101
|
+
return (bytes / (1024 * 1024)).toFixed(1) + " MB";
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** Every card the snapshot carries, in lane order. */
|
|
105
|
+
function cards(view) {
|
|
106
|
+
var all = [];
|
|
107
|
+
for (var i = 0; i < view.lanes.length; i++) all = all.concat(view.lanes[i].cards);
|
|
108
|
+
return all;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** One card by id, or undefined when the last frame no longer has it. */
|
|
112
|
+
function findCard(view, id) {
|
|
113
|
+
var all = cards(view);
|
|
114
|
+
for (var i = 0; i < all.length; i++) if (all[i].id === id) return all[i];
|
|
115
|
+
return undefined;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** The one line under a card's meta: why it sits where it does. */
|
|
119
|
+
function reasonOf(card, view) {
|
|
120
|
+
var last = card.notes.length > 0 ? card.notes[card.notes.length - 1] : undefined;
|
|
121
|
+
// An order is the strongest reason a card is not being worked on, and the one a
|
|
122
|
+
// reader cannot see from the lane: it comes before the note that says the same.
|
|
123
|
+
// The sentence is the snapshot's, so this line and the tool's refusal name one list.
|
|
124
|
+
if (card.waits !== undefined) return { text: "waits on " + card.waits, kind: "waiting" };
|
|
125
|
+
if (card.status === "blocked") {
|
|
126
|
+
// The lane already says blocked and the tone already says danger; the line says why.
|
|
127
|
+
// The recorded reason comes first: the note is what somebody happened to write last,
|
|
128
|
+
// and it is the reason a move to blocked asks for. The slot carries the reason alone —
|
|
129
|
+
// the lane head already says blocked, and a status prefix in it would be a second voice.
|
|
130
|
+
if (card.blockedBy) return { text: card.blockedBy, kind: "blocked" };
|
|
131
|
+
return { text: last ? last.text : "blocked, with nothing recorded", kind: "blocked" };
|
|
132
|
+
}
|
|
133
|
+
// An offer is a decision the lead already made once: the lane says nobody holds it, and
|
|
134
|
+
// this says how long the broadcast has gone unanswered, which is the difference between
|
|
135
|
+
// nobody having looked and nobody taking it.
|
|
136
|
+
if (card.status === "todo" && card.offeredAt !== undefined) {
|
|
137
|
+
return { text: "offered " + ago(card.offeredAt) + " ago · nobody took it", kind: "waiting" };
|
|
138
|
+
}
|
|
139
|
+
if (card.status === "doing" && !card.plan) {
|
|
140
|
+
return { text: "no plan recorded", kind: "unplanned" };
|
|
141
|
+
}
|
|
142
|
+
if (card.status === "doing") return { text: "planned", kind: "plan" };
|
|
143
|
+
if (card.status === "done") {
|
|
144
|
+
var landed = card.landed ? " · landed " + card.landed.commit : "";
|
|
145
|
+
return { text: "✓ done" + (card.owner ? " by " + card.owner.label : "") + landed, kind: "done" };
|
|
146
|
+
}
|
|
147
|
+
return last ? { text: last.text, kind: "note" } : undefined;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** A path hint, or the +N chip that points at the rest. */
|
|
151
|
+
function paths(card, limit) {
|
|
152
|
+
var list = el("ul", N.cardPaths);
|
|
153
|
+
var shown = card.paths.slice(0, limit);
|
|
154
|
+
for (var i = 0; i < shown.length; i++) {
|
|
155
|
+
var item = el("li", "");
|
|
156
|
+
item.appendChild(chip(shown[i]));
|
|
157
|
+
list.appendChild(item);
|
|
158
|
+
}
|
|
159
|
+
if (card.paths.length > limit) {
|
|
160
|
+
var rest = el("li", "");
|
|
161
|
+
rest.appendChild(chip("+" + (card.paths.length - limit)));
|
|
162
|
+
list.appendChild(rest);
|
|
163
|
+
}
|
|
164
|
+
return list;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/** One card in its lane. */
|
|
168
|
+
function cardNode(card, view) {
|
|
169
|
+
var item = el("li", N.card);
|
|
170
|
+
item.setAttribute("data-card", card.id);
|
|
171
|
+
item.setAttribute("data-status", card.status);
|
|
172
|
+
item.setAttribute("data-rev", String(card.rev));
|
|
173
|
+
|
|
174
|
+
var head = el("p", N.cardHead);
|
|
175
|
+
head.appendChild(el("span", N.cardId, "#" + card.id));
|
|
176
|
+
var title = actionButton(card.title, function () {
|
|
177
|
+
board.openCard(card.id);
|
|
178
|
+
});
|
|
179
|
+
title.className = N.cardTitle;
|
|
180
|
+
head.appendChild(title);
|
|
181
|
+
item.appendChild(head);
|
|
182
|
+
|
|
183
|
+
var meta = el("p", N.cardMeta);
|
|
184
|
+
meta.appendChild(card.owner ? chip(card.owner.label, "accent") : el("span", "", "unassigned"));
|
|
185
|
+
// The order inside a lane is the priority, so an urgent card says so: an order
|
|
186
|
+
// nobody can see reads as an accident. The lane's colour stays the card's own.
|
|
187
|
+
if (card.priority !== "normal") meta.appendChild(chip(card.priority));
|
|
188
|
+
// The stamp, so the clock can keep the age honest between frames: a board can be
|
|
189
|
+
// quiet for ten minutes while the page still says "live".
|
|
190
|
+
var age = el("span", "", ago(card.updatedAt));
|
|
191
|
+
age.setAttribute("data-at", String(card.updatedAt));
|
|
192
|
+
meta.appendChild(age);
|
|
193
|
+
item.appendChild(meta);
|
|
194
|
+
|
|
195
|
+
if (card.paths.length > 0) item.appendChild(paths(card, PATH_CHIPS));
|
|
196
|
+
|
|
197
|
+
var reason = reasonOf(card, view);
|
|
198
|
+
if (reason) {
|
|
199
|
+
// The tone is a lookup, not a suffix: a name assembled from a prefix is a name no
|
|
200
|
+
// test can compare with the stylesheet.
|
|
201
|
+
var tone = REASON_TONE[reason.kind];
|
|
202
|
+
var line = el("p", tone === undefined ? N.cardReason : N.cardReason + " " + tone, reason.text);
|
|
203
|
+
// It clamps to one line on the card, so the full sentence is the tooltip.
|
|
204
|
+
line.title = reason.text;
|
|
205
|
+
item.appendChild(line);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return item;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* The cards as the page draws them.
|
|
213
|
+
*
|
|
214
|
+
* A folded lane keeps its cards in the document, and so does a panel the other tab
|
|
215
|
+
* hides: the box is the only honest test of whether a reader can see one, and it is
|
|
216
|
+
* what j/k walks and what the dialog's position counts.
|
|
217
|
+
*/
|
|
218
|
+
function drawnCards() {
|
|
219
|
+
var nodes = document.querySelectorAll(".board-card");
|
|
220
|
+
var seen = [];
|
|
221
|
+
for (var i = 0; i < nodes.length; i++) {
|
|
222
|
+
if (nodes[i].hidden) continue;
|
|
223
|
+
if (!nodes[i].getClientRects || nodes[i].getClientRects().length === 0) continue;
|
|
224
|
+
seen.push(nodes[i]);
|
|
225
|
+
}
|
|
226
|
+
return seen;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/** The toolbar's identity, progress, footer, and the tab's own name. */
|
|
230
|
+
function renderChrome(view) {
|
|
231
|
+
var me = document.getElementById("board-me");
|
|
232
|
+
if (me) {
|
|
233
|
+
me.textContent = view.me.label;
|
|
234
|
+
me.title = "This page is served as " + view.me.label + ", and it cannot change a card";
|
|
235
|
+
}
|
|
236
|
+
var progress = document.getElementById("board-progress");
|
|
237
|
+
if (progress) {
|
|
238
|
+
var total = view.counts.total;
|
|
239
|
+
// One or two cards is not a ratio: an empty bar beside "0/1 done" is noise.
|
|
240
|
+
progress.hidden = total < 3;
|
|
241
|
+
var fill = document.getElementById("board-progress-fill");
|
|
242
|
+
if (fill) fill.style.width = total === 0 ? "0%" : Math.round((view.counts.done / total) * 100) + "%";
|
|
243
|
+
var text = document.getElementById("board-progress-text");
|
|
244
|
+
if (text) text.textContent = view.counts.done + "/" + total + " done";
|
|
245
|
+
}
|
|
246
|
+
var where = document.getElementById("board-where");
|
|
247
|
+
if (where) {
|
|
248
|
+
// A footer that only counts read cards would say "2 cards" over a board holding
|
|
249
|
+
// three files, so the unreadable ones are named beside the size.
|
|
250
|
+
where.textContent =
|
|
251
|
+
"store " + view.where + " · " + plural(view.counts.total, "card") + " · " + size(view.bytes) +
|
|
252
|
+
(view.skipped > 0 ? " · " + plural(view.skipped, "unreadable") : "");
|
|
253
|
+
}
|
|
254
|
+
// The tab has to tell two boards apart, and a board's own name is the last segment of
|
|
255
|
+
// its store: one board per repository, so that key is the subject. The count is what
|
|
256
|
+
// a browser full of tabs is read for, and an empty board says nothing rather than 0.
|
|
257
|
+
var open = view.counts.total - view.counts.done;
|
|
258
|
+
document.title = tabTitle("board", view.where.split("/").pop(), open > 0 ? open + " open" : undefined);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/** 1 card, 2 cards — the one body, handed in by the bundle's assembler. */
|
|
262
|
+
var plural = __PLURAL__;
|
|
263
|
+
/** board · my-pi-agent · 3 open, the tab's one shape, handed in the same way. */
|
|
264
|
+
var tabTitle = __TAB_TITLE__;
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Rewrite every age already on the page from its own timestamp.
|
|
268
|
+
*
|
|
269
|
+
* A frame carries the ages of the moment it was built, so on a board nobody is
|
|
270
|
+
* writing to the cards would slowly lie about how long they have sat there. The
|
|
271
|
+
* ticker calls this; it touches nothing else, so a filter, a folded lane, or an
|
|
272
|
+
* open dialog survives it.
|
|
273
|
+
*/
|
|
274
|
+
function refreshAges() {
|
|
275
|
+
var nodes = document.querySelectorAll("[data-at]");
|
|
276
|
+
for (var i = 0; i < nodes.length; i++) {
|
|
277
|
+
var next = ago(Number(nodes[i].getAttribute("data-at")));
|
|
278
|
+
if (nodes[i].textContent !== next) nodes[i].textContent = next;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/** Whether the filter keeps a card: the module's one predicate, spliced in as source. */
|
|
283
|
+
var matches = __MATCHES__;
|
|
284
|
+
|
|
285
|
+
/** The lane section for one status, created once and then reused. */
|
|
286
|
+
function laneNode(lane) {
|
|
287
|
+
var section = document.querySelector('.board-lane[data-status="' + lane.status + '"]');
|
|
288
|
+
if (section) return section;
|
|
289
|
+
section = el("section", N.lane);
|
|
290
|
+
section.setAttribute("data-status", lane.status);
|
|
291
|
+
var head = el("h2", N.laneHead);
|
|
292
|
+
var toggle = actionButton(LANE_LABEL[lane.status] || lane.status, function () {
|
|
293
|
+
board.toggleLane(lane.status);
|
|
294
|
+
});
|
|
295
|
+
toggle.className = N.laneToggle;
|
|
296
|
+
toggle.setAttribute("aria-expanded", "true");
|
|
297
|
+
toggle.setAttribute("aria-controls", "lane-" + lane.status);
|
|
298
|
+
toggle.id = "lane-head-" + lane.status;
|
|
299
|
+
head.appendChild(el("span", N.laneDot));
|
|
300
|
+
head.appendChild(toggle);
|
|
301
|
+
head.appendChild(el("span", N.laneCount));
|
|
302
|
+
section.appendChild(head);
|
|
303
|
+
var list = el("ol", N.laneList);
|
|
304
|
+
list.id = "lane-" + lane.status;
|
|
305
|
+
section.appendChild(list);
|
|
306
|
+
section.appendChild(el("p", N.laneEmpty, LANE_EMPTY[lane.status] || "nothing"));
|
|
307
|
+
return section;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/** Draw the lanes, rebuilding only one whose cards actually moved. */
|
|
311
|
+
function renderLanes(view) {
|
|
312
|
+
var lanes = document.getElementById("board-lanes");
|
|
313
|
+
if (!lanes) return;
|
|
314
|
+
if (lanes.getAttribute("data-skeleton") === "1") {
|
|
315
|
+
// The placeholders have done their job; the real lanes replace them whole.
|
|
316
|
+
lanes.textContent = "";
|
|
317
|
+
lanes.removeAttribute("data-skeleton");
|
|
318
|
+
}
|
|
319
|
+
var query = board.query || "";
|
|
320
|
+
var empty = document.getElementById("board-no-cards");
|
|
321
|
+
if (empty) empty.hidden = view.counts.total !== 0;
|
|
322
|
+
lanes.hidden = view.counts.total === 0;
|
|
323
|
+
for (var i = 0; i < view.lanes.length; i++) {
|
|
324
|
+
var lane = view.lanes[i];
|
|
325
|
+
var section = laneNode(lane);
|
|
326
|
+
if (section.parentNode !== lanes) lanes.appendChild(section);
|
|
327
|
+
// Finished work starts folded, once: the default is what the page opens with, and
|
|
328
|
+
// toggleLane writes a boolean from then on, so a reader who unfolds Done keeps it
|
|
329
|
+
// unfolded through every frame a peer's write causes.
|
|
330
|
+
if (lane.status === "done" && lane.cards.length >= DONE_FOLD_AT) {
|
|
331
|
+
board.collapsed = board.collapsed || {};
|
|
332
|
+
if (board.collapsed[lane.status] === undefined) board.collapsed[lane.status] = true;
|
|
333
|
+
}
|
|
334
|
+
// A lane holding nothing is a rail: its dot, its word, and its count, in the width
|
|
335
|
+
// the fact deserves. The filter never does this — filtering is a view of the cards,
|
|
336
|
+
// not a statement that the lane has none.
|
|
337
|
+
section.setAttribute("data-empty", lane.cards.length === 0 ? "true" : "false");
|
|
338
|
+
// The lane's share of the row follows the work it holds: the sheet turns this number
|
|
339
|
+
// into a flex-grow, so two cards earn more width than one and a title is not wrapped
|
|
340
|
+
// for want of a column the lane is not using. A count, never text.
|
|
341
|
+
section.style.setProperty("--lane-count", String(lane.cards.length));
|
|
342
|
+
var shown = lane.cards.filter(function (card) { return matches(card, query); });
|
|
343
|
+
var sig = lane.cards.map(function (card) { return card.id + ":" + card.rev; }).join(",") + "|" + query;
|
|
344
|
+
if (section.getAttribute("data-sig") !== sig) {
|
|
345
|
+
section.setAttribute("data-sig", sig);
|
|
346
|
+
var list = section.querySelector(".board-lane__list");
|
|
347
|
+
list.textContent = "";
|
|
348
|
+
for (var j = 0; j < shown.length; j++) list.appendChild(cardNode(shown[j], view));
|
|
349
|
+
}
|
|
350
|
+
var count = section.querySelector(".board-lane__count");
|
|
351
|
+
if (count) {
|
|
352
|
+
count.textContent = query === "" || shown.length === lane.cards.length
|
|
353
|
+
? String(lane.cards.length)
|
|
354
|
+
: shown.length + "/" + lane.cards.length;
|
|
355
|
+
}
|
|
356
|
+
var none = section.querySelector(".board-lane__empty");
|
|
357
|
+
if (none) {
|
|
358
|
+
none.hidden = shown.length !== 0;
|
|
359
|
+
none.textContent = query !== "" && lane.cards.length > 0 ? "no match" : LANE_EMPTY[lane.status] || "nothing";
|
|
360
|
+
}
|
|
361
|
+
var collapsed = board.collapsed && board.collapsed[lane.status] === true;
|
|
362
|
+
var list = section.querySelector(".board-lane__list");
|
|
363
|
+
if (list) list.hidden = collapsed;
|
|
364
|
+
// A folded lane shows nothing, so it takes the rail's width the way an empty one
|
|
365
|
+
// does: the count in its head is the whole fact, and the lanes with cards keep the
|
|
366
|
+
// width that would otherwise be a tray of nothing.
|
|
367
|
+
section.setAttribute("data-folded", collapsed ? "true" : "false");
|
|
368
|
+
var toggle = section.querySelector(".board-lane__toggle");
|
|
369
|
+
if (toggle) toggle.setAttribute("aria-expanded", String(!collapsed));
|
|
370
|
+
}
|
|
371
|
+
renderPicker(view);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/** The lane selector the narrow layout shows instead of four columns. */
|
|
375
|
+
function renderPicker(view) {
|
|
376
|
+
var picker = document.getElementById("board-picker");
|
|
377
|
+
if (!picker) return;
|
|
378
|
+
picker.textContent = "";
|
|
379
|
+
for (var i = 0; i < view.lanes.length; i++) {
|
|
380
|
+
(function (lane) {
|
|
381
|
+
var item = el("button", "bf-segmented__item", (LANE_LABEL[lane.status] || lane.status) + " " + lane.cards.length);
|
|
382
|
+
item.type = "button";
|
|
383
|
+
item.setAttribute("role", "tab");
|
|
384
|
+
item.setAttribute("data-lane", lane.status);
|
|
385
|
+
item.addEventListener("click", function () { board.showLane(lane.status); });
|
|
386
|
+
picker.appendChild(item);
|
|
387
|
+
})(view.lanes[i]);
|
|
388
|
+
}
|
|
389
|
+
board.applyLane();
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/** Fill the card dialog's contents from a card, without opening or closing it. */
|
|
393
|
+
function fillDialog(card, view) {
|
|
394
|
+
document.getElementById("card-dialog-title").textContent = card.title;
|
|
395
|
+
document.getElementById("card-dialog-id").textContent = "#" + card.id;
|
|
396
|
+
// The lane is a status of its own, not a word inside a sentence: the same dot and
|
|
397
|
+
// label a lane head wears, so the dialog and the board speak the same vocabulary.
|
|
398
|
+
var state = document.getElementById("card-dialog-state");
|
|
399
|
+
state.setAttribute("data-status", card.status);
|
|
400
|
+
state.textContent = card.status;
|
|
401
|
+
|
|
402
|
+
// One fact per row of the ladder, so a reader can pick out a value instead of
|
|
403
|
+
// hunting for it in a sentence. A row with nothing to say takes its label with it:
|
|
404
|
+
// an empty cell under a heading reads as a missing value, not as an absence.
|
|
405
|
+
var pair = function (name, shown) {
|
|
406
|
+
var dt = document.getElementById("card-dialog-" + name + "-label");
|
|
407
|
+
var dd = document.getElementById("card-dialog-" + name);
|
|
408
|
+
if (dt) dt.hidden = !shown;
|
|
409
|
+
if (dd) dd.hidden = !shown;
|
|
410
|
+
return dd;
|
|
411
|
+
};
|
|
412
|
+
var setRow = function (name, shown, value, full) {
|
|
413
|
+
var dd = pair(name, shown);
|
|
414
|
+
dd.textContent = shown ? value : "";
|
|
415
|
+
if (full === undefined) dd.removeAttribute("title");
|
|
416
|
+
else dd.setAttribute("title", full);
|
|
417
|
+
};
|
|
418
|
+
|
|
419
|
+
var owner = document.getElementById("card-dialog-owner");
|
|
420
|
+
owner.textContent = "";
|
|
421
|
+
owner.appendChild(card.owner ? chip(card.owner.label, "accent") : document.createTextNode("unassigned"));
|
|
422
|
+
pair("owner", true);
|
|
423
|
+
|
|
424
|
+
var priority = pair("priority", card.priority !== "normal");
|
|
425
|
+
priority.textContent = "";
|
|
426
|
+
if (card.priority !== "normal") priority.appendChild(chip(card.priority));
|
|
427
|
+
|
|
428
|
+
setRow("updated", true, ago(card.updatedAt));
|
|
429
|
+
// How long the card has been in the lane it is in now, which updatedAt cannot say:
|
|
430
|
+
// a note filed against a blocked card moves that stamp without moving the card. Absent
|
|
431
|
+
// for a card written before the log existed, so the row takes its label with it.
|
|
432
|
+
setRow("lane", card.since !== undefined, card.status + " · " + ago(card.since));
|
|
433
|
+
setRow("by", true, card.by);
|
|
434
|
+
setRow("landed", card.landed !== undefined, card.landed ? card.landed.commit : "", card.landed ? card.landed.commit : undefined);
|
|
435
|
+
|
|
436
|
+
// The dialog and the card face say one thing, from one field: the ids it was linked
|
|
437
|
+
// to are the storage, and a reader is owed the lanes that make them a decision.
|
|
438
|
+
setRow("depends", card.waits !== undefined, card.waits || "", card.waits);
|
|
439
|
+
// The other side of the same link: what leaving this card undone is holding up, which
|
|
440
|
+
// is the one thing a card's own row cannot say.
|
|
441
|
+
setRow("dependents", card.dependents !== undefined, card.dependents || "", card.dependents);
|
|
442
|
+
|
|
443
|
+
// A long plan path elides on one line, and the whole of it is on the hover title and
|
|
444
|
+
// in the text, so it stays copyable: the reason to open a card is often its plan.
|
|
445
|
+
setRow("plan", Boolean(card.plan), card.plan || "", card.plan || undefined);
|
|
446
|
+
|
|
447
|
+
pair("paths", card.paths.length > 0);
|
|
448
|
+
var pathList = document.getElementById("card-dialog-path-list");
|
|
449
|
+
pathList.textContent = "";
|
|
450
|
+
for (var i = 0; i < card.paths.length; i++) {
|
|
451
|
+
var item = el("li", "");
|
|
452
|
+
var hint = chip(card.paths[i]);
|
|
453
|
+
hint.className += " " + N.dialogPath;
|
|
454
|
+
hint.title = card.paths[i];
|
|
455
|
+
item.appendChild(hint);
|
|
456
|
+
pathList.appendChild(item);
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
var notes = document.getElementById("card-dialog-notes");
|
|
460
|
+
notes.textContent = "";
|
|
461
|
+
for (var j = 0; j < card.notes.length; j++) {
|
|
462
|
+
var note = card.notes[j];
|
|
463
|
+
// The prose is the record and reads at body size; who wrote it and when is its
|
|
464
|
+
// footnote, so it follows the sentence instead of standing over it.
|
|
465
|
+
var row = el("li", N.dialogNote);
|
|
466
|
+
row.appendChild(el("p", N.dialogNoteText, note.text));
|
|
467
|
+
var foot = el("p", N.dialogNoteFoot);
|
|
468
|
+
foot.appendChild(el("span", N.dialogNoteBy, note.by));
|
|
469
|
+
foot.appendChild(el("span", N.dialogNoteAt, ago(note.at)));
|
|
470
|
+
row.appendChild(foot);
|
|
471
|
+
notes.appendChild(row);
|
|
472
|
+
}
|
|
473
|
+
var count = document.getElementById("card-dialog-note-count");
|
|
474
|
+
if (count) count.textContent = card.notes.length === 0 ? " · none" : " · " + card.notes.length;
|
|
475
|
+
var earlier = document.getElementById("card-dialog-earlier");
|
|
476
|
+
earlier.hidden = card.notesDropped === 0;
|
|
477
|
+
earlier.textContent = card.notesDropped === 0 ? "" : card.notesDropped + " earlier notes dropped";
|
|
478
|
+
|
|
479
|
+
// A modal whose title changes is not announced, so walking with j/k says where it
|
|
480
|
+
// landed: the position is counted over the cards as they are drawn, not as stored, so
|
|
481
|
+
// a filter or a folded lane cannot make the number disagree with the screen.
|
|
482
|
+
var where = document.getElementById("card-dialog-where");
|
|
483
|
+
if (where) {
|
|
484
|
+
var drawn = drawnCards();
|
|
485
|
+
var at = 0;
|
|
486
|
+
for (var k = 0; k < drawn.length; k++) {
|
|
487
|
+
if (drawn[k].getAttribute("data-card") === String(card.id)) at = k + 1;
|
|
488
|
+
}
|
|
489
|
+
// Opened from the Team tab there is no drawn board to count along, so the line
|
|
490
|
+
// names the card instead of claiming a position it cannot know.
|
|
491
|
+
where.textContent = drawn.length === 0 ? card.title : "card " + at + " of " + drawn.length + ": " + card.title;
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
/** Paint a whole snapshot. */
|
|
496
|
+
board.render = function (view) {
|
|
497
|
+
board.view = view;
|
|
498
|
+
renderChrome(view);
|
|
499
|
+
renderLanes(view);
|
|
500
|
+
board.renderTeam(view);
|
|
501
|
+
if (board.openId !== undefined) {
|
|
502
|
+
var card = findCard(view, board.openId);
|
|
503
|
+
if (card) fillDialog(card, view);
|
|
504
|
+
else board.closeCard();
|
|
505
|
+
}
|
|
506
|
+
};
|
|
507
|
+
|
|
508
|
+
board.el = el;
|
|
509
|
+
board.typing = typing;
|
|
510
|
+
board.chip = chip;
|
|
511
|
+
board.actionButton = actionButton;
|
|
512
|
+
board.ago = ago;
|
|
513
|
+
board.findCard = findCard;
|
|
514
|
+
board.drawnCards = drawnCards;
|
|
515
|
+
board.matches = matches;
|
|
516
|
+
board.fillDialog = fillDialog;
|
|
517
|
+
board.refreshAges = refreshAges;
|
|
518
|
+
board.plural = plural;
|
|
519
|
+
})();`;
|