@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
|
@@ -8,10 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
export const WEB_CHROME_JS = String.raw` function setStatus(text, kind) {
|
|
11
|
-
if (
|
|
12
|
-
statusEl.textContent = text || "";
|
|
13
|
-
statusEl.title = text || "";
|
|
14
|
-
statusEl.className = kind ? "bf-status bf-status--" + kind : "bf-status";
|
|
11
|
+
if (statusEl) paintStatus(statusEl, text, kind);
|
|
15
12
|
}
|
|
16
13
|
|
|
17
14
|
// The always-visible header chip: connection, comment count, and whether
|
|
@@ -56,7 +53,8 @@ export const WEB_CHROME_JS = String.raw` function setStatus(text, kind) {
|
|
|
56
53
|
titleEl.textContent = data.title;
|
|
57
54
|
titleEl.title = data.title;
|
|
58
55
|
}
|
|
59
|
-
|
|
56
|
+
// The document's own <title> already names the plan, rendered with the page: a second
|
|
57
|
+
// copy of the same sentence in script is a tab title that can disagree with the head.
|
|
60
58
|
}
|
|
61
59
|
|
|
62
60
|
function updateProgress() {
|
|
@@ -1,23 +1,36 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The browser review client, served as `/client.js`.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* the
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* `EventSource` keeps a
|
|
13
|
-
*
|
|
4
|
+
* Served as one script with no build step, assembled from typed source: the element builder,
|
|
5
|
+
* typing test, status paint, and JSON POST come from `lib/web/client-runtime.ts`, and every
|
|
6
|
+
* rule the page has lives in its own `web-*.ts` module, interpolated below — the outline,
|
|
7
|
+
* the blocks and their navigation, the composer, the draft guard, the frame, the settlement,
|
|
8
|
+
* the keymap, and the Markdown, chrome, comments, and collaboration halves.
|
|
9
|
+
*
|
|
10
|
+
* What is left here is the composition root, deliberately: the element handles, the state
|
|
11
|
+
* the rules are handed, one adapter per rule that names it without arguments, the listeners
|
|
12
|
+
* that attach them, the `EventSource` that keeps a tab's presence known, and the boot order.
|
|
13
|
+
* None of it decides anything a test could disagree with — a rule that does belongs in a
|
|
14
|
+
* module, where it can be typed and driven.
|
|
15
|
+
*
|
|
16
|
+
* It renders the plan from the JSON data island using DOM text nodes only — plan and comment
|
|
17
|
+
* text never becomes markup — with each block's comment threads indented beneath it, code
|
|
18
|
+
* highlighted by copying only `tok-*` spans through `DOMParser`, and the annotation list
|
|
19
|
+
* saved back to the server.
|
|
14
20
|
*/
|
|
15
21
|
|
|
16
|
-
import { dialogScript, keysDialogScript, THEME_TOGGLE_JS } from "../../lib/web/index.ts";
|
|
22
|
+
import { CLIENT_RUNTIME_JS, dialogScript, keysDialogScript, THEME_TOGGLE_JS } from "../../lib/web/index.ts";
|
|
17
23
|
import { WEB_CHROME_JS } from "./web-chrome.ts";
|
|
18
24
|
import { WEB_COMMENTS_JS } from "./web-comments.ts";
|
|
19
25
|
import { WEB_COLLAB_JS } from "./web-collab.ts";
|
|
20
26
|
import { WEB_MARKDOWN_JS } from "./web-markdown.ts";
|
|
27
|
+
import { WEB_BLOCKS_JS } from "./web-blocks.ts";
|
|
28
|
+
import { WEB_COMPOSER_JS } from "./web-composer.ts";
|
|
29
|
+
import { WEB_DRAFT_JS } from "./web-draft.ts";
|
|
30
|
+
import { WEB_OUTLINE_JS } from "./web-outline.ts";
|
|
31
|
+
import { WEB_KEYS_JS } from "./web-keys.ts";
|
|
32
|
+
import { WEB_PLAN_JS } from "./web-plan.ts";
|
|
33
|
+
import { WEB_SETTLE_JS } from "./web-settle.ts";
|
|
21
34
|
|
|
22
35
|
export const REVIEW_CLIENT_JS = String.raw`(function () {
|
|
23
36
|
"use strict";
|
|
@@ -55,6 +68,15 @@ export const REVIEW_CLIENT_JS = String.raw`(function () {
|
|
|
55
68
|
var pendingQuote = null;
|
|
56
69
|
var activeIndex = -1;
|
|
57
70
|
var outlineItems = {};
|
|
71
|
+
/** The island's blocks, or none: one place the client reads them for navigation. */
|
|
72
|
+
function blocks() {
|
|
73
|
+
return Array.isArray(data.blocks) ? data.blocks : [];
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** The island's outline, or none: the one place the client reads it for the outline. */
|
|
77
|
+
function outlineEntries() {
|
|
78
|
+
return Array.isArray(data.outline) ? data.outline : [];
|
|
79
|
+
}
|
|
58
80
|
var stream = null;
|
|
59
81
|
|
|
60
82
|
var planEl = document.getElementById("plan");
|
|
@@ -67,337 +89,155 @@ export const REVIEW_CLIENT_JS = String.raw`(function () {
|
|
|
67
89
|
var outlineList = document.getElementById("outline-list");
|
|
68
90
|
var progressEl = document.getElementById("progress-fill");
|
|
69
91
|
|
|
70
|
-
|
|
71
|
-
var node = document.createElement(tag);
|
|
72
|
-
if (className) node.className = className;
|
|
73
|
-
return node;
|
|
74
|
-
}
|
|
92
|
+
${CLIENT_RUNTIME_JS}
|
|
75
93
|
|
|
76
|
-
${
|
|
94
|
+
${WEB_OUTLINE_JS}
|
|
77
95
|
|
|
78
|
-
${
|
|
96
|
+
${WEB_BLOCKS_JS}
|
|
79
97
|
|
|
80
|
-
${
|
|
98
|
+
${WEB_KEYS_JS}
|
|
81
99
|
|
|
82
|
-
${
|
|
83
|
-
function closeComposer() {
|
|
84
|
-
var open = planEl ? planEl.querySelector(".bf-composer") : null;
|
|
85
|
-
if (open && open.parentNode) open.parentNode.removeChild(open);
|
|
86
|
-
Array.prototype.forEach.call(planEl ? planEl.querySelectorAll(".bf-block--composing") : [], function (block) {
|
|
87
|
-
block.classList.remove("bf-block--composing");
|
|
88
|
-
});
|
|
89
|
-
// The only status a composer leaves behind is the refusal to leave it; once
|
|
90
|
-
// the draft is gone that sentence is stale.
|
|
91
|
-
if (statusEl && statusEl.textContent === DRAFT_REFUSAL) setStatus("");
|
|
92
|
-
// Closing is how a half-typed comment is discarded, so the stored draft follows
|
|
93
|
-
// it. A close with nothing open must not rewrite — and so drop — a restored one.
|
|
94
|
-
if (open) saveDraft();
|
|
95
|
-
}
|
|
100
|
+
${WEB_SETTLE_JS}
|
|
96
101
|
|
|
97
|
-
|
|
98
|
-
if (sectionObserver && sectionObserver.disconnect) sectionObserver.disconnect();
|
|
99
|
-
sectionObserver = null;
|
|
100
|
-
if (!outlineList || !window.IntersectionObserver) return;
|
|
101
|
-
var links = {};
|
|
102
|
-
Array.prototype.forEach.call(outlineList.querySelectorAll("a"), function (link) {
|
|
103
|
-
links[link.getAttribute("data-id")] = link;
|
|
104
|
-
});
|
|
105
|
-
var headings = Array.prototype.slice.call(planEl.querySelectorAll(".bf-block[id]"));
|
|
106
|
-
if (headings.length === 0) return;
|
|
107
|
-
sectionObserver = new IntersectionObserver(function (entries) {
|
|
108
|
-
entries.forEach(function (entry) {
|
|
109
|
-
if (!entry.isIntersecting) return;
|
|
110
|
-
Array.prototype.forEach.call(outlineList.querySelectorAll("a"), function (link) {
|
|
111
|
-
link.removeAttribute("aria-current");
|
|
112
|
-
});
|
|
113
|
-
var link = links[entry.target.id];
|
|
114
|
-
if (link) link.setAttribute("aria-current", "true");
|
|
115
|
-
});
|
|
116
|
-
}, { rootMargin: "-88px 0px -68% 0px", threshold: 0 });
|
|
117
|
-
headings.forEach(function (heading) { sectionObserver.observe(heading); });
|
|
118
|
-
}
|
|
102
|
+
${WEB_PLAN_JS}
|
|
119
103
|
|
|
120
|
-
|
|
121
|
-
outlineItems = {};
|
|
122
|
-
if (!outlineList) return;
|
|
123
|
-
outlineList.replaceChildren();
|
|
124
|
-
var items = Array.isArray(data.outline) ? data.outline : [];
|
|
125
|
-
items.forEach(function (item) {
|
|
126
|
-
var li = el("li", "lvl-" + item.level);
|
|
127
|
-
var link = el("a");
|
|
128
|
-
link.href = "#" + item.id;
|
|
129
|
-
link.setAttribute("data-id", item.id);
|
|
130
|
-
var label = el("span", "review-nav__link");
|
|
131
|
-
label.textContent = item.text;
|
|
132
|
-
link.appendChild(label);
|
|
133
|
-
if (item.changed) {
|
|
134
|
-
var dot = el("span", "review-changed");
|
|
135
|
-
dot.setAttribute("role", "img");
|
|
136
|
-
dot.setAttribute("aria-label", "Changed since your last review");
|
|
137
|
-
dot.title = "Changed since your last review";
|
|
138
|
-
link.appendChild(dot);
|
|
139
|
-
}
|
|
140
|
-
var count = el("span", "review-nav__count");
|
|
141
|
-
count.hidden = true;
|
|
142
|
-
link.appendChild(count);
|
|
143
|
-
li.appendChild(link);
|
|
144
|
-
outlineList.appendChild(li);
|
|
145
|
-
outlineItems[item.line] = count;
|
|
146
|
-
});
|
|
147
|
-
}
|
|
104
|
+
${WEB_COMPOSER_JS}
|
|
148
105
|
|
|
149
|
-
|
|
150
|
-
function sectionFor(line) {
|
|
151
|
-
var items = Array.isArray(data.outline) ? data.outline : [];
|
|
152
|
-
var found = null;
|
|
153
|
-
items.forEach(function (item) { if (item.line <= line) found = item; });
|
|
154
|
-
return found;
|
|
155
|
-
}
|
|
106
|
+
${WEB_DRAFT_JS}
|
|
156
107
|
|
|
157
|
-
|
|
158
|
-
function renderFacts() {
|
|
159
|
-
// The server counts the same step list approval seeds, so this never
|
|
160
|
-
// disagrees with the todo list over a checkbox outside the Steps section.
|
|
161
|
-
var steps = data.steps || { done: 0, total: 0 };
|
|
162
|
-
// A plan with no Steps section has no progress to show; 0/0 is noise.
|
|
163
|
-
var stepFact = document.getElementById("step-fact");
|
|
164
|
-
if (stepFact) stepFact.hidden = !steps.total;
|
|
165
|
-
var stepFill = document.getElementById("step-fill");
|
|
166
|
-
if (stepFill) stepFill.style.width = (steps.total > 0 ? Math.round((steps.done / steps.total) * 100) : 0) + "%";
|
|
167
|
-
var stepCount = document.getElementById("step-count");
|
|
168
|
-
if (stepCount) stepCount.textContent = steps.done + "/" + steps.total;
|
|
169
|
-
var totalEl = document.getElementById("comment-total");
|
|
170
|
-
if (totalEl) totalEl.textContent = String(annotations.length);
|
|
171
|
-
Object.keys(outlineItems).forEach(function (line) { outlineItems[line].hidden = true; });
|
|
172
|
-
annotations.forEach(function (annotation) {
|
|
173
|
-
var section = sectionFor(annotation.anchor);
|
|
174
|
-
if (!section) return;
|
|
175
|
-
var count = outlineItems[section.line];
|
|
176
|
-
if (!count) return;
|
|
177
|
-
var value = count.hidden ? 0 : Number(count.textContent);
|
|
178
|
-
count.textContent = String(value + 1);
|
|
179
|
-
count.hidden = false;
|
|
180
|
-
});
|
|
181
|
-
updateState();
|
|
182
|
-
}
|
|
108
|
+
${WEB_CHROME_JS}
|
|
183
109
|
|
|
184
|
-
|
|
185
|
-
return planEl ? Array.prototype.slice.call(planEl.querySelectorAll(".bf-block")) : [];
|
|
186
|
-
}
|
|
110
|
+
${WEB_COMMENTS_JS}
|
|
187
111
|
|
|
188
|
-
|
|
189
|
-
var blocks = blockElements();
|
|
190
|
-
if (blocks.length === 0) return;
|
|
191
|
-
activeIndex = Math.max(0, Math.min(blocks.length - 1, index));
|
|
192
|
-
blocks.forEach(function (block, at) { block.classList.toggle("bf-block--active", at === activeIndex); });
|
|
193
|
-
var current = blocks[activeIndex];
|
|
194
|
-
if (current && current.scrollIntoView) current.scrollIntoView({ block: "nearest" });
|
|
195
|
-
}
|
|
112
|
+
${WEB_COLLAB_JS}
|
|
196
113
|
|
|
197
|
-
|
|
198
|
-
var blocks = blockElements();
|
|
199
|
-
if (blocks.length === 0) return;
|
|
200
|
-
if (activeIndex < 0) { setActive(delta < 0 ? blocks.length - 1 : 0); return; }
|
|
201
|
-
setActive(activeIndex + delta);
|
|
202
|
-
}
|
|
114
|
+
${WEB_MARKDOWN_JS}
|
|
203
115
|
|
|
204
|
-
|
|
205
|
-
function
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
116
|
+
// Review facts: the plan's step progress, and the comments per section.
|
|
117
|
+
function facts() {
|
|
118
|
+
// The server counts the same step list approval seeds, so this never disagrees with
|
|
119
|
+
// the todo list over a checkbox outside the Steps section.
|
|
120
|
+
renderFacts(data.steps || { done: 0, total: 0 }, annotations, outlineItems, outlineEntries());
|
|
121
|
+
updateState();
|
|
209
122
|
}
|
|
210
123
|
|
|
211
|
-
/**
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
124
|
+
/**
|
|
125
|
+
* What the composer lifecycle reads, built where its state is.
|
|
126
|
+
*
|
|
127
|
+
* The bundle holds the state and the element handles; web-composer.ts holds the rules,
|
|
128
|
+
* so each call hands it the things those rules ask for and nothing more.
|
|
129
|
+
*/
|
|
130
|
+
function composer() {
|
|
131
|
+
return {
|
|
132
|
+
planEl: planEl,
|
|
133
|
+
statusEl: statusEl,
|
|
134
|
+
refusal: DRAFT_REFUSAL,
|
|
135
|
+
setStatus: setStatus,
|
|
136
|
+
saveDraft: saveDraft,
|
|
137
|
+
open: openComposer,
|
|
138
|
+
};
|
|
216
139
|
}
|
|
217
140
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
141
|
+
/**
|
|
142
|
+
* Close the composer, for the surfaces that never see the bundle.
|
|
143
|
+
*
|
|
144
|
+
* The comment fragments call this while they build the form; they own the DOM, not the
|
|
145
|
+
* four pieces the rule reads, so they name the action and this supplies the state.
|
|
146
|
+
*/
|
|
147
|
+
function closeOpenComposer() {
|
|
148
|
+
closeComposer(composer());
|
|
225
149
|
}
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
add.type = "button";
|
|
253
|
-
add.textContent = "+";
|
|
254
|
-
add.setAttribute("aria-label", "Comment on this block");
|
|
255
|
-
add.addEventListener("click", function () {
|
|
256
|
-
var quote = pendingQuote && pendingQuote.wrap === wrap ? pendingQuote.quote : selectionIn(wrap);
|
|
257
|
-
pendingQuote = null;
|
|
258
|
-
openComposer(wrap, block, quote);
|
|
259
|
-
});
|
|
260
|
-
rail.appendChild(add);
|
|
261
|
-
if (mine.length > 0) {
|
|
262
|
-
var badge = el("span", "bf-block__count");
|
|
263
|
-
badge.textContent = String(mine.length);
|
|
264
|
-
badge.title = mine.length === 1 ? "1 review comment" : mine.length + " review comments";
|
|
265
|
-
rail.appendChild(badge);
|
|
266
|
-
}
|
|
267
|
-
wrap.appendChild(rail);
|
|
268
|
-
wrap.addEventListener("focusin", function () { setActive(blockElements().indexOf(wrap)); });
|
|
269
|
-
planEl.appendChild(wrap);
|
|
270
|
-
renderComments(planEl, block.start);
|
|
271
|
-
});
|
|
272
|
-
var orphans = annotations.filter(function (annotation) { return !anchored[annotation.anchor]; });
|
|
273
|
-
if (orphans.length > 0) {
|
|
274
|
-
var box = el("div", "bf-comments bf-comments--orphans");
|
|
275
|
-
var note = el("p"); note.textContent = "Comments not attached to a current block";
|
|
276
|
-
box.appendChild(note);
|
|
277
|
-
renderCommentCards(box, orphans);
|
|
278
|
-
planEl.appendChild(box);
|
|
279
|
-
}
|
|
280
|
-
planEl.scrollTop = scrollTop;
|
|
281
|
-
// The DOM was rebuilt, so the active block's class is gone; restore it in
|
|
282
|
-
// place rather than scrolling, so j/k resume where the reviewer was.
|
|
283
|
-
var active = activeIndex >= 0 ? blockElements()[activeIndex] : null;
|
|
284
|
-
if (active) active.classList.add("bf-block--active");
|
|
285
|
-
// The focused element went with the old DOM, so put the tab stop back on the
|
|
286
|
-
// block's comment control: that is where adding, editing, or deleting left it.
|
|
287
|
-
if (keepFocus && active) {
|
|
288
|
-
var stop = active.querySelector(".bf-block__add");
|
|
289
|
-
if (stop) stop.focus();
|
|
290
|
-
}
|
|
291
|
-
renderFacts();
|
|
292
|
-
observeSections();
|
|
293
|
-
updateState();
|
|
150
|
+
/**
|
|
151
|
+
* What a repaint reads and changes, built where the state is.
|
|
152
|
+
*
|
|
153
|
+
* Reads are getters and mutations are callbacks, so the bundle keeps every variable it
|
|
154
|
+
* owns and web-plan.ts holds only the shape of one frame.
|
|
155
|
+
*/
|
|
156
|
+
function planFrame() {
|
|
157
|
+
return {
|
|
158
|
+
planEl: planEl,
|
|
159
|
+
outlineList: outlineList,
|
|
160
|
+
focused: document.activeElement,
|
|
161
|
+
blocks: blocks,
|
|
162
|
+
annotations: function () { return annotations; },
|
|
163
|
+
activeIndex: function () { return activeIndex; },
|
|
164
|
+
setActiveIndex: function (index) { activeIndex = index; },
|
|
165
|
+
pendingQuote: function () { return pendingQuote; },
|
|
166
|
+
clearPendingQuote: function () { pendingQuote = null; },
|
|
167
|
+
composer: composer,
|
|
168
|
+
renderBlock: renderBlockContent,
|
|
169
|
+
renderComments: renderComments,
|
|
170
|
+
renderCommentCards: renderCommentCards,
|
|
171
|
+
selectionIn: selectionIn,
|
|
172
|
+
open: openComposer,
|
|
173
|
+
facts: facts,
|
|
174
|
+
updateState: updateState,
|
|
175
|
+
};
|
|
294
176
|
}
|
|
295
177
|
|
|
178
|
+
function renderAll() {
|
|
179
|
+
sectionObserver = renderPlanFrame(planFrame(), closeOpenComposer, observeSections, sectionObserver);
|
|
180
|
+
}
|
|
296
181
|
|
|
297
182
|
// Save, approve, and post share the same POST shape; what differs is whether
|
|
298
183
|
// the review settles. Only the owner's save and approve resolve the session,
|
|
299
184
|
// so only their callback marks the page terminal. A reviewer posts only their
|
|
300
185
|
// own comments, so a page showing everyone's cannot re-post another writer's.
|
|
301
|
-
function submit(url, okText, failText, onOk) {
|
|
302
|
-
var payload = role === "reviewer" ? annotations.filter(isMine) : annotations;
|
|
303
|
-
fetch(url + "?token=" + encodeURIComponent(token), {
|
|
304
|
-
method: "POST",
|
|
305
|
-
headers: { "Content-Type": "application/json" },
|
|
306
|
-
body: JSON.stringify({ annotations: payload })
|
|
307
|
-
}).then(function (response) {
|
|
308
|
-
if (response.ok) {
|
|
309
|
-
dirty = false;
|
|
310
|
-
clearDraft();
|
|
311
|
-
setStatus(okText, "saved");
|
|
312
|
-
if (onOk) onOk();
|
|
313
|
-
updateState();
|
|
314
|
-
// The server binds a reviewer's name to her link on the first post; adopt it
|
|
315
|
-
// so a second tab of the same link posts under the same name.
|
|
316
|
-
return response.json().then(function (payload) {
|
|
317
|
-
if (role === "reviewer" && payload && typeof payload.author === "string" && payload.author) adoptName(payload.author);
|
|
318
|
-
}, function () {});
|
|
319
|
-
}
|
|
320
|
-
if (response.status === 409) {
|
|
321
|
-
// A conflict is either a plan that moved under the page or a link already
|
|
322
|
-
// bound to another name; a reload fixes both, and the server names which.
|
|
323
|
-
if (approveButton) approveButton.disabled = true;
|
|
324
|
-
if (saveButton) saveButton.disabled = true;
|
|
325
|
-
if (postButton) postButton.disabled = true;
|
|
326
|
-
offerReload();
|
|
327
|
-
return response.json().then(
|
|
328
|
-
function (payload) { setStatus(payload && payload.error ? payload.error : PLAN_MOVED, "error"); },
|
|
329
|
-
function () { setStatus(PLAN_MOVED, "error"); }
|
|
330
|
-
);
|
|
331
|
-
}
|
|
332
|
-
// Surface the server's own reason (a malformed comment, a stale body)
|
|
333
|
-
// rather than a bare status code.
|
|
334
|
-
return response.json().then(
|
|
335
|
-
function (payload) { throw new Error(payload && payload.error ? payload.error : "server returned " + response.status); },
|
|
336
|
-
function () { throw new Error("server returned " + response.status); }
|
|
337
|
-
);
|
|
338
|
-
}).catch(function (error) {
|
|
339
|
-
setStatus(failText + error.message, "error");
|
|
340
|
-
});
|
|
341
|
-
}
|
|
342
|
-
|
|
343
186
|
// A composer holds text the page has not committed, so the ways off the page
|
|
344
187
|
// refuse while it does: leaving would drop what the reviewer typed.
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
return area && area.value.trim() ? area : null;
|
|
188
|
+
/** The ways off the page refuse while a comment is half-written; this says so once. */
|
|
189
|
+
function confirmNoDraft() {
|
|
190
|
+
return guardDraft(planEl, statusEl, DRAFT_REFUSAL, paintStatus);
|
|
349
191
|
}
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* What the settlement reads and changes, built where the state is.
|
|
195
|
+
*
|
|
196
|
+
* The flags live here, the buttons live here, the stream lives here; web-settle.ts owns
|
|
197
|
+
* the rules about them and reports each change through these callbacks.
|
|
198
|
+
*/
|
|
199
|
+
function settlement() {
|
|
200
|
+
return {
|
|
201
|
+
token: function () { return token; },
|
|
202
|
+
role: function () { return role; },
|
|
203
|
+
mine: function () { return annotations.filter(isMine); },
|
|
204
|
+
annotations: function () { return annotations; },
|
|
205
|
+
settled: function () { return saved || approved || closed; },
|
|
206
|
+
setStatus: setStatus,
|
|
207
|
+
buttons: function () { return { approve: approveButton, save: saveButton, post: postButton }; },
|
|
208
|
+
clearDraft: clearDraft,
|
|
209
|
+
markClean: function () { dirty = false; },
|
|
210
|
+
adoptName: adoptName,
|
|
211
|
+
offerReload: offerReload,
|
|
212
|
+
markSettled: function (kind) {
|
|
213
|
+
if (kind === "saved") saved = true;
|
|
214
|
+
else if (kind === "approved") approved = true;
|
|
215
|
+
else closed = true;
|
|
216
|
+
},
|
|
217
|
+
settleStream: settleStream,
|
|
218
|
+
lockActions: lockActions,
|
|
219
|
+
updateState: updateState,
|
|
220
|
+
confirmNoDraft: confirmNoDraft,
|
|
221
|
+
post: postJson,
|
|
222
|
+
send: function (url) { return fetch(url, { method: "POST" }); },
|
|
223
|
+
planMoved: PLAN_MOVED,
|
|
224
|
+
};
|
|
356
225
|
}
|
|
357
226
|
|
|
358
227
|
function save() {
|
|
359
|
-
|
|
360
|
-
if (guardDraft()) return;
|
|
361
|
-
setStatus("Saving\u2026");
|
|
362
|
-
submit("/api/feedback", "Saved \u2014 return to Pi to continue.", "Could not save: ", function () {
|
|
363
|
-
saved = true;
|
|
364
|
-
settleStream();
|
|
365
|
-
lockActions();
|
|
366
|
-
});
|
|
228
|
+
saveReview(settlement());
|
|
367
229
|
}
|
|
368
230
|
|
|
369
|
-
// A reviewer's write never settles the page: the server keeps the review open,
|
|
370
|
-
// so the page keeps following it and she can keep editing her own comments.
|
|
371
231
|
function postComments() {
|
|
372
|
-
|
|
373
|
-
if (guardDraft()) return;
|
|
374
|
-
setStatus("Posting\u2026");
|
|
375
|
-
submit("/api/comment", "Comments posted \u2014 the owner sees them now.", "Could not post: ", null);
|
|
232
|
+
postReviewComments(settlement());
|
|
376
233
|
}
|
|
377
234
|
|
|
378
235
|
function approve() {
|
|
379
|
-
|
|
380
|
-
setStatus("Approving\u2026");
|
|
381
|
-
submit("/api/approve", "Approved \u2014 return to Pi to continue.", "Could not approve: ", function () {
|
|
382
|
-
approved = true;
|
|
383
|
-
settleStream();
|
|
384
|
-
lockActions();
|
|
385
|
-
});
|
|
236
|
+
approveReview(settlement());
|
|
386
237
|
}
|
|
387
238
|
|
|
388
239
|
function cancel() {
|
|
389
|
-
|
|
390
|
-
if (role !== "owner" || saved || approved || closed) return;
|
|
391
|
-
if (guardDraft()) return;
|
|
392
|
-
closed = true;
|
|
393
|
-
settleStream();
|
|
394
|
-
lockActions();
|
|
395
|
-
setStatus("Closing\u2026");
|
|
396
|
-
updateState();
|
|
397
|
-
fetch("/api/cancel?token=" + encodeURIComponent(token), { method: "POST" }).catch(function () {}).then(function () {
|
|
398
|
-
setStatus("Closed \u2014 return to Pi.");
|
|
399
|
-
updateState();
|
|
400
|
-
});
|
|
240
|
+
cancelReview(settlement());
|
|
401
241
|
}
|
|
402
242
|
|
|
403
243
|
if (postButton) postButton.addEventListener("click", postComments);
|
|
@@ -413,7 +253,7 @@ ${WEB_MARKDOWN_JS}
|
|
|
413
253
|
|
|
414
254
|
function askApprove() {
|
|
415
255
|
if (saved || approved || closed) return;
|
|
416
|
-
if (
|
|
256
|
+
if (confirmNoDraft()) return;
|
|
417
257
|
if (!approveDialog || !window.bfApprove) { approve(); return; }
|
|
418
258
|
window.bfApprove.open();
|
|
419
259
|
}
|
|
@@ -450,38 +290,47 @@ ${WEB_MARKDOWN_JS}
|
|
|
450
290
|
// Remember a selection made inside a block so clicking the rail button can
|
|
451
291
|
// still quote it after the button press has collapsed the selection.
|
|
452
292
|
document.addEventListener("mouseup", function (event) {
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
293
|
+
rememberSelection(event.target, {
|
|
294
|
+
planEl: planEl,
|
|
295
|
+
settled: function () { return saved || approved || closed; },
|
|
296
|
+
selectionIn: selectionIn,
|
|
297
|
+
setPending: function (quote) { pendingQuote = quote; },
|
|
298
|
+
});
|
|
457
299
|
});
|
|
458
300
|
|
|
459
301
|
// The sheet's own wiring — trigger, close button, backdrop, Tab trap, and Esc —
|
|
460
302
|
// is shared with browse; the page keeps only its own keymap.
|
|
461
303
|
${keysDialogScript("shortcuts")}
|
|
462
304
|
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
305
|
+
/**
|
|
306
|
+
* What a key may do, built where the state and the handlers are.
|
|
307
|
+
*
|
|
308
|
+
* The listener below stays in the bundle with the rest of the wiring; the map of key to
|
|
309
|
+
* action is web-keys.ts, which is the part with a rule to it.
|
|
310
|
+
*/
|
|
311
|
+
function keyActions() {
|
|
312
|
+
return {
|
|
313
|
+
composerOpen: function () { return !!(planEl && planEl.querySelector(".bf-composer")); },
|
|
314
|
+
closeComposer: closeOpenComposer,
|
|
315
|
+
cancel: cancel,
|
|
316
|
+
sheetOpen: function () { return window.bfKeys.isOpen(); },
|
|
317
|
+
toggleSheet: function () { window.bfKeys.toggle(); },
|
|
318
|
+
moveActive: function (delta) {
|
|
319
|
+
var next = moveActive(planEl, activeIndex, delta);
|
|
320
|
+
if (next !== null) activeIndex = next;
|
|
321
|
+
},
|
|
322
|
+
commentOnActive: function () { activeIndex = commentOnActive(composer(), activeIndex, blocks()); },
|
|
323
|
+
askApprove: askApprove,
|
|
324
|
+
save: save,
|
|
325
|
+
postComments: postComments,
|
|
326
|
+
hasSaveControl: function () { return !!saveButton; },
|
|
327
|
+
hasApproveControl: function () { return !!approveButton; },
|
|
328
|
+
typing: typing,
|
|
329
|
+
};
|
|
466
330
|
}
|
|
467
331
|
|
|
468
332
|
document.addEventListener("keydown", function (event) {
|
|
469
|
-
|
|
470
|
-
// The sheet owns its own Esc; this peels the composer, then the review.
|
|
471
|
-
if (planEl && planEl.querySelector(".bf-composer")) { closeComposer(); return; }
|
|
472
|
-
cancel();
|
|
473
|
-
return;
|
|
474
|
-
}
|
|
475
|
-
if (typing(event.target)) return;
|
|
476
|
-
if (event.metaKey || event.ctrlKey || event.altKey) return;
|
|
477
|
-
if (event.key === "?") { event.preventDefault(); window.bfKeys.toggle(); return; }
|
|
478
|
-
// While the shortcut sheet is open it owns the keyboard.
|
|
479
|
-
if (window.bfKeys.isOpen()) return;
|
|
480
|
-
if (event.key === "j") { event.preventDefault(); moveActive(1); return; }
|
|
481
|
-
if (event.key === "k") { event.preventDefault(); moveActive(-1); return; }
|
|
482
|
-
if (event.key === "c") { event.preventDefault(); commentOnActive(); return; }
|
|
483
|
-
if (event.key === "a" && approveButton) { event.preventDefault(); askApprove(); return; }
|
|
484
|
-
if (event.key === "s") { event.preventDefault(); if (saveButton) save(); else postComments(); return; }
|
|
333
|
+
handleKey(event, keyActions());
|
|
485
334
|
});
|
|
486
335
|
|
|
487
336
|
// A closed tab is detected by the stream below: the server sees the last
|
|
@@ -517,9 +366,9 @@ ${WEB_MARKDOWN_JS}
|
|
|
517
366
|
var restoredTyping = restoreDraft();
|
|
518
367
|
renderHeader();
|
|
519
368
|
renderName();
|
|
520
|
-
renderOutline();
|
|
521
|
-
|
|
522
|
-
reopenComposer(restoredTyping);
|
|
369
|
+
outlineItems = renderOutline(outlineList, outlineEntries());
|
|
370
|
+
renderAll();
|
|
371
|
+
reopenComposer(composer(), restoredTyping, { saved: saved, approved: approved, closed: closed }, blocks());
|
|
523
372
|
updateProgress();
|
|
524
373
|
})();
|
|
525
374
|
`;
|
|
@@ -131,7 +131,7 @@ export const WEB_COLLAB_JS = String.raw` /**
|
|
|
131
131
|
reviewerName = value;
|
|
132
132
|
try { window.localStorage.setItem("bf-review-name", reviewerName); } catch (error) {}
|
|
133
133
|
renderName();
|
|
134
|
-
|
|
134
|
+
renderAll();
|
|
135
135
|
}
|
|
136
136
|
|
|
137
137
|
function askName() {
|
|
@@ -147,7 +147,7 @@ export const WEB_COLLAB_JS = String.raw` /**
|
|
|
147
147
|
try { window.localStorage.setItem("bf-review-name", reviewerName); } catch (error) {}
|
|
148
148
|
if (window.bfName) window.bfName.close();
|
|
149
149
|
renderName();
|
|
150
|
-
|
|
150
|
+
renderAll();
|
|
151
151
|
}
|
|
152
152
|
|
|
153
153
|
/**
|
|
@@ -197,7 +197,7 @@ export const WEB_COLLAB_JS = String.raw` /**
|
|
|
197
197
|
if (typeof next.author === "string" && next.author) adoptName(next.author);
|
|
198
198
|
// This tab's own unsaved comments belong on top of what just arrived.
|
|
199
199
|
restoreDraft();
|
|
200
|
-
|
|
200
|
+
renderAll();
|
|
201
201
|
renderFacts();
|
|
202
202
|
updateState();
|
|
203
203
|
setStatus(kind === "plan" ? "The plan changed on disk; showing the new version." : "Updated with a new comment.");
|