@songsid/agend 2.3.5-alpha.3 → 2.3.5-alpha.5
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/THIRD_PARTY_NOTICES.md +52 -0
- package/dist/accounts-view.d.ts +97 -0
- package/dist/accounts-view.js +116 -0
- package/dist/accounts-view.js.map +1 -0
- package/dist/agent-cli-instructions.md +1 -1
- package/dist/agent-credentials.d.ts +24 -0
- package/dist/agent-credentials.js +66 -0
- package/dist/agent-credentials.js.map +1 -0
- package/dist/agent-endpoint.d.ts +3 -1
- package/dist/agent-endpoint.js +13 -0
- package/dist/agent-endpoint.js.map +1 -1
- package/dist/agent-file-read.js +1 -1
- package/dist/agent-file-read.js.map +1 -1
- package/dist/agent-runtime-api.d.ts +4 -0
- package/dist/agent-runtime-api.js +59 -0
- package/dist/agent-runtime-api.js.map +1 -0
- package/dist/agent-runtime-change.d.ts +76 -0
- package/dist/agent-runtime-change.js +114 -0
- package/dist/agent-runtime-change.js.map +1 -0
- package/dist/agent-workspace.d.ts +93 -0
- package/dist/agent-workspace.js +353 -0
- package/dist/agent-workspace.js.map +1 -0
- package/dist/auth-api.d.ts +27 -0
- package/dist/auth-api.js +168 -3
- package/dist/auth-api.js.map +1 -1
- package/dist/authority/session-authorizations.d.ts +28 -4
- package/dist/authority/session-authorizations.js +63 -17
- package/dist/authority/session-authorizations.js.map +1 -1
- package/dist/backend/antigravity.js +3 -1
- package/dist/backend/antigravity.js.map +1 -1
- package/dist/backend/codex-footer-hints.d.ts +7 -0
- package/dist/backend/codex-footer-hints.js +41 -0
- package/dist/backend/codex-footer-hints.js.map +1 -0
- package/dist/backend/codex-metadata.d.ts +14 -0
- package/dist/backend/codex-metadata.js +45 -0
- package/dist/backend/codex-metadata.js.map +1 -1
- package/dist/backend/codex-quota-history.d.ts +7 -0
- package/dist/backend/codex-quota-history.js +57 -0
- package/dist/backend/codex-quota-history.js.map +1 -0
- package/dist/backend/codex-session-lookup.d.ts +6 -0
- package/dist/backend/codex-session-lookup.js +1 -1
- package/dist/backend/codex-session-lookup.js.map +1 -1
- package/dist/backend/codex-speed.d.ts +14 -0
- package/dist/backend/codex-speed.js +35 -0
- package/dist/backend/codex-speed.js.map +1 -0
- package/dist/backend/codex.d.ts +7 -0
- package/dist/backend/codex.js +85 -24
- package/dist/backend/codex.js.map +1 -1
- package/dist/backend/grok-weekly-reset.d.ts +24 -0
- package/dist/backend/grok-weekly-reset.js +95 -0
- package/dist/backend/grok-weekly-reset.js.map +1 -0
- package/dist/backend/grok.d.ts +30 -1
- package/dist/backend/grok.js +301 -54
- package/dist/backend/grok.js.map +1 -1
- package/dist/backend/kiro.d.ts +3 -0
- package/dist/backend/kiro.js +50 -2
- package/dist/backend/kiro.js.map +1 -1
- package/dist/backend/muse.js +5 -3
- package/dist/backend/muse.js.map +1 -1
- package/dist/backend/native-pane.d.ts +3 -1
- package/dist/backend/native-pane.js +2 -2
- package/dist/backend/native-pane.js.map +1 -1
- package/dist/backend/types.d.ts +13 -1
- package/dist/backend/types.js +3 -0
- package/dist/backend/types.js.map +1 -1
- package/dist/build-provenance-cli.d.ts +3 -0
- package/dist/build-provenance-cli.js +11 -0
- package/dist/build-provenance-cli.js.map +1 -0
- package/dist/build-provenance.d.ts +20 -0
- package/dist/build-provenance.js +67 -0
- package/dist/build-provenance.js.map +1 -0
- package/dist/build-provenance.json +1 -0
- package/dist/cache-ledger.d.ts +95 -0
- package/dist/cache-ledger.js +276 -4
- package/dist/cache-ledger.js.map +1 -1
- package/dist/cache-service.d.ts +5 -0
- package/dist/cache-service.js +16 -4
- package/dist/cache-service.js.map +1 -1
- package/dist/channel/adapters/discord.js +11 -3
- package/dist/channel/adapters/discord.js.map +1 -1
- package/dist/channel/adapters/telegram.d.ts +51 -0
- package/dist/channel/adapters/telegram.js +235 -17
- package/dist/channel/adapters/telegram.js.map +1 -1
- package/dist/channel/mcp-tools.js +3 -3
- package/dist/channel/mcp-tools.js.map +1 -1
- package/dist/channel/types.d.ts +2 -0
- package/dist/claude-auth-status.d.ts +48 -0
- package/dist/claude-auth-status.js +149 -0
- package/dist/claude-auth-status.js.map +1 -0
- package/dist/cli.js +138 -37
- package/dist/cli.js.map +1 -1
- package/dist/config-validator.js +21 -1
- package/dist/config-validator.js.map +1 -1
- package/dist/cross-instance-notice.d.ts +10 -0
- package/dist/cross-instance-notice.js +67 -5
- package/dist/cross-instance-notice.js.map +1 -1
- package/dist/daemon.d.ts +70 -1
- package/dist/daemon.js +547 -25
- package/dist/daemon.js.map +1 -1
- package/dist/dashboard-permissions.d.ts +8 -0
- package/dist/dashboard-permissions.js +16 -0
- package/dist/dashboard-permissions.js.map +1 -1
- package/dist/delivery-consumed-watch.d.ts +23 -1
- package/dist/delivery-consumed-watch.js +30 -3
- package/dist/delivery-consumed-watch.js.map +1 -1
- package/dist/delivery-reconciliation.d.ts +1 -1
- package/dist/delivery-reconciliation.js +1 -1
- package/dist/delivery-reconciliation.js.map +1 -1
- package/dist/doctor.js +20 -9
- package/dist/doctor.js.map +1 -1
- package/dist/export-import.d.ts +7 -0
- package/dist/export-import.js +44 -10
- package/dist/export-import.js.map +1 -1
- package/dist/fleet-context.d.ts +2 -0
- package/dist/fleet-manager.d.ts +362 -7
- package/dist/fleet-manager.js +1522 -91
- package/dist/fleet-manager.js.map +1 -1
- package/dist/fleet-windows-runtime.d.ts +8 -2
- package/dist/fleet-windows-runtime.js +93 -6
- package/dist/fleet-windows-runtime.js.map +1 -1
- package/dist/general-knowledge/skills/concepts/SKILL.md +425 -0
- package/dist/general-knowledge/skills/credential-profiles/SKILL.md +5 -2
- package/dist/general-knowledge/skills/cross-instance-messaging/SKILL.md +1 -0
- package/dist/general-knowledge/skills/fleet-health/SKILL.md +2 -2
- package/dist/general-knowledge/skills/instance-lifecycle/SKILL.md +3 -1
- package/dist/general-knowledge/skills/kiro-engine-migration/SKILL.md +41 -4
- package/dist/general-knowledge/skills/session-management/SKILL.md +1 -1
- package/dist/general-knowledge/skills/tui-effort/SKILL.md +10 -10
- package/dist/general-knowledge/skills/web-dashboard/SKILL.md +31 -15
- package/dist/general-knowledge/skills/worker-collaboration/SKILL.md +1 -1
- package/dist/general-role.d.ts +1 -1
- package/dist/general-role.js +1 -0
- package/dist/general-role.js.map +1 -1
- package/dist/hub/admission.d.ts +28 -0
- package/dist/hub/admission.js +62 -0
- package/dist/hub/admission.js.map +1 -0
- package/dist/hub/attachments/limits.d.ts +20 -0
- package/dist/hub/attachments/limits.js +48 -0
- package/dist/hub/attachments/limits.js.map +1 -0
- package/dist/hub/attachments/store.d.ts +116 -0
- package/dist/hub/attachments/store.js +246 -0
- package/dist/hub/attachments/store.js.map +1 -0
- package/dist/hub/audit/index.d.ts +62 -0
- package/dist/hub/audit/index.js +344 -0
- package/dist/hub/audit/index.js.map +1 -0
- package/dist/hub/browser-certificate.d.ts +38 -0
- package/dist/hub/browser-certificate.js +247 -0
- package/dist/hub/browser-certificate.js.map +1 -0
- package/dist/hub/buttons-model.d.ts +9 -0
- package/dist/hub/buttons-model.js +42 -0
- package/dist/hub/buttons-model.js.map +1 -0
- package/dist/hub/buttons.d.ts +54 -0
- package/dist/hub/buttons.js +346 -0
- package/dist/hub/buttons.js.map +1 -0
- package/dist/hub/catalog.d.ts +74 -0
- package/dist/hub/catalog.js +195 -0
- package/dist/hub/catalog.js.map +1 -0
- package/dist/hub/client.d.ts +69 -0
- package/dist/hub/client.js +300 -0
- package/dist/hub/client.js.map +1 -0
- package/dist/hub/compose/member.d.ts +139 -0
- package/dist/hub/compose/member.js +450 -0
- package/dist/hub/compose/member.js.map +1 -0
- package/dist/hub/compose/server.d.ts +77 -0
- package/dist/hub/compose/server.js +349 -0
- package/dist/hub/compose/server.js.map +1 -0
- package/dist/hub/compose/views.d.ts +12 -0
- package/dist/hub/compose/views.js +4 -0
- package/dist/hub/compose/views.js.map +1 -0
- package/dist/hub/db.d.ts +56 -0
- package/dist/hub/db.js +188 -0
- package/dist/hub/db.js.map +1 -0
- package/dist/hub/directory/config.d.ts +39 -0
- package/dist/hub/directory/config.js +108 -0
- package/dist/hub/directory/config.js.map +1 -0
- package/dist/hub/directory/directory.d.ts +18 -0
- package/dist/hub/directory/directory.js +320 -0
- package/dist/hub/directory/directory.js.map +1 -0
- package/dist/hub/directory/index.d.ts +7 -0
- package/dist/hub/directory/index.js +4 -0
- package/dist/hub/directory/index.js.map +1 -0
- package/dist/hub/directory/profiles.d.ts +41 -0
- package/dist/hub/directory/profiles.js +90 -0
- package/dist/hub/directory/profiles.js.map +1 -0
- package/dist/hub/directory/types.d.ts +71 -0
- package/dist/hub/directory/types.js +14 -0
- package/dist/hub/directory/types.js.map +1 -0
- package/dist/hub/execution/cli-provider.d.ts +23 -0
- package/dist/hub/execution/cli-provider.js +341 -0
- package/dist/hub/execution/cli-provider.js.map +1 -0
- package/dist/hub/execution/index.d.ts +13 -0
- package/dist/hub/execution/index.js +8 -0
- package/dist/hub/execution/index.js.map +1 -0
- package/dist/hub/execution/model-broker.d.ts +24 -0
- package/dist/hub/execution/model-broker.js +208 -0
- package/dist/hub/execution/model-broker.js.map +1 -0
- package/dist/hub/execution/provider-setup.d.ts +11 -0
- package/dist/hub/execution/provider-setup.js +21 -0
- package/dist/hub/execution/provider-setup.js.map +1 -0
- package/dist/hub/execution/sandbox-runner.d.ts +1 -0
- package/dist/hub/execution/sandbox-runner.js +221 -0
- package/dist/hub/execution/sandbox-runner.js.map +1 -0
- package/dist/hub/execution/service.d.ts +136 -0
- package/dist/hub/execution/service.js +459 -0
- package/dist/hub/execution/service.js.map +1 -0
- package/dist/hub/execution/store.d.ts +61 -0
- package/dist/hub/execution/store.js +182 -0
- package/dist/hub/execution/store.js.map +1 -0
- package/dist/hub/execution/views.d.ts +20 -0
- package/dist/hub/execution/views.js +48 -0
- package/dist/hub/execution/views.js.map +1 -0
- package/dist/hub/http/compose.d.ts +20 -0
- package/dist/hub/http/compose.js +518 -0
- package/dist/hub/http/compose.js.map +1 -0
- package/dist/hub/http/core.d.ts +49 -0
- package/dist/hub/http/core.js +124 -0
- package/dist/hub/http/core.js.map +1 -0
- package/dist/hub/http/deps.d.ts +61 -0
- package/dist/hub/http/deps.js +2 -0
- package/dist/hub/http/deps.js.map +1 -0
- package/dist/hub/http/schema.d.ts +14 -0
- package/dist/hub/http/schema.js +80 -0
- package/dist/hub/http/schema.js.map +1 -0
- package/dist/hub/identity.d.ts +32 -0
- package/dist/hub/identity.js +80 -0
- package/dist/hub/identity.js.map +1 -0
- package/dist/hub/init/browser-certificate.d.ts +9 -0
- package/dist/hub/init/browser-certificate.js +78 -0
- package/dist/hub/init/browser-certificate.js.map +1 -0
- package/dist/hub/init/browser-tls.d.ts +18 -0
- package/dist/hub/init/browser-tls.js +62 -0
- package/dist/hub/init/browser-tls.js.map +1 -0
- package/dist/hub/init/cli.d.ts +3 -0
- package/dist/hub/init/cli.js +83 -0
- package/dist/hub/init/cli.js.map +1 -0
- package/dist/hub/init/directory-certificate.d.ts +24 -0
- package/dist/hub/init/directory-certificate.js +222 -0
- package/dist/hub/init/directory-certificate.js.map +1 -0
- package/dist/hub/init/existing-fleet.d.ts +6 -0
- package/dist/hub/init/existing-fleet.js +58 -0
- package/dist/hub/init/existing-fleet.js.map +1 -0
- package/dist/hub/init/handoff.d.ts +8 -0
- package/dist/hub/init/handoff.js +36 -0
- package/dist/hub/init/handoff.js.map +1 -0
- package/dist/hub/init/identity.d.ts +5 -0
- package/dist/hub/init/identity.js +24 -0
- package/dist/hub/init/identity.js.map +1 -0
- package/dist/hub/init/initializer.d.ts +24 -0
- package/dist/hub/init/initializer.js +385 -0
- package/dist/hub/init/initializer.js.map +1 -0
- package/dist/hub/init/inventory.d.ts +17 -0
- package/dist/hub/init/inventory.js +33 -0
- package/dist/hub/init/inventory.js.map +1 -0
- package/dist/hub/init/layout.d.ts +32 -0
- package/dist/hub/init/layout.js +53 -0
- package/dist/hub/init/layout.js.map +1 -0
- package/dist/hub/init/network.d.ts +18 -0
- package/dist/hub/init/network.js +104 -0
- package/dist/hub/init/network.js.map +1 -0
- package/dist/hub/init/plan-digest.d.ts +5 -0
- package/dist/hub/init/plan-digest.js +47 -0
- package/dist/hub/init/plan-digest.js.map +1 -0
- package/dist/hub/init/prompt.d.ts +26 -0
- package/dist/hub/init/prompt.js +110 -0
- package/dist/hub/init/prompt.js.map +1 -0
- package/dist/hub/init/secret-file.d.ts +1 -0
- package/dist/hub/init/secret-file.js +59 -0
- package/dist/hub/init/secret-file.js.map +1 -0
- package/dist/hub/init/text.d.ts +127 -0
- package/dist/hub/init/text.js +247 -0
- package/dist/hub/init/text.js.map +1 -0
- package/dist/hub/init/types.d.ts +114 -0
- package/dist/hub/init/types.js +9 -0
- package/dist/hub/init/types.js.map +1 -0
- package/dist/hub/init/wizard.d.ts +34 -0
- package/dist/hub/init/wizard.js +554 -0
- package/dist/hub/init/wizard.js.map +1 -0
- package/dist/hub/initialization.d.ts +23 -0
- package/dist/hub/initialization.js +28 -0
- package/dist/hub/initialization.js.map +1 -0
- package/dist/hub/member/agent-mode.d.ts +6 -0
- package/dist/hub/member/agent-mode.js +23 -0
- package/dist/hub/member/agent-mode.js.map +1 -0
- package/dist/hub/member/attachments.d.ts +29 -0
- package/dist/hub/member/attachments.js +107 -0
- package/dist/hub/member/attachments.js.map +1 -0
- package/dist/hub/member/exposure-api.d.ts +87 -0
- package/dist/hub/member/exposure-api.js +64 -0
- package/dist/hub/member/exposure-api.js.map +1 -0
- package/dist/hub/member/exposures.d.ts +123 -0
- package/dist/hub/member/exposures.js +282 -0
- package/dist/hub/member/exposures.js.map +1 -0
- package/dist/hub/member/pairing-api.d.ts +85 -0
- package/dist/hub/member/pairing-api.js +385 -0
- package/dist/hub/member/pairing-api.js.map +1 -0
- package/dist/hub/member/quota.d.ts +20 -0
- package/dist/hub/member/quota.js +98 -0
- package/dist/hub/member/quota.js.map +1 -0
- package/dist/hub/member-journal.d.ts +104 -0
- package/dist/hub/member-journal.js +336 -0
- package/dist/hub/member-journal.js.map +1 -0
- package/dist/hub/member-reconciliation.d.ts +14 -0
- package/dist/hub/member-reconciliation.js +97 -0
- package/dist/hub/member-reconciliation.js.map +1 -0
- package/dist/hub/mount.d.ts +39 -0
- package/dist/hub/mount.js +123 -0
- package/dist/hub/mount.js.map +1 -0
- package/dist/hub/pairing-client.d.ts +17 -0
- package/dist/hub/pairing-client.js +108 -0
- package/dist/hub/pairing-client.js.map +1 -0
- package/dist/hub/pairing.d.ts +65 -0
- package/dist/hub/pairing.js +211 -0
- package/dist/hub/pairing.js.map +1 -0
- package/dist/hub/policy.d.ts +63 -0
- package/dist/hub/policy.js +147 -0
- package/dist/hub/policy.js.map +1 -0
- package/dist/hub/preview.d.ts +32 -0
- package/dist/hub/preview.js +64 -0
- package/dist/hub/preview.js.map +1 -0
- package/dist/hub/private-files.d.ts +5 -0
- package/dist/hub/private-files.js +44 -0
- package/dist/hub/private-files.js.map +1 -0
- package/dist/hub/profile.d.ts +59 -0
- package/dist/hub/profile.js +73 -0
- package/dist/hub/profile.js.map +1 -0
- package/dist/hub/protocol.d.ts +779 -0
- package/dist/hub/protocol.js +93 -0
- package/dist/hub/protocol.js.map +1 -0
- package/dist/hub/quota.d.ts +24 -0
- package/dist/hub/quota.js +112 -0
- package/dist/hub/quota.js.map +1 -0
- package/dist/hub/representative.d.ts +141 -0
- package/dist/hub/representative.js +528 -0
- package/dist/hub/representative.js.map +1 -0
- package/dist/hub/retention.d.ts +17 -0
- package/dist/hub/retention.js +45 -0
- package/dist/hub/retention.js.map +1 -0
- package/dist/hub/rooms.d.ts +120 -0
- package/dist/hub/rooms.js +535 -0
- package/dist/hub/rooms.js.map +1 -0
- package/dist/hub/runtime.d.ts +106 -0
- package/dist/hub/runtime.js +298 -0
- package/dist/hub/runtime.js.map +1 -0
- package/dist/hub/server.d.ts +56 -0
- package/dist/hub/server.js +202 -0
- package/dist/hub/server.js.map +1 -0
- package/dist/hub/service-start.d.ts +17 -0
- package/dist/hub/service-start.js +109 -0
- package/dist/hub/service-start.js.map +1 -0
- package/dist/hub/sessions.d.ts +53 -0
- package/dist/hub/sessions.js +196 -0
- package/dist/hub/sessions.js.map +1 -0
- package/dist/hub/socket.d.ts +24 -0
- package/dist/hub/socket.js +79 -0
- package/dist/hub/socket.js.map +1 -0
- package/dist/hub/store.d.ts +77 -0
- package/dist/hub/store.js +88 -0
- package/dist/hub/store.js.map +1 -0
- package/dist/hub/tls.d.ts +31 -0
- package/dist/hub/tls.js +135 -0
- package/dist/hub/tls.js.map +1 -0
- package/dist/hub/types.d.ts +401 -0
- package/dist/hub/types.js +2 -0
- package/dist/hub/types.js.map +1 -0
- package/dist/hub/validation.d.ts +33 -0
- package/dist/hub/validation.js +136 -0
- package/dist/hub/validation.js.map +1 -0
- package/dist/hub/version-floor.d.ts +10 -0
- package/dist/hub/version-floor.js +45 -0
- package/dist/hub/version-floor.js.map +1 -0
- package/dist/hub-browser-certificate-policy.d.ts +31 -0
- package/dist/hub-browser-certificate-policy.js +25 -0
- package/dist/hub-browser-certificate-policy.js.map +1 -0
- package/dist/hub-membership-settings.d.ts +6 -0
- package/dist/hub-membership-settings.js +21 -0
- package/dist/hub-membership-settings.js.map +1 -0
- package/dist/hub-mode.d.ts +15 -0
- package/dist/hub-mode.js +17 -0
- package/dist/hub-mode.js.map +1 -0
- package/dist/hub-pairing-summary.d.ts +48 -0
- package/dist/hub-pairing-summary.js +36 -0
- package/dist/hub-pairing-summary.js.map +1 -0
- package/dist/hub-report-target.d.ts +32 -0
- package/dist/hub-report-target.js +33 -0
- package/dist/hub-report-target.js.map +1 -0
- package/dist/hub-retention-policy.d.ts +14 -0
- package/dist/hub-retention-policy.js +15 -0
- package/dist/hub-retention-policy.js.map +1 -0
- package/dist/instance-config-impact.js +3 -0
- package/dist/instance-config-impact.js.map +1 -1
- package/dist/instance-lifecycle.d.ts +17 -2
- package/dist/instance-lifecycle.js +58 -8
- package/dist/instance-lifecycle.js.map +1 -1
- package/dist/instance-topic-delete.d.ts +77 -0
- package/dist/instance-topic-delete.js +123 -0
- package/dist/instance-topic-delete.js.map +1 -0
- package/dist/instructions.d.ts +1 -1
- package/dist/instructions.js +1 -1
- package/dist/instructions.js.map +1 -1
- package/dist/kiro-engine-status.d.ts +11 -0
- package/dist/kiro-engine-status.js +6 -0
- package/dist/kiro-engine-status.js.map +1 -1
- package/dist/launchd-service-domain.d.ts +5 -0
- package/dist/launchd-service-domain.js +21 -0
- package/dist/launchd-service-domain.js.map +1 -0
- package/dist/locale.js +125 -3
- package/dist/locale.js.map +1 -1
- package/dist/log-service.d.ts +28 -0
- package/dist/log-service.js +29 -0
- package/dist/log-service.js.map +1 -1
- package/dist/log-steps.d.ts +4 -3
- package/dist/log-steps.js +32 -7
- package/dist/log-steps.js.map +1 -1
- package/dist/logger.d.ts +16 -2
- package/dist/logger.js +346 -66
- package/dist/logger.js.map +1 -1
- package/dist/login-flows.d.ts +15 -7
- package/dist/login-flows.js +29 -3
- package/dist/login-flows.js.map +1 -1
- package/dist/login-manager.js +25 -4
- package/dist/login-manager.js.map +1 -1
- package/dist/ls-rows.js +3 -4
- package/dist/ls-rows.js.map +1 -1
- package/dist/missed-offline.d.ts +25 -0
- package/dist/missed-offline.js +79 -0
- package/dist/missed-offline.js.map +1 -0
- package/dist/native-instance-api.d.ts +0 -1
- package/dist/native-instance-api.js +15 -3
- package/dist/native-instance-api.js.map +1 -1
- package/dist/native-instance-input.d.ts +3 -0
- package/dist/native-instance-input.js +6 -1
- package/dist/native-instance-input.js.map +1 -1
- package/dist/native-instance-registry.d.ts +6 -0
- package/dist/native-instance-registry.js +24 -0
- package/dist/native-instance-registry.js.map +1 -1
- package/dist/native-mcp.d.ts +3 -0
- package/dist/native-mcp.js +15 -2
- package/dist/native-mcp.js.map +1 -1
- package/dist/native-pane-lifecycle.d.ts +8 -0
- package/dist/native-pane-lifecycle.js +13 -4
- package/dist/native-pane-lifecycle.js.map +1 -1
- package/dist/native-pane-manager.js +17 -9
- package/dist/native-pane-manager.js.map +1 -1
- package/dist/needs-you-hub.d.ts +9 -1
- package/dist/needs-you-hub.js +46 -8
- package/dist/needs-you-hub.js.map +1 -1
- package/dist/needs-you.d.ts +47 -2
- package/dist/needs-you.js +31 -3
- package/dist/needs-you.js.map +1 -1
- package/dist/office-share-page.js +3 -3
- package/dist/office-share-page.js.map +1 -1
- package/dist/office-share.d.ts +11 -0
- package/dist/office-share.js +14 -0
- package/dist/office-share.js.map +1 -1
- package/dist/office-talk.d.ts +26 -0
- package/dist/office-talk.js +50 -0
- package/dist/office-talk.js.map +1 -0
- package/dist/outbound-handlers.d.ts +14 -0
- package/dist/outbound-handlers.js +49 -6
- package/dist/outbound-handlers.js.map +1 -1
- package/dist/outbound-schemas.d.ts +33 -1
- package/dist/outbound-schemas.js +29 -16
- package/dist/outbound-schemas.js.map +1 -1
- package/dist/pane-quota-history.d.ts +9 -0
- package/dist/pane-quota-history.js +67 -0
- package/dist/pane-quota-history.js.map +1 -0
- package/dist/pause-marker.d.ts +1 -1
- package/dist/pause-marker.js +1 -1
- package/dist/pause-marker.js.map +1 -1
- package/dist/platform-general.d.ts +46 -0
- package/dist/platform-general.js +91 -0
- package/dist/platform-general.js.map +1 -0
- package/dist/public-web-gateway.js +7 -0
- package/dist/public-web-gateway.js.map +1 -1
- package/dist/public-web-link.d.ts +6 -0
- package/dist/public-web-link.js +9 -0
- package/dist/public-web-link.js.map +1 -1
- package/dist/queued-handoffs.d.ts +33 -0
- package/dist/queued-handoffs.js +75 -0
- package/dist/queued-handoffs.js.map +1 -0
- package/dist/quickstart.d.ts +13 -0
- package/dist/quickstart.js +81 -5
- package/dist/quickstart.js.map +1 -1
- package/dist/quota-notice.d.ts +7 -0
- package/dist/quota-notice.js +40 -0
- package/dist/quota-notice.js.map +1 -1
- package/dist/reply-buttons-controller.js +10 -6
- package/dist/reply-buttons-controller.js.map +1 -1
- package/dist/reply-polls.d.ts +2 -2
- package/dist/reply-polls.js +3 -3
- package/dist/reply-polls.js.map +1 -1
- package/dist/runtime-api.d.ts +2 -0
- package/dist/runtime-api.js +106 -13
- package/dist/runtime-api.js.map +1 -1
- package/dist/runtime-build-info.d.ts +9 -0
- package/dist/runtime-build-info.js +59 -0
- package/dist/runtime-build-info.js.map +1 -0
- package/dist/runtime-bundle.d.ts +6 -1
- package/dist/runtime-bundle.js +17 -9
- package/dist/runtime-bundle.js.map +1 -1
- package/dist/runtime-cli.d.ts +13 -1
- package/dist/runtime-cli.js +173 -19
- package/dist/runtime-cli.js.map +1 -1
- package/dist/runtime-client-store.d.ts +27 -1
- package/dist/runtime-client-store.js +86 -6
- package/dist/runtime-client-store.js.map +1 -1
- package/dist/runtime-client.d.ts +74 -0
- package/dist/runtime-client.js +336 -26
- package/dist/runtime-client.js.map +1 -1
- package/dist/runtime-connection.d.ts +22 -0
- package/dist/runtime-connection.js +93 -0
- package/dist/runtime-connection.js.map +1 -0
- package/dist/runtime-coordinator.d.ts +16 -0
- package/dist/runtime-coordinator.js +287 -62
- package/dist/runtime-coordinator.js.map +1 -1
- package/dist/runtime-credential-profiles.d.ts +78 -0
- package/dist/runtime-credential-profiles.js +280 -0
- package/dist/runtime-credential-profiles.js.map +1 -0
- package/dist/runtime-install.d.ts +50 -0
- package/dist/runtime-install.js +186 -0
- package/dist/runtime-install.js.map +1 -0
- package/dist/runtime-pane-assets.d.ts +1 -1
- package/dist/runtime-pane-assets.js +1 -1
- package/dist/runtime-pane-assets.js.map +1 -1
- package/dist/runtime-pane-boot.d.ts +7 -0
- package/dist/runtime-pane-boot.js +23 -0
- package/dist/runtime-pane-boot.js.map +1 -1
- package/dist/runtime-pane-codex-index.d.ts +10 -1
- package/dist/runtime-pane-codex-index.js +131 -27
- package/dist/runtime-pane-codex-index.js.map +1 -1
- package/dist/runtime-pane-host.d.ts +19 -0
- package/dist/runtime-pane-host.js +50 -7
- package/dist/runtime-pane-host.js.map +1 -1
- package/dist/runtime-pane-link.d.ts +7 -0
- package/dist/runtime-pane-link.js +93 -2
- package/dist/runtime-pane-link.js.map +1 -1
- package/dist/runtime-pane-mcp-relay.d.ts +1 -0
- package/dist/runtime-pane-mcp-relay.js +10 -0
- package/dist/runtime-pane-mcp-relay.js.map +1 -1
- package/dist/runtime-pane-mcp.d.ts +4 -0
- package/dist/runtime-pane-mcp.js +6 -3
- package/dist/runtime-pane-mcp.js.map +1 -1
- package/dist/runtime-pane-native.d.ts +32 -2
- package/dist/runtime-pane-native.js +55 -6
- package/dist/runtime-pane-native.js.map +1 -1
- package/dist/runtime-pane-port.d.ts +5 -0
- package/dist/runtime-pane-port.js +17 -1
- package/dist/runtime-pane-port.js.map +1 -1
- package/dist/runtime-pane-process.d.ts +1 -1
- package/dist/runtime-pane-process.js +11 -2
- package/dist/runtime-pane-process.js.map +1 -1
- package/dist/runtime-pane-protocol.d.ts +11 -1
- package/dist/runtime-pane-protocol.js +27 -5
- package/dist/runtime-pane-protocol.js.map +1 -1
- package/dist/runtime-pane-quota-history.d.ts +6 -0
- package/dist/runtime-pane-quota-history.js +44 -0
- package/dist/runtime-pane-quota-history.js.map +1 -0
- package/dist/runtime-pane-service.d.ts +5 -0
- package/dist/runtime-pane-service.js +47 -12
- package/dist/runtime-pane-service.js.map +1 -1
- package/dist/runtime-pane-transcript.d.ts +5 -0
- package/dist/runtime-pane-transcript.js +66 -5
- package/dist/runtime-pane-transcript.js.map +1 -1
- package/dist/runtime-pane-worker.js +19 -6
- package/dist/runtime-pane-worker.js.map +1 -1
- package/dist/runtime-protocol.d.ts +30 -2
- package/dist/runtime-protocol.js +30 -0
- package/dist/runtime-protocol.js.map +1 -1
- package/dist/runtime-update-activity.d.ts +21 -0
- package/dist/runtime-update-activity.js +51 -0
- package/dist/runtime-update-activity.js.map +1 -0
- package/dist/runtime-update-artifact.d.ts +31 -0
- package/dist/runtime-update-artifact.js +252 -0
- package/dist/runtime-update-artifact.js.map +1 -0
- package/dist/runtime-update-install.d.ts +31 -0
- package/dist/runtime-update-install.js +205 -0
- package/dist/runtime-update-install.js.map +1 -0
- package/dist/runtime-update-journal.d.ts +44 -0
- package/dist/runtime-update-journal.js +121 -0
- package/dist/runtime-update-journal.js.map +1 -0
- package/dist/runtime-update-lock.d.ts +48 -0
- package/dist/runtime-update-lock.js +217 -0
- package/dist/runtime-update-lock.js.map +1 -0
- package/dist/runtime-update-owners.d.ts +29 -0
- package/dist/runtime-update-owners.js +240 -0
- package/dist/runtime-update-owners.js.map +1 -0
- package/dist/runtime-update-protocol.d.ts +15 -0
- package/dist/runtime-update-protocol.js +13 -0
- package/dist/runtime-update-protocol.js.map +1 -0
- package/dist/runtime-windows-bridge.d.ts +2 -1
- package/dist/runtime-windows-bridge.js +11 -1
- package/dist/runtime-windows-bridge.js.map +1 -1
- package/dist/scheduler/db.d.ts +2 -1
- package/dist/scheduler/db.js +23 -9
- package/dist/scheduler/db.js.map +1 -1
- package/dist/scheduler/task-board-text.js +1 -1
- package/dist/scheduler/task-board-text.js.map +1 -1
- package/dist/scheduler/task-board.d.ts +13 -0
- package/dist/scheduler/task-board.js +28 -3
- package/dist/scheduler/task-board.js.map +1 -1
- package/dist/service-drain.d.ts +19 -0
- package/dist/service-drain.js +64 -0
- package/dist/service-drain.js.map +1 -0
- package/dist/service-installer.d.ts +7 -2
- package/dist/service-installer.js +13 -10
- package/dist/service-installer.js.map +1 -1
- package/dist/settings-api.d.ts +20 -0
- package/dist/settings-api.js +414 -4
- package/dist/settings-api.js.map +1 -1
- package/dist/settings-baseline.d.ts +6 -0
- package/dist/settings-baseline.js +3 -1
- package/dist/settings-baseline.js.map +1 -1
- package/dist/settings-change.js +29 -6
- package/dist/settings-change.js.map +1 -1
- package/dist/settings-effect.d.ts +13 -0
- package/dist/settings-effect.js +98 -1
- package/dist/settings-effect.js.map +1 -1
- package/dist/settings-http-confirmation.d.ts +3 -0
- package/dist/settings-http-confirmation.js +28 -1
- package/dist/settings-http-confirmation.js.map +1 -1
- package/dist/settings-instance-patch.js +27 -0
- package/dist/settings-instance-patch.js.map +1 -1
- package/dist/setup-wizard.js +6 -2
- package/dist/setup-wizard.js.map +1 -1
- package/dist/task-board-tool.d.ts +13 -1
- package/dist/task-board-tool.js +104 -4
- package/dist/task-board-tool.js.map +1 -1
- package/dist/task-board-waker.d.ts +6 -0
- package/dist/task-board-waker.js +4 -0
- package/dist/task-board-waker.js.map +1 -1
- package/dist/tmux-binary.d.ts +59 -0
- package/dist/tmux-binary.js +200 -0
- package/dist/tmux-binary.js.map +1 -0
- package/dist/tmux-control.js +4 -4
- package/dist/tmux-control.js.map +1 -1
- package/dist/tmux-exec-helper.js +32 -1
- package/dist/tmux-exec-helper.js.map +1 -1
- package/dist/tmux-manager.d.ts +1 -1
- package/dist/tmux-manager.js +45 -36
- package/dist/tmux-manager.js.map +1 -1
- package/dist/tmux-read.js +4 -3
- package/dist/tmux-read.js.map +1 -1
- package/dist/tool-permissions.d.ts +5 -2
- package/dist/tool-permissions.js +6 -5
- package/dist/tool-permissions.js.map +1 -1
- package/dist/tool-progress.d.ts +2 -0
- package/dist/tool-progress.js +19 -3
- package/dist/tool-progress.js.map +1 -1
- package/dist/topic-commands.js +10 -5
- package/dist/topic-commands.js.map +1 -1
- package/dist/transcript-monitor.d.ts +20 -0
- package/dist/transcript-monitor.js +47 -3
- package/dist/transcript-monitor.js.map +1 -1
- package/dist/transcript-needles.d.ts +7 -0
- package/dist/transcript-needles.js +9 -1
- package/dist/transcript-needles.js.map +1 -1
- package/dist/transcript-records.d.ts +1 -1
- package/dist/transcript-records.js +2 -1
- package/dist/transcript-records.js.map +1 -1
- package/dist/types.d.ts +6 -0
- package/dist/ui/account-identity.js +27 -0
- package/dist/ui/accounts-view.js +214 -0
- package/dist/ui/agent-runtime-settings.js +68 -0
- package/dist/ui/agent-runtime.js +20 -2
- package/dist/ui/backend-signin.js +39 -21
- package/dist/ui/chat-ambient.js +1 -1
- package/dist/ui/chat-md-strings.js +3 -0
- package/dist/ui/chat-render.js +3 -670
- package/dist/ui/chat-store.js +1 -1
- package/dist/ui/chat-strings.js +3 -333
- package/dist/ui/chat-thread.js +3 -919
- package/dist/ui/chat-time.js +3 -80
- package/dist/ui/chat-tour.js +1 -1
- package/dist/ui/credential-profile-select.js +54 -0
- package/dist/ui/fleet-office-dialogue.d.ts +30 -0
- package/dist/ui/fleet-office-dialogue.js +382 -0
- package/dist/ui/fleet-office-pack.d.ts +9 -0
- package/dist/ui/fleet-office-pack.js +76 -4
- package/dist/ui/fleet-office-skin.js +23 -5
- package/dist/ui/fleet-office-store.js +5 -0
- package/dist/ui/fleet-office.js +173 -6
- package/dist/ui/fleet-task-card.js +10 -6
- package/dist/ui/fleet-task-common.js +12 -2
- package/dist/ui/fleet-task-menu.js +188 -0
- package/dist/ui/fleet-task-stats.js +124 -0
- package/dist/ui/fleet-task-view.js +340 -0
- package/dist/ui/fleet-tasks.js +131 -43
- package/dist/ui/fleet-usage.js +90 -27
- package/dist/ui/html-attachment.js +3 -40
- package/dist/ui/hub-agent-copy.js +23 -0
- package/dist/ui/hub-button-actions.js +65 -0
- package/dist/ui/hub-chat-store.js +158 -0
- package/dist/ui/hub-chat-strings.js +77 -0
- package/dist/ui/hub-client.js +329 -0
- package/dist/ui/hub-owner-strings.js +51 -0
- package/dist/ui/hub-provider-guidance.js +31 -0
- package/dist/ui/hub-quota.js +85 -0
- package/dist/ui/hub-strings.js +162 -0
- package/dist/ui/hub-switcher.js +27 -0
- package/dist/ui/native-instance-settings.js +5 -8
- package/dist/ui/native-pane-recovery.js +1 -1
- package/dist/ui/office-share-page.js +4 -2
- package/dist/ui/panel-chat.js +36 -49
- package/dist/ui/panel-details.js +1 -1
- package/dist/ui/panel-fleet.js +82 -12
- package/dist/ui/panel-hub.js +619 -0
- package/dist/ui/panel-log.js +2 -2
- package/dist/ui/panel-needs.js +24 -8
- package/dist/ui/panel-runtimes.js +239 -45
- package/dist/ui/panel-settings.js +266 -192
- package/dist/ui/panel-windows-cli.js +88 -42
- package/dist/ui/preview-pages.js +1 -1
- package/dist/ui/preview-panel.js +92 -6
- package/dist/ui/preview-versions.js +1 -1
- package/dist/ui/preview.js +3 -476
- package/dist/ui/runtime-commands.d.ts +4 -0
- package/dist/ui/runtime-commands.js +59 -0
- package/dist/ui/search-dialog.js +2 -0
- package/dist/ui/settings-access-strings.js +42 -0
- package/dist/ui/settings-agent-filters.js +94 -0
- package/dist/ui/settings-appearance.js +122 -23
- package/dist/ui/settings-build-version.js +35 -0
- package/dist/ui/settings-codex-copy.js +19 -0
- package/dist/ui/settings-devices.js +253 -0
- package/dist/ui/settings-dialogs.js +33 -12
- package/dist/ui/settings-guests.js +25 -11
- package/dist/ui/settings-hub-browser-certificate.js +74 -0
- package/dist/ui/settings-hub-membership.js +72 -0
- package/dist/ui/settings-hub-retention.js +52 -0
- package/dist/ui/settings-hub.js +196 -0
- package/dist/ui/settings-layout.js +242 -0
- package/dist/ui/settings-model.js +24 -2
- package/dist/ui/settings-outside.js +104 -38
- package/dist/ui/settings-registry.js +9 -4
- package/dist/ui/settings-shared-strings.js +143 -0
- package/dist/ui/settings-shared.js +345 -0
- package/dist/ui/settings-shortcuts.js +65 -0
- package/dist/ui/settings-strings.js +89 -32
- package/dist/ui/settings-web-authority.js +17 -11
- package/dist/ui/shared/accounts.css +15 -0
- package/dist/ui/shared/app-i18n.js +2 -2
- package/dist/ui/shared/app-needs.js +1 -1
- package/dist/ui/shared/app-route.js +6 -1
- package/dist/ui/shared/app-session.js +6 -2
- package/dist/ui/shared/app-shell.js +81 -15
- package/dist/ui/shared/app.css +1114 -13
- package/dist/ui/shared/app.js +12 -3
- package/dist/ui/shared/chat-composer.js +68 -0
- package/dist/ui/shared/chat-md-strings.js +9 -0
- package/dist/ui/shared/chat-render.js +682 -0
- package/dist/ui/shared/chat-strings.js +333 -0
- package/dist/ui/shared/chat-thread.js +969 -0
- package/dist/ui/shared/chat-time.js +80 -0
- package/dist/ui/shared/header-tools.js +3 -5
- package/dist/ui/shared/html-attachment.js +42 -0
- package/dist/ui/shared/hub-chat.css +85 -0
- package/dist/ui/shared/hub-owner.css +33 -0
- package/dist/ui/shared/hub-quota.css +8 -0
- package/dist/ui/shared/hub.css +71 -0
- package/dist/ui/shared/instance-nav.js +3 -2
- package/dist/ui/shared/machine-name.js +13 -0
- package/dist/ui/shared/page-head.js +8 -0
- package/dist/ui/shared/panel-view.js +77 -4
- package/dist/ui/shared/preview.js +476 -0
- package/dist/ui/shared/runtime-install.css +26 -0
- package/dist/ui/shared/settings-width.js +31 -0
- package/dist/ui/shared/side-rail.js +15 -0
- package/dist/ui/shared/signin.js +172 -0
- package/dist/ui/shared/task-board.css +95 -0
- package/dist/ui/shared/tokens.css +1 -0
- package/dist/ui/shared/ui-dialog.js +2 -2
- package/dist/ui/shared/ui-menu.js +3 -3
- package/dist/ui/shared/usage-dialog.js +18 -1
- package/dist/ui/shared/view-history.css +17 -0
- package/dist/ui/shared/view-history.js +438 -0
- package/dist/ui/shared/view-strings.js +16 -0
- package/dist/ui/shared/workspace.css +20 -0
- package/dist/ui/signin.html +21 -0
- package/dist/ui/windows-cli-catalog.js +41 -12
- package/dist/ui/workspace-panel.js +189 -0
- package/dist/ui/workspace-strings.js +35 -0
- package/dist/unattended-install.d.ts +11 -2
- package/dist/unattended-install.js +41 -6
- package/dist/unattended-install.js.map +1 -1
- package/dist/usage/capacity.d.ts +69 -0
- package/dist/usage/capacity.js +119 -0
- package/dist/usage/capacity.js.map +1 -0
- package/dist/usage/i18n-keys.d.ts +1 -1
- package/dist/usage/i18n-keys.js +3 -0
- package/dist/usage/i18n-keys.js.map +1 -1
- package/dist/usage/profile-timeline.d.ts +16 -0
- package/dist/usage/profile-timeline.js +58 -0
- package/dist/usage/profile-timeline.js.map +1 -0
- package/dist/usage/providers.js +2 -1
- package/dist/usage/providers.js.map +1 -1
- package/dist/usage/quota-history.d.ts +43 -0
- package/dist/usage/quota-history.js +168 -0
- package/dist/usage/quota-history.js.map +1 -0
- package/dist/usage/usage-api.d.ts +31 -0
- package/dist/usage/usage-api.js +80 -1
- package/dist/usage/usage-api.js.map +1 -1
- package/dist/usage-report.d.ts +25 -1
- package/dist/usage-report.js +55 -4
- package/dist/usage-report.js.map +1 -1
- package/dist/usage-rollup.d.ts +6 -1
- package/dist/usage-rollup.js +16 -4
- package/dist/usage-rollup.js.map +1 -1
- package/dist/view-api.d.ts +16 -0
- package/dist/view-api.js +105 -23
- package/dist/view-api.js.map +1 -1
- package/dist/view-history.d.ts +84 -0
- package/dist/view-history.js +116 -0
- package/dist/view-history.js.map +1 -0
- package/dist/web-api.d.ts +49 -2
- package/dist/web-api.js +279 -8
- package/dist/web-api.js.map +1 -1
- package/dist/web-backend-login.d.ts +23 -1
- package/dist/web-backend-login.js +11 -17
- package/dist/web-backend-login.js.map +1 -1
- package/dist/web-device-login.d.ts +153 -0
- package/dist/web-device-login.js +278 -0
- package/dist/web-device-login.js.map +1 -0
- package/dist/web-office-skins.d.ts +9 -0
- package/dist/web-office-skins.js +16 -1
- package/dist/web-office-skins.js.map +1 -1
- package/dist/web-session-authorization.d.ts +13 -2
- package/dist/web-session-authorization.js +10 -3
- package/dist/web-session-authorization.js.map +1 -1
- package/dist/web-session.d.ts +35 -0
- package/dist/web-session.js +7 -0
- package/dist/web-session.js.map +1 -1
- package/dist/web-settings-patch.d.ts +3 -1
- package/dist/web-settings-patch.js +4 -1
- package/dist/web-settings-patch.js.map +1 -1
- package/dist/web-shell-routes.d.ts +6 -0
- package/dist/web-shell-routes.js +7 -0
- package/dist/web-shell-routes.js.map +1 -1
- package/dist/web-terminal.d.ts +4 -30
- package/dist/web-terminal.js +9 -6
- package/dist/web-terminal.js.map +1 -1
- package/dist/windows-agents.d.ts +4 -1
- package/dist/windows-agents.js +14 -10
- package/dist/windows-agents.js.map +1 -1
- package/dist/windows-cli-api.d.ts +12 -1
- package/dist/windows-cli-api.js +29 -2
- package/dist/windows-cli-api.js.map +1 -1
- package/dist/windows-cli-providers.d.ts +0 -9
- package/dist/windows-cli-providers.js +4 -9
- package/dist/windows-cli-providers.js.map +1 -1
- package/dist/windows-cli-worker.d.ts +25 -0
- package/dist/windows-cli-worker.js +98 -7
- package/dist/windows-cli-worker.js.map +1 -1
- package/dist/windows-cli.d.ts +11 -0
- package/dist/windows-cli.js +17 -3
- package/dist/windows-cli.js.map +1 -1
- package/dist/yaml-shared-nodes.js +8 -3
- package/dist/yaml-shared-nodes.js.map +1 -1
- package/launcher/agend-install.cjs +8 -1
- package/launcher/launchd-session-type.cjs +14 -0
- package/launcher/postinstall.cjs +27 -2
- package/launcher/private-home.cjs +1 -1
- package/launcher/tmux-bundle.cjs +247 -0
- package/package.json +3 -2
- package/templates/launchd.plist.ejs +3 -1
- package/templates/runtime-install/install.ps1 +270 -0
|
@@ -0,0 +1,969 @@
|
|
|
1
|
+
// #1408 §0/§4: the conversation thread — the keyed DOM renderer #1307 and #1306 reviewed, now owned by the chat panel
|
|
2
|
+
// through a ref. Preact never manages these children: a node here may hold a live preview iframe, and an iframe that
|
|
3
|
+
// is moved or re-parented reloads. So:
|
|
4
|
+
// - a new message is appended; a changed one (its ticks) is replaced; the rest are left alone — an opened code block
|
|
5
|
+
// stays open, a selection survives, where the reader scrolled stays theirs;
|
|
6
|
+
// - before ONE node is replaced, moved or removed: AgendPreview.stopIn(that node) — frames elsewhere keep running;
|
|
7
|
+
// - when the whole thread goes (the chat left, another instance opened): AgendPreview.stopAll("leave").
|
|
8
|
+
// Unrelated updates (a new message, another message's tick) stop nothing (#1306 §6.2, #1408 §4).
|
|
9
|
+
import "./chat-render.js";
|
|
10
|
+
import "./preview.js";
|
|
11
|
+
import { HTML_PREVIEW_MAX, loadHtmlAttachment, sizeLabel } from "./html-attachment.js";
|
|
12
|
+
import { chatTime, chatTimeFull, onDayChange } from "./chat-time.js";
|
|
13
|
+
import { textLinkSegments } from "/assets/app-route.js";
|
|
14
|
+
|
|
15
|
+
const R = () => globalThis.AgendChatRender;
|
|
16
|
+
/** #1589: the public link plays no media in the page (its page says so: <body data-public-link="1">); #2547: its text cards show. */
|
|
17
|
+
const PUBLIC_LINK = () => globalThis.document?.body?.dataset?.publicLink === "1";
|
|
18
|
+
/** #1589: how much of a text attachment is shown before "Show all". */
|
|
19
|
+
export const TEXT_PREVIEW_LINES = 200;
|
|
20
|
+
/** #1589: the most elements one formatted text card may build (table cells, highlight spans, Markdown elements), Show all
|
|
21
|
+
* included. Lines and the 1 MiB read do not bound it (one 200 KB line can be 100,000 cells): past it the card is plain
|
|
22
|
+
* text, one text node. */
|
|
23
|
+
export const TEXT_PREVIEW_NODES = 4000;
|
|
24
|
+
/** A csv/tsv wider than this is shown as text, as Markdown tables are capped at 20 columns. */
|
|
25
|
+
export const TEXT_PREVIEW_COLUMNS = 50;
|
|
26
|
+
/**
|
|
27
|
+
* #2863: the most a text card reads (bytes). It stays 1 MiB while an HTML preview goes to 8 MiB (and 50 MiB on
|
|
28
|
+
* "Preview anyway"): a text card reads, parses and formats the file on this page's own thread (Markdown, highlighting,
|
|
29
|
+
* a CSV table; Show all draws every line), where an HTML preview runs in its own frame. A bigger file is a download.
|
|
30
|
+
*/
|
|
31
|
+
export const TEXT_CARD_MAX = 1024 * 1024;
|
|
32
|
+
const P = () => globalThis.AgendPreview;
|
|
33
|
+
/** #1672: a status line longer than this (lines, or characters) is folded until it is opened. */
|
|
34
|
+
/**
|
|
35
|
+
* #1672 / the user (hmsg-d6ac6dfb): a status line shows two lines — its header ("A → B:") and the first line of the
|
|
36
|
+
* message, each one row (clipped with an ellipsis). Its "open" control shows when that hides something: more lines than
|
|
37
|
+
* two, or a row clipped at the width it is drawn at (measured after each draw and resize: fitStatus). Where nothing is
|
|
38
|
+
* measured (no layout), a line longer than STATUS_LINE_CHARS stands for "clipped".
|
|
39
|
+
*/
|
|
40
|
+
export const STATUS_FOLD_LINES = 2;
|
|
41
|
+
export const STATUS_LINE_CHARS = 100;
|
|
42
|
+
const REPLY = '<svg class="icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M9 14 4 9l5-5"/><path d="M4 9h10.5a5.5 5.5 0 0 1 0 11H11"/></svg>';
|
|
43
|
+
const COPY = '<svg class="icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>';
|
|
44
|
+
|
|
45
|
+
/** Escapes & " ' < >: safe for text and inside a quoted attribute. */
|
|
46
|
+
export function escAttr(s) { return String(s == null ? "" : s).replace(/&/g, "&").replace(/"/g, """).replace(/'/g, "'").replace(/</g, "<").replace(/>/g, ">"); }
|
|
47
|
+
export const msgKey = (x) => `${x.boot}-${x.id}`;
|
|
48
|
+
/** How many elements an HTML string would build: its opening tags (escaped text has none). */
|
|
49
|
+
function tagsIn(html) {
|
|
50
|
+
let n = 0;
|
|
51
|
+
for (let i = html.indexOf("<"); i !== -1; i = html.indexOf("<", i + 1)) { const c = html.charCodeAt(i + 1); if ((c | 32) >= 97 && (c | 32) <= 122) n++; }
|
|
52
|
+
return n;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* M-2448: a Markdown file drawn into `el` for the side panel — renderMarkdown, which escapes first (no raw HTML, no
|
|
56
|
+
* script: the same renderer and the same output as the chat's Markdown card), or the text as plain text when its
|
|
57
|
+
* formatting would build more than TEXT_PREVIEW_NODES elements. This module is the one that writes rendered Markdown
|
|
58
|
+
* (web-csp-1268); the panel asks it. Returns "md" or "plain".
|
|
59
|
+
*/
|
|
60
|
+
export function drawMarkdown(el, src) {
|
|
61
|
+
const doc = el.ownerDocument;
|
|
62
|
+
el.textContent = "";
|
|
63
|
+
const html = globalThis.AgendChatRender.renderMarkdown(String(src));
|
|
64
|
+
// Each code block gains three elements below (its frame, head and label): counted before anything is built.
|
|
65
|
+
const pres = (html.match(/<pre[\s>]/g) || []).length;
|
|
66
|
+
if (tagsIn(html) + 3 * pres > TEXT_PREVIEW_NODES) {
|
|
67
|
+
const pre = doc.createElement("pre"), code = doc.createElement("code");
|
|
68
|
+
pre.className = "tc-pre"; code.textContent = String(src); pre.append(code); el.append(pre);
|
|
69
|
+
return "plain";
|
|
70
|
+
}
|
|
71
|
+
// Inside the chat's own message container, so the chat's Markdown look applies as it is (lists, links, tables, code)
|
|
72
|
+
// — also with a stylesheet from before this panel existed.
|
|
73
|
+
const msg = doc.createElement("div"), md = doc.createElement("div");
|
|
74
|
+
msg.className = "msg pv-md-msg";
|
|
75
|
+
md.className = "md";
|
|
76
|
+
md.innerHTML = html;
|
|
77
|
+
// Code blocks in the chat's frame, with their language (the thread's Wrap/Copy controls belong to the thread).
|
|
78
|
+
for (const pre of md.querySelectorAll("pre")) {
|
|
79
|
+
const box = doc.createElement("div"), head = doc.createElement("div"), lang = doc.createElement("span");
|
|
80
|
+
box.className = "codeblock"; head.className = "cb-head"; lang.className = "cb-lang";
|
|
81
|
+
lang.textContent = pre.dataset.lang || "text";
|
|
82
|
+
head.append(lang);
|
|
83
|
+
pre.replaceWith(box); box.append(head, pre);
|
|
84
|
+
}
|
|
85
|
+
msg.append(md);
|
|
86
|
+
el.append(msg);
|
|
87
|
+
return "md";
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Board 00ef2430: a thread opens with only its newest messages drawn — enough for a screen — so switching chats paints
|
|
91
|
+
* at once; the older ones are drawn after that, a chunk at a time between frames, the reader's place kept.
|
|
92
|
+
*/
|
|
93
|
+
export const THREAD_TAIL = 24;
|
|
94
|
+
export const THREAD_CHUNK = 40;
|
|
95
|
+
/** #2158: a reader's scroll (smooth, momentum) is theirs until it has been this quiet since their last input or scroll. */
|
|
96
|
+
export const READER_QUIET_MS = 250;
|
|
97
|
+
const SCROLL_KEYS = new Set(["PageUp", "PageDown", "ArrowUp", "ArrowDown", "Home", "End", " "]);
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* #2053: a message's rendered Markdown, kept across renders and chat switches. A draw compares each message's html with
|
|
101
|
+
* its node's, so every render of a thread rendered every drawn message's Markdown again (~320 calls per 200-message
|
|
102
|
+
* switch). renderMarkdown's output depends only on the text, whether its HTML cards are on (an agent's message), and —
|
|
103
|
+
* for a text that has them — whether the page knows each server-emoji id and the name it has for each mention: the key
|
|
104
|
+
* holds exactly those, so a cached html is always what renderMarkdown would return now. Bounded: least recently used out.
|
|
105
|
+
*/
|
|
106
|
+
export const MD_CACHE_MAX = 4000;
|
|
107
|
+
const mdCache = new Map();
|
|
108
|
+
const EMOJI_IDS = /<a?:[A-Za-z0-9_]{2,32}:(\d{15,25})>/g;
|
|
109
|
+
const MENTION_IDS = /<(@!?|@&|#)(\d{15,25})>/g;
|
|
110
|
+
const MENTION_KINDS = { "@": "u", "@!": "u", "@&": "r", "#": "c" };
|
|
111
|
+
export function renderedMarkdown(text, cards, emojis, mentions) {
|
|
112
|
+
const src = typeof text === "string" ? text : String(text == null ? "" : text);
|
|
113
|
+
let sig = cards ? "c" : "-";
|
|
114
|
+
if (src.includes("<")) {
|
|
115
|
+
// Read as renderMarkdown reads it: it drops NUL first (its placeholder mark), so "<@\0id>" is a mention there too.
|
|
116
|
+
const seen = src.includes("\u0000") ? src.replace(/\u0000/g, "") : src;
|
|
117
|
+
for (const m of seen.matchAll(EMOJI_IDS)) sig += `|e${m[1]}${emojis && emojis(m[1]) ? 1 : 0}`;
|
|
118
|
+
for (const m of seen.matchAll(MENTION_IDS)) { const name = mentions ? mentions(MENTION_KINDS[m[1]], m[2]) : null; sig += `|m${m[1]}${m[2]}=${typeof name === "string" ? name.length + ":" + name : "-"}`; }
|
|
119
|
+
}
|
|
120
|
+
const key = `${sig}\u0000${src}`;
|
|
121
|
+
let html = mdCache.get(key);
|
|
122
|
+
if (html === undefined) html = R().renderMarkdown(src, { htmlCards: cards, emojis, mentions });
|
|
123
|
+
else mdCache.delete(key);
|
|
124
|
+
mdCache.set(key, html);
|
|
125
|
+
while (mdCache.size > MD_CACHE_MAX) mdCache.delete(mdCache.keys().next().value);
|
|
126
|
+
return html;
|
|
127
|
+
}
|
|
128
|
+
/** Tests only: forget every rendered Markdown. */
|
|
129
|
+
export function clearRenderedMarkdown() { mdCache.clear(); }
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Bind a renderer to `list` (the thread element) inside `scroller`. opts: { t, tf, isUser(x), onJump({show, unseen}),
|
|
133
|
+
* setPreviewOptIn(on), copyText(text) → Promise<boolean>, download(code), panel? }. panel (#1481): { shown() → the card
|
|
134
|
+
* key the side panel shows, or null; open(spec, { run }); version(); claim(); claims() } — without it a card has no "Open in panel".
|
|
135
|
+
*/
|
|
136
|
+
export function createThread(list, scroller, opts) {
|
|
137
|
+
const nodes = new Map(); // key → { html, node }
|
|
138
|
+
// A server emoji's picture that does not load reads `:name:` (its alt) instead of an empty box (error does not bubble:
|
|
139
|
+
// caught on the way down).
|
|
140
|
+
list.addEventListener("error", (e) => {
|
|
141
|
+
const img = e.target;
|
|
142
|
+
if (!img || img.tagName !== "IMG" || !img.classList || !img.classList.contains("emoji")) return;
|
|
143
|
+
const span = list.ownerDocument.createElement("span");
|
|
144
|
+
span.className = "emoji-text";
|
|
145
|
+
span.textContent = img.getAttribute("alt") || "";
|
|
146
|
+
span.title = img.getAttribute("title") || span.textContent;
|
|
147
|
+
img.replaceWith(span);
|
|
148
|
+
}, true);
|
|
149
|
+
let msgs = [];
|
|
150
|
+
let unseen = 0;
|
|
151
|
+
// The reader is at the newest message. While pinned, content that grows after it was drawn — an image loading, a
|
|
152
|
+
// preview card, the composer growing — keeps the view at the bottom (#1269); a reader who scrolled up is never moved.
|
|
153
|
+
// Only a move UP unpins: the browser's own scroll anchoring also fires scroll events when content grows (the view
|
|
154
|
+
// moves down or stays), and those must not count as the reader leaving the bottom. Reaching the bottom pins again.
|
|
155
|
+
let pinned = true;
|
|
156
|
+
// Where the view was (and how tall it was) at the last scroll the page saw or made: a move up from there unpins.
|
|
157
|
+
let lastTop = 0, lastView = 0;
|
|
158
|
+
const mark = () => { lastTop = scroller.scrollTop; lastView = scroller.clientHeight; };
|
|
159
|
+
// The oldest message drawn (by key): everything after it is drawn, so new messages never push the window. Null: none
|
|
160
|
+
// drawn yet — the next render draws the newest THREAD_TAIL and the rest follows (grow).
|
|
161
|
+
let firstKey = null;
|
|
162
|
+
let growTimer = null, growFrame = null, disposed = false, parked = false;
|
|
163
|
+
const cardRefresh = new WeakMap(); // card node → re-read availability (weak: a card that left takes its source with it)
|
|
164
|
+
const cardDrop = new WeakMap(); // card node → drop the HTML read it is waiting on (the chat left: park)
|
|
165
|
+
const tr = opts.t, trf = opts.tf;
|
|
166
|
+
const stopIn = (node) => (P() ? P().stopIn(node) : []);
|
|
167
|
+
/** #1770: write a message node's time label for now; unchanged text is not touched. */
|
|
168
|
+
function timeLabel(node) {
|
|
169
|
+
const el = node.querySelector(".meta .time[data-ts]");
|
|
170
|
+
if (!el) return;
|
|
171
|
+
const v = chatTime(el.dataset.ts, { yesterday: (hm) => trf("chat.yesterdayAt", hm) });
|
|
172
|
+
if (el.textContent !== v) el.textContent = v;
|
|
173
|
+
}
|
|
174
|
+
// #1770: "today" ends at the fleet's midnight; the labels are rewritten then (and on every render), while there are any.
|
|
175
|
+
let offDay = null;
|
|
176
|
+
function armDay() {
|
|
177
|
+
if (nodes.size && !offDay) offDay = onDayChange(() => { for (const v of nodes.values()) timeLabel(v.node); });
|
|
178
|
+
else if (!nodes.size && offDay) { offDay(); offDay = null; }
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const TICKS = () => ({ queued: tr("chat.tick_queued"), processing: tr("chat.tick_processing"), delivered: tr("chat.tick_delivered"), failed: tr("chat.tick_failed"), cancelled: tr("chat.tick_cancelled") });
|
|
182
|
+
|
|
183
|
+
function msgHtml(x) {
|
|
184
|
+
// #1770: the label ("17:38", "Yesterday 17:38", "10/11 17:38") depends on today, so it is not part of the html a node
|
|
185
|
+
// is kept by: it is written into the node (timeLabels) — crossing midnight relabels in place and reloads no preview.
|
|
186
|
+
const time = x.ts ? `<span class="time" data-ts="${escAttr(x.ts)}" title="${escAttr(chatTimeFull(x.ts))}"></span>` : `<span class="time"></span>`;
|
|
187
|
+
if (x.outgoing) return outgoingHtml(x);
|
|
188
|
+
if (x.role === "status") return statusHtml(x, time);
|
|
189
|
+
const u = opts.isUser(x);
|
|
190
|
+
// renderMarkdown escapes the whole text before it adds any tag of its own (chat-render.js).
|
|
191
|
+
const ticks = u && x.delivery ? R().deliveryHtml(x.delivery, TICKS()) : "";
|
|
192
|
+
const tools = u ? "" : `<div data-trust="thread-control" class="msg-tools"><button type="button" class="chip-btn icon-only" data-act="copyMsg" data-arg="${escAttr(msgKey(x))}" title="${escAttr(tr("chat.copyMessage"))}" aria-label="${escAttr(tr("chat.copyMessage"))}">${COPY}</button></div>`;
|
|
193
|
+
const buttons = x.role === "agent" ? replyButtonsHtml(x.buttons) : "";
|
|
194
|
+
// A person's click on a reply button shows as the button's label, never the line the agent was given (its value).
|
|
195
|
+
const clicked = x.role === "user" && x.click && typeof x.click.label === "string";
|
|
196
|
+
// #2272: reply to it — a hover strip at its top right (phones: swipe left or long-press; R on a focused message).
|
|
197
|
+
const key = msgKey(x);
|
|
198
|
+
const act = opts.canReply?.() === false ? "" : `<div data-trust="thread-control" class="msg-actions"><button type="button" class="chip-btn icon-only" data-act="reply" data-arg="${escAttr(key)}" title="${escAttr(tr("chat.reply"))}" aria-label="${escAttr(tr("chat.reply"))}">${REPLY}</button></div>`;
|
|
199
|
+
return `<div class="msg ${u ? "user" : "agent"}" data-key="${escAttr(key)}" tabindex="-1">${act}<div class="meta"><span class="sender">${escAttr(x.sender)}</span>${time}${ticks}</div>${quoteHtml(x.replyTo)}<div class="body">${clicked ? clickHtml(x.click) : `<div class="md">${renderedMarkdown(x.text, x.role === "agent", opts.emojis, opts.mentions)}</div>`}${R().attachmentsHtml(x.attachments, { htmlCards: x.role === "agent", goneTitle: tr("chat.attGone"), goneLabel: tr("chat.attUnavailable"), inlinePreviews: !PUBLIC_LINK(), textPreviews: true })}${buttons}</div>${R().reactionsHtml(x.reactions, tr("chat.reactedBy", x.instance || ""))}${tools}</div>`;
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* A message this page is sending (chat-store.js): its own bubble at once, dimmed with "Sending…" until the server
|
|
203
|
+
* takes it, then as sent until the server's copy replaces it; failed: marked, with Retry and Delete. Each file shows
|
|
204
|
+
* its upload — a picture as itself (a local preview), anything else by name — with its own bar, moved in place by
|
|
205
|
+
* progress() (the html does not hold the numbers, so a moving bar never redraws the bubble). No data-key: it is no
|
|
206
|
+
* message yet (nothing replies to it, links to it or copies it).
|
|
207
|
+
*/
|
|
208
|
+
function outgoingHtml(x) {
|
|
209
|
+
const st = x.outgoing.state, going = st === "queued" || st === "sending";
|
|
210
|
+
const files = (x.files || []).map((f, i) => {
|
|
211
|
+
const pic = f.url ? `<img class="out-pic" src="${escAttr(f.url)}" alt="${escAttr(f.name)}">` : `<span class="out-doc" aria-hidden="true"></span>`;
|
|
212
|
+
return `<div class="out-file" data-i="${escAttr(i)}">${pic}<div class="out-file-info"><span class="out-name">${escAttr(f.name)}</span>`
|
|
213
|
+
+ `<span class="out-bar" role="progressbar" aria-label="${escAttr(trf("chat.uploadingFile", f.name))}" aria-valuemin="0" aria-valuemax="100"><span class="out-fill"></span></span>`
|
|
214
|
+
+ `<span class="out-pct"></span></div></div>`;
|
|
215
|
+
}).join("");
|
|
216
|
+
const failed = st === "failed" ? `<div data-trust="thread-control" class="out-note lbar" role="alert"><span class="out-error">${escAttr(trf("chat.notSentBecause", x.outgoing.error || ""))}</span>`
|
|
217
|
+
+ `<span class="out-actions"><button type="button" class="btn btn-sm btn-primary" data-act="retryOut" data-arg="${escAttr(x.clientId)}">${escAttr(tr("chat.retrySend"))}</button>`
|
|
218
|
+
+ `<button type="button" class="btn btn-sm" data-act="putBackOut" data-arg="${escAttr(x.clientId)}">${escAttr(tr("chat.putBack"))}</button>`
|
|
219
|
+
+ `<button type="button" class="btn btn-sm" data-act="deleteOut" data-arg="${escAttr(x.clientId)}">${escAttr(tr("chat.deleteUnsent"))}</button></span></div>` : "";
|
|
220
|
+
const state = going ? `<span class="out-state" role="status"><span class="out-spin" aria-hidden="true"></span>${escAttr(tr("chat.sending"))}</span>` : "";
|
|
221
|
+
return `<div class="msg user out out-${st}" data-out="${escAttr(x.clientId)}"${going ? ' aria-busy="true"' : ""} tabindex="-1"><div class="meta"><span class="sender">${escAttr(x.sender)}</span>${state}</div>`
|
|
222
|
+
+ `${quoteHtml(x.replyTo)}<div class="body">${x.text ? `<div class="md">${renderedMarkdown(x.text, false, opts.emojis, opts.mentions)}</div>` : ""}`
|
|
223
|
+
+ `${files ? `<div class="out-files">${files}</div>` : ""}</div>${failed}</div>`;
|
|
224
|
+
}
|
|
225
|
+
/** The pending bubbles' upload bars and percentages, as their files stand now (in place: CSSOM, no redraw). */
|
|
226
|
+
function progress() {
|
|
227
|
+
for (const v of nodes.values()) {
|
|
228
|
+
const x = v.x;
|
|
229
|
+
if (!x || !x.outgoing || !x.files || !x.files.length) continue;
|
|
230
|
+
for (const el of v.node.querySelectorAll(".out-file[data-i]")) {
|
|
231
|
+
const f = x.files[Number(el.dataset.i)];
|
|
232
|
+
if (!f) continue;
|
|
233
|
+
const pct = f.state === "done" ? 100 : f.total > 0 ? Math.min(100, Math.floor((f.loaded / f.total) * 100)) : 0;
|
|
234
|
+
el.dataset.state = f.state;
|
|
235
|
+
const bar = el.querySelector(".out-bar"), fill = el.querySelector(".out-fill"), label = el.querySelector(".out-pct");
|
|
236
|
+
if (bar) bar.setAttribute("aria-valuenow", String(pct));
|
|
237
|
+
if (fill) fill.style.transform = `scaleX(${pct / 100})`;
|
|
238
|
+
if (label) { const text = f.state === "failed" ? tr("chat.uploadFailed") : f.state === "waiting" ? tr("chat.uploadWaiting") : `${pct}%`; if (label.textContent !== text) label.textContent = text; }
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* #2272: what a reply quotes, above its body — the quoted sender and first words (or that it had files), as the server
|
|
245
|
+
* read them when it was sent; a button that shows that message. A guest that may not see it gets only "an earlier
|
|
246
|
+
* message". Text only: everything is escaped.
|
|
247
|
+
*/
|
|
248
|
+
function quoteHtml(q) {
|
|
249
|
+
if (!q || typeof q !== "object" || typeof q.boot !== "string" || typeof q.id !== "number") return "";
|
|
250
|
+
const words = q.hidden ? tr("chat.replyHidden")
|
|
251
|
+
: (typeof q.text === "string" && q.text) || (q.media === "photo" ? tr("chat.replyPhoto") : q.media === "document" ? tr("chat.replyFile") : "");
|
|
252
|
+
const who = !q.hidden && typeof q.sender === "string" && q.sender ? `<span class="quote-sender">${escAttr(q.sender)}</span> ` : "";
|
|
253
|
+
return `<button type="button" data-trust="thread-control" class="msg-quote" data-act="jumpQuote" data-arg="${escAttr(`${q.boot}-${q.id}`)}" title="${escAttr(tr("chat.replyJump"))}"><span class="quote-mark">${REPLY}</span> ${who}<span class="quote-text">${escAttr(words)}</span></button>`;
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* A button click as a chip: 🔘 and the label (escaped: the label is the agent's text). With `of` (live, while its
|
|
257
|
+
* reply is in this thread) it is a button that shows that reply; restored from disk, plain.
|
|
258
|
+
*/
|
|
259
|
+
function clickHtml(click) {
|
|
260
|
+
const inner = `<span class="click-mark" aria-hidden="true">🔘</span><span class="sr-only">${escAttr(tr("chat.clicked"))} </span>${escAttr(click.label)}`;
|
|
261
|
+
const of = typeof click.of === "string" && /^[0-9a-f]{32}$/.test(click.of) ? click.of : null;
|
|
262
|
+
return `<div class="click-chip" data-trust="thread-control">${of
|
|
263
|
+
? `<button type="button" class="click-link" data-act="showButtons" data-arg="${escAttr(of)}" title="${escAttr(tr("chat.clickShow"))}">${inner}</button>`
|
|
264
|
+
: `<span class="click-text">${inner}</span>`}</div>`;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* #1672: a status line (another instance's message, a daemon notice) is muted, read-only plain text — never Markdown,
|
|
269
|
+
* a preview card, a button or a person's bubble. Checked before isUser, which reads the sender: a fleet name need not
|
|
270
|
+
* start with "agend". A long one is folded until it is opened.
|
|
271
|
+
*/
|
|
272
|
+
function statusHtml(x, time) {
|
|
273
|
+
const text = String(x.text == null ? "" : x.text);
|
|
274
|
+
// Rows split on any line break (a CRLF is one break, never a row and a stray CR); the text itself is not changed.
|
|
275
|
+
const lines = R().lineCount(text.replace(/\r\n?/g, "\n"));
|
|
276
|
+
const manyLines = lines > STATUS_FOLD_LINES;
|
|
277
|
+
const overflows = manyLines || text.split(/\r\n?|\n/).some(l => l.length > STATUS_LINE_CHARS);
|
|
278
|
+
// Its control: a short label at the end of line 2; its full name (and whether it is open) for a screen reader. Shown
|
|
279
|
+
// only while the line overflows (class `overflows`, kept by fitStatus at the width it is drawn at).
|
|
280
|
+
const more = `<button data-trust="thread-control" type="button" class="st-more" data-act="toggleFold" aria-expanded="false"${manyLines ? ` data-lines="${escAttr(lines)}"` : ""} aria-label="${escAttr(manyLines ? trf("chat.showAll", lines) : tr("chat.showMore"))}">${escAttr(manyLines ? trf("chat.showAllShort", lines) : tr("chat.showMoreShort"))}</button>`;
|
|
281
|
+
const tools = `<div data-trust="thread-control" class="msg-tools"><button type="button" class="chip-btn icon-only" data-act="copyMsg" data-arg="${escAttr(msgKey(x))}" title="${escAttr(tr("chat.copyMessage"))}" aria-label="${escAttr(tr("chat.copyMessage"))}">${COPY}</button></div>`;
|
|
282
|
+
// #1990: the other instance's name opens its chat (an in-app link the router takes: no target).
|
|
283
|
+
// Each line its own row (folded: the first two, each clipped to one row); the line breaks stay in the text.
|
|
284
|
+
const body = textLinkSegments(text, x.links).map(sg => sg.href ? `<a data-trust="link" class="st-link" href="${escAttr(sg.href)}">${escAttr(sg.text)}</a>` : escAttr(sg.text)).join("")
|
|
285
|
+
.split(/\r\n?|\n/).map(l => `<span class="st-ln">${l}</span>`).join("\n");
|
|
286
|
+
return `<div class="msg status-line folded${overflows ? " overflows" : ""}"><div class="meta">${time}</div><div class="body"><div class="st-text">${body}</div>${more}</div>${tools}</div>`;
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* #1266: an agent reply's buttons. Labels are text (escaped, never Markdown); the click names the set and the index,
|
|
290
|
+
* nothing else. Ended (chosen / expired) or a click in flight: every button off; the chosen one marked, and who.
|
|
291
|
+
*/
|
|
292
|
+
function replyButtonsHtml(b) {
|
|
293
|
+
if (!b || typeof b.id !== "string" || !Array.isArray(b.labels) || !b.labels.length) return "";
|
|
294
|
+
if (b.poll && typeof b.poll === "object") return pollHtml(b);
|
|
295
|
+
const busy = !!(opts.replyButtonBusy && opts.replyButtonBusy(b.id));
|
|
296
|
+
const off = b.state !== "open" || busy;
|
|
297
|
+
const items = b.labels.map((label, i) => {
|
|
298
|
+
const chosen = b.state === "chosen" && b.chosen === i;
|
|
299
|
+
return `<button type="button" class="btn btn-sm rb-btn${chosen ? " chosen" : ""}" data-act="replyButton" data-arg="${escAttr(`${b.id}:${i}`)}"${off ? " disabled" : ""}>${escAttr(chosen ? `✓ ${label}` : label)}</button>`;
|
|
300
|
+
}).join("");
|
|
301
|
+
const note = b.state === "chosen" ? trf("chat.rbChosen", b.by || "") : b.state === "expired" ? tr("chat.rbExpired") : "";
|
|
302
|
+
return `<div data-trust="reply" class="rb-row" role="group" aria-label="${escAttr(tr("chat.rbGroup"))}">${items}${note ? `<span class="rb-note">${escAttr(note)}</span>` : ""}</div>`;
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* #1823: a poll. Anyone here may click while it is open; the page marks its own vote (opts.pollMine), which no one else
|
|
306
|
+
* sees. Counts only when the server sends them (revealed: live, or closed); names only at close with show_voters. The
|
|
307
|
+
* time left is not in the html (a node is kept by its html): pollLeft writes it, every second.
|
|
308
|
+
*/
|
|
309
|
+
function pollHtml(b) {
|
|
310
|
+
const p = b.poll, closed = b.state === "closed";
|
|
311
|
+
const busy = !!(opts.replyButtonBusy && opts.replyButtonBusy(b.id));
|
|
312
|
+
const mine = (opts.pollMine && opts.pollMine(b.id)) || [];
|
|
313
|
+
const counts = Array.isArray(p.counts) && p.counts.length === b.labels.length ? p.counts : null;
|
|
314
|
+
const items = b.labels.map((label, i) => {
|
|
315
|
+
const me = !closed && mine.includes(i);
|
|
316
|
+
const tally = counts ? ` · ${counts[i]}${closed && p.answered ? ` (${Math.round((counts[i] / p.answered) * 100)}%)` : ""}` : "";
|
|
317
|
+
return `<button type="button" class="btn btn-sm rb-btn${me ? " chosen" : ""}" data-act="replyButton" data-arg="${escAttr(`${b.id}:${i}`)}" aria-pressed="${me ? "true" : "false"}"${closed || busy ? " disabled" : ""}>${escAttr(`${me ? "✓ " : ""}${label}${tally}`)}</button>`;
|
|
318
|
+
}).join("");
|
|
319
|
+
const status = closed
|
|
320
|
+
? escAttr(trf("chat.pollClosed", p.answered))
|
|
321
|
+
: `${escAttr(trf("chat.pollAnswered", p.answered))} · ⏳ <span class="poll-left" data-deadline="${escAttr(p.deadline)}" title="${escAttr(tr("chat.pollLeft"))}"></span>`;
|
|
322
|
+
const voters = closed && Array.isArray(p.voters)
|
|
323
|
+
? `<ul data-trust="reply" class="poll-voters" aria-label="${escAttr(tr("chat.pollVoters"))}">${b.labels.map((label, i) => `<li>${escAttr(`${label}: ${(p.voters[i] || []).join(", ") || "—"}`)}</li>`).join("")}</ul>`
|
|
324
|
+
: "";
|
|
325
|
+
return `<div data-trust="reply" class="rb-row poll" role="group" aria-label="${escAttr(tr("chat.pollGroup"))}">${items}<span class="rb-note">${status}</span></div>${voters}`;
|
|
326
|
+
}
|
|
327
|
+
/** #1823: the time left on this node's open polls, now. */
|
|
328
|
+
function pollLeft(node) {
|
|
329
|
+
for (const el of node.querySelectorAll(".poll-left[data-deadline]")) {
|
|
330
|
+
const v = R().pollRemaining(Number(el.dataset.deadline) - (opts.serverNow ? opts.serverNow() : Date.now()));
|
|
331
|
+
if (el.textContent !== v) el.textContent = v;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
// #1823: while an open poll is on screen, its time left is rewritten every second.
|
|
335
|
+
let pollTick = null;
|
|
336
|
+
function armPolls() {
|
|
337
|
+
const open = nodes.size > 0 && !!list.querySelector(".poll-left[data-deadline]");
|
|
338
|
+
if (open && !pollTick) pollTick = setInterval(() => pollLeft(list), 1000);
|
|
339
|
+
else if (!open && pollTick) { clearInterval(pollTick); pollTick = null; }
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
function msgNode(html, x, stoppedKeys) {
|
|
343
|
+
const tpl = list.ownerDocument.createElement("template");
|
|
344
|
+
tpl.innerHTML = html;
|
|
345
|
+
const node = tpl.content.firstElementChild;
|
|
346
|
+
decorateCode(node);
|
|
347
|
+
timeLabel(node);
|
|
348
|
+
pollLeft(node);
|
|
349
|
+
if (x) decorateHtmlCards(node, x, stoppedKeys || []);
|
|
350
|
+
if (x) decorateTextCards(node, x);
|
|
351
|
+
return node;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// Code blocks: a header (language, Wrap, Copy); a long one is folded until opened. Built from nodes.
|
|
355
|
+
function decorateCode(root) {
|
|
356
|
+
const doc = list.ownerDocument;
|
|
357
|
+
for (const pre of root.querySelectorAll(".md pre")) {
|
|
358
|
+
const box = doc.createElement("div"); box.className = "codeblock";
|
|
359
|
+
const head = doc.createElement("div"); head.className = "cb-head"; head.setAttribute("data-trust", "thread-control");
|
|
360
|
+
const lang = doc.createElement("span"); lang.className = "cb-lang"; lang.textContent = pre.dataset.lang || "text";
|
|
361
|
+
const wrap = doc.createElement("button"); wrap.type = "button"; wrap.className = "chip-btn"; wrap.dataset.act = "toggleWrap"; wrap.textContent = tr("chat.wrap");
|
|
362
|
+
const copy = doc.createElement("button"); copy.type = "button"; copy.className = "chip-btn"; copy.dataset.act = "copyCode"; copy.textContent = tr("chat.copy");
|
|
363
|
+
head.append(lang, wrap, copy);
|
|
364
|
+
pre.replaceWith(box); box.append(head, pre);
|
|
365
|
+
const lines = R().lineCount(pre.textContent);
|
|
366
|
+
if (lines > R().CODE_FOLD_LINES) {
|
|
367
|
+
box.classList.add("folded");
|
|
368
|
+
const more = doc.createElement("button"); more.type = "button"; more.className = "cb-more"; more.setAttribute("data-trust", "thread-control"); more.dataset.act = "toggleFold";
|
|
369
|
+
more.dataset.lines = String(lines); more.textContent = trf("chat.showAll", lines);
|
|
370
|
+
box.append(more);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// HTML cards (#1306): each ```html fence of a server-marked agent message gets a card under its code block, and each
|
|
376
|
+
// .html/.htm file it attached gets one under its files (§6.1, Q4). Building a card makes no frame and fetches
|
|
377
|
+
// nothing; Preview does (AgendPreview.start → mountPreview), after reading an attachment's HTML on that click.
|
|
378
|
+
function decorateHtmlCards(node, x, stoppedKeys) {
|
|
379
|
+
const doc = list.ownerDocument;
|
|
380
|
+
const fences = R().htmlFences(x.text);
|
|
381
|
+
for (const ph of node.querySelectorAll(".html-card[data-card]")) {
|
|
382
|
+
const n = Number(ph.dataset.card), fence = fences[n];
|
|
383
|
+
if (!fence) continue;
|
|
384
|
+
ph.textContent = "";
|
|
385
|
+
if (ph.classList.contains("truncated") || !fence.terminated) {
|
|
386
|
+
const note = doc.createElement("div"); note.className = "pv-note"; note.setAttribute("data-trust", "thread-control"); note.textContent = tr("chat.pvTruncated"); ph.append(note);
|
|
387
|
+
continue;
|
|
388
|
+
}
|
|
389
|
+
buildCard(ph, x, `${msgKey(x)}:f${n}`, { n, code: fence.code }, stoppedKeys);
|
|
390
|
+
}
|
|
391
|
+
const atts = Array.isArray(x.attachments) ? x.attachments : [];
|
|
392
|
+
for (const ph of node.querySelectorAll(".html-card[data-att]")) {
|
|
393
|
+
const att = atts.find(a => a && a.id === ph.dataset.att);
|
|
394
|
+
if (!att || !R().isHtmlAttachment(att)) continue;
|
|
395
|
+
ph.textContent = "";
|
|
396
|
+
buildCard(ph, x, `${msgKey(x)}:a${att.id}`, { att: { id: att.id, name: att.name, size: att.size, url: att.url } }, stoppedKeys);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
// #1589: a text attachment (md, csv, json, code, plain) gets a card: Show reads the file once (the same capped,
|
|
400
|
+
// same-origin read as an HTML attachment's) and shows it — Markdown through renderMarkdown (it escapes first), code
|
|
401
|
+
// through the highlighter (escaped), csv as a table and anything else as text, all built with textContent. Nothing in
|
|
402
|
+
// the file is ever run: no frame, no script, no HTML of its own.
|
|
403
|
+
function decorateTextCards(node, x) {
|
|
404
|
+
const atts = Array.isArray(x.attachments) ? x.attachments : [];
|
|
405
|
+
for (const ph of node.querySelectorAll(".text-card[data-att]")) {
|
|
406
|
+
const att = atts.find(a => a && a.id === ph.dataset.att);
|
|
407
|
+
const type = att ? R().attachmentPreviewType(att) : null;
|
|
408
|
+
if (!att || !type || type !== ph.dataset.type) continue;
|
|
409
|
+
buildTextCard(ph, att, type, x);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
function buildTextCard(ph, att, type, x) {
|
|
413
|
+
const doc = list.ownerDocument;
|
|
414
|
+
const el = (tag, cls, text) => { const e = doc.createElement(tag); if (cls) e.className = cls; if (text != null) e.textContent = text; return e; };
|
|
415
|
+
ph.textContent = "";
|
|
416
|
+
const head = el("div", "tc-head"); head.setAttribute("data-trust", "thread-control");
|
|
417
|
+
const label = el("span", "tc-label", att.name);
|
|
418
|
+
const show = el("button", "btn btn-sm tc-show", tr("chat.tcShow")); show.type = "button";
|
|
419
|
+
const dl = el("a", "btn btn-sm tc-dl", tr("chat.tcDownload")); dl.href = R().attachmentUrl(att); dl.setAttribute("download", att.name);
|
|
420
|
+
head.append(label, show);
|
|
421
|
+
// M-2448: a Markdown file opens in the side panel too (the panel reads it, or takes what Show already read).
|
|
422
|
+
const panel = opts.panel || null;
|
|
423
|
+
if (type === "md" && panel && typeof panel.openMarkdown === "function" && x) {
|
|
424
|
+
const toPanel = el("button", "btn btn-sm btn-ghost tc-panel", tr("chat.pvOpenPanel")); toPanel.type = "button";
|
|
425
|
+
toPanel.onclick = () => panel.openMarkdown({ key: `${msgKey(x)}:m${att.id}`, instance: x.instance, msgKey: msgKey(x),
|
|
426
|
+
att: { id: att.id, name: att.name, size: att.size, url: att.url }, sender: x.sender, ts: x.ts, ...(text !== null ? { text } : {}) });
|
|
427
|
+
head.append(toPanel);
|
|
428
|
+
}
|
|
429
|
+
head.append(dl);
|
|
430
|
+
const note = el("div", "pv-note"); note.setAttribute("data-trust", "thread-control");
|
|
431
|
+
const body = el("div", "tc-body"); body.hidden = true;
|
|
432
|
+
ph.append(head, note, body);
|
|
433
|
+
let text = null, reading = 0, reads = 0, all = false;
|
|
434
|
+
const draw = () => {
|
|
435
|
+
body.textContent = "";
|
|
436
|
+
const lines = text.split(/\r?\n/);
|
|
437
|
+
const cut = !all && lines.length > TEXT_PREVIEW_LINES;
|
|
438
|
+
const shown = cut ? lines.slice(0, TEXT_PREVIEW_LINES) : lines;
|
|
439
|
+
const src = shown.join("\n");
|
|
440
|
+
// Formatting is built only within TEXT_PREVIEW_NODES: the markup is measured as a string first, never as DOM.
|
|
441
|
+
const plain = () => {
|
|
442
|
+
const pre = el("pre", "tc-pre"), code = el("code", null, src);
|
|
443
|
+
const note = el("div", "pv-note", tr("chat.tcPlain")); note.setAttribute("data-trust", "thread-control");
|
|
444
|
+
pre.append(code); body.append(note, pre);
|
|
445
|
+
};
|
|
446
|
+
if (type === "md") {
|
|
447
|
+
const html = R().renderMarkdown(src);
|
|
448
|
+
if (tagCount(html) > TEXT_PREVIEW_NODES) plain();
|
|
449
|
+
else { const md = el("div", "md"); md.innerHTML = html; body.append(md); }
|
|
450
|
+
} else if (type === "csv" || type === "tsv") {
|
|
451
|
+
const table = csvTable(src, type === "tsv" ? "\t" : ",", el);
|
|
452
|
+
if (table) body.append(table); else plain();
|
|
453
|
+
} else {
|
|
454
|
+
const lang = type === "json" ? "json" : type.startsWith("code:") ? type.slice(5) : "";
|
|
455
|
+
const lit = lang ? R().highlight(src, lang) : null;
|
|
456
|
+
if (lit != null && tagCount(lit) > TEXT_PREVIEW_NODES) plain();
|
|
457
|
+
else {
|
|
458
|
+
const pre = el("pre", "tc-pre"), code = el("code");
|
|
459
|
+
if (lit != null) code.innerHTML = lit; else code.textContent = src;
|
|
460
|
+
pre.append(code); body.append(pre);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
if (cut) {
|
|
464
|
+
const more = el("button", "btn btn-sm tc-more", trf("chat.tcShowAll", lines.length)); more.setAttribute("data-trust", "thread-control"); more.type = "button";
|
|
465
|
+
more.onclick = () => { all = true; draw(); };
|
|
466
|
+
body.append(more);
|
|
467
|
+
}
|
|
468
|
+
};
|
|
469
|
+
// #2053: as an HTML card's read (cardDrop), one still waiting when the chat left is dropped, the card not left Loading.
|
|
470
|
+
cardDrop.set(ph, () => { if (reading) { reading = 0; note.textContent = ""; } });
|
|
471
|
+
show.onclick = async () => {
|
|
472
|
+
if (reading) return;
|
|
473
|
+
if (text !== null) { body.hidden = !body.hidden; show.textContent = tr(body.hidden ? "chat.tcShow" : "chat.tcHide"); return; }
|
|
474
|
+
const mine = reading = ++reads; note.textContent = tr("chat.pvLoading");
|
|
475
|
+
const r = await loadHtmlAttachment({ id: att.id, name: att.name, size: att.size, url: att.url }, undefined, { ask: TEXT_CARD_MAX, max: TEXT_CARD_MAX });
|
|
476
|
+
if (reading !== mine) return; // dropped (its chat left meanwhile): a new click reads again
|
|
477
|
+
reading = 0;
|
|
478
|
+
if (!ph.isConnected) return; // the card left the page while it was read: nothing to draw
|
|
479
|
+
if (!r.ok) { note.textContent = r.reason === "over" ? tr("chat.tcOver") : attReason(r.reason); return; }
|
|
480
|
+
note.textContent = "";
|
|
481
|
+
text = r.code;
|
|
482
|
+
if (type === "json") { try { text = JSON.stringify(JSON.parse(text), null, 2); } catch { /* shown as written */ } }
|
|
483
|
+
draw();
|
|
484
|
+
body.hidden = false; show.textContent = tr("chat.tcHide");
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
/** How many elements an HTML string would build: its opening tags (escaped text has none). */
|
|
488
|
+
function tagCount(html) {
|
|
489
|
+
let n = 0;
|
|
490
|
+
for (let i = html.indexOf("<"); i !== -1; i = html.indexOf("<", i + 1)) { const c = html.charCodeAt(i + 1); if ((c | 32) >= 97 && (c | 32) <= 122) n++; }
|
|
491
|
+
return n;
|
|
492
|
+
}
|
|
493
|
+
/** A csv/tsv as a table: quoted fields ("a, b", "say ""hi""") kept whole; every cell is textContent. */
|
|
494
|
+
function csvTable(src, sep, el) {
|
|
495
|
+
const rows = [];
|
|
496
|
+
let row = [], cell = "", quoted = false;
|
|
497
|
+
for (let i = 0; i < src.length; i++) {
|
|
498
|
+
const c = src[i];
|
|
499
|
+
if (quoted) {
|
|
500
|
+
if (c === '"') { if (src[i + 1] === '"') { cell += '"'; i++; } else quoted = false; }
|
|
501
|
+
else cell += c;
|
|
502
|
+
} else if (c === '"' && cell === "") quoted = true;
|
|
503
|
+
else if (c === sep) { row.push(cell); cell = ""; }
|
|
504
|
+
else if (c === "\n") { row.push(cell.replace(/\r$/, "")); rows.push(row); row = []; cell = ""; }
|
|
505
|
+
else cell += c;
|
|
506
|
+
}
|
|
507
|
+
if (cell !== "" || row.length) { row.push(cell.replace(/\r$/, "")); rows.push(row); }
|
|
508
|
+
// Within the card's budget, or not a table at all (the caller shows it as text).
|
|
509
|
+
let cells = 0;
|
|
510
|
+
for (const r of rows) { cells += r.length; if (r.length > TEXT_PREVIEW_COLUMNS || cells + rows.length > TEXT_PREVIEW_NODES) return null; }
|
|
511
|
+
const table = el("table", "tc-table");
|
|
512
|
+
rows.forEach((r, i) => {
|
|
513
|
+
const tr_ = el("tr");
|
|
514
|
+
for (const v of r) tr_.append(el(i === 0 ? "th" : "td", null, v));
|
|
515
|
+
table.append(tr_);
|
|
516
|
+
});
|
|
517
|
+
const wrap = el("div", "tc-table-wrap"); wrap.append(table);
|
|
518
|
+
return wrap;
|
|
519
|
+
}
|
|
520
|
+
/** Why an attachment's HTML could not be read, for the card's note. */
|
|
521
|
+
const attReason = (reason) => tr(reason === "over" ? "chat.pvAttOver" : reason === "gone" ? "chat.pvAttGone" : "chat.pvAttFailed");
|
|
522
|
+
/** One card: `source` is { n, code } for a fence or { att } for an attachment (its HTML read on the first click). */
|
|
523
|
+
function buildCard(ph, x, key, source, stoppedKeys) {
|
|
524
|
+
const doc = list.ownerDocument;
|
|
525
|
+
const el = (tag, cls, text) => { const e = doc.createElement(tag); if (cls) e.className = cls; if (text != null) e.textContent = text; return e; };
|
|
526
|
+
const att = source.att || null;
|
|
527
|
+
let code = att ? null : source.code;
|
|
528
|
+
const head = el("div", "pv-head"); head.setAttribute("data-trust", "thread-control");
|
|
529
|
+
const label = el("span", "pv-label", att ? `${tr("chat.pvHtml")} · ${att.name}` : tr("chat.pvHtml"));
|
|
530
|
+
const run = el("button", "btn btn-sm pv-run", tr("chat.pvPreview")); run.type = "button";
|
|
531
|
+
// #2863: a large file (over 8 MB) is previewed only on this second click, in the card or the panel as first asked.
|
|
532
|
+
const anywayB = el("button", "btn btn-sm pv-anyway", tr("chat.pvAnyway")); anywayB.type = "button"; anywayB.hidden = true;
|
|
533
|
+
const stopB = el("button", "btn btn-sm pv-stop", tr("chat.pvStop")); stopB.type = "button"; stopB.hidden = true;
|
|
534
|
+
// An attachment already has its download link (the file itself) right above the card.
|
|
535
|
+
const dl = att ? null : el("button", "btn btn-sm btn-ghost pv-dl", tr("chat.pvDownload"));
|
|
536
|
+
if (dl) dl.type = "button";
|
|
537
|
+
const panel = opts.panel || null;
|
|
538
|
+
const toPanel = panel ? el("button", "btn btn-sm btn-ghost pv-open", tr("chat.pvOpenPanel")) : null;
|
|
539
|
+
if (toPanel) toPanel.type = "button";
|
|
540
|
+
const menu = el("details", "pv-menu"); menu.setAttribute("data-trust", "preview-menu");
|
|
541
|
+
const sum = el("summary", null, "⋯"); sum.title = tr("chat.pvMenu"); sum.setAttribute("aria-label", tr("chat.pvMenu"));
|
|
542
|
+
const optB = el("button", "chip-btn pv-opt"); optB.type = "button";
|
|
543
|
+
const neverB = el("button", "chip-btn pv-never"); neverB.type = "button";
|
|
544
|
+
menu.append(sum, optB, neverB);
|
|
545
|
+
head.append(label, run, anywayB, stopB, ...(toPanel ? [toPanel] : []), ...(dl ? [dl] : []), menu);
|
|
546
|
+
const note = el("div", "pv-note"); note.setAttribute("data-trust", "thread-control");
|
|
547
|
+
// #1568: the banner, with its "I understand" (hidden until the next sign-in; the frame's border stays).
|
|
548
|
+
const banner = el("div", "pv-banner"); banner.setAttribute("data-trust", "thread-control"); banner.hidden = true;
|
|
549
|
+
const ack = el("button", "btn btn-sm btn-ghost pv-ack", P().bannerAckLabel()); ack.type = "button"; ack.title = P().bannerAckTitle();
|
|
550
|
+
banner.append(el("span", "pv-banner-text", P().banner()), ack);
|
|
551
|
+
const holder = el("div", "pv-holder");
|
|
552
|
+
ph.append(head, note, banner, holder);
|
|
553
|
+
let state = "idle";
|
|
554
|
+
// Reading an attachment: one read per card at a time (a second click while it reads does nothing), and a read
|
|
555
|
+
// whose card was stopped, replaced or left meanwhile is dropped (`reading` moved on, or the card left the page).
|
|
556
|
+
let reading = 0, reads = 0;
|
|
557
|
+
// #2863: where "Preview anyway" runs it ("card" | "panel") and what the card says meanwhile; null: not asked.
|
|
558
|
+
// `tooBig`: over the 50 MB ceiling (listed, or read) — the card says so and offers only the file's download.
|
|
559
|
+
let large = null, largeNote = "", tooBig = !!att && typeof att.size === "number" && att.size > HTML_PREVIEW_MAX;
|
|
560
|
+
const refresh = (reason) => {
|
|
561
|
+
const a = P().availability();
|
|
562
|
+
const going = state === "starting" || state === "running";
|
|
563
|
+
const inPanel = !!panel && panel.shown() === key; // #1481: the panel shows this block; the card points there
|
|
564
|
+
if (going || reading) large = null;
|
|
565
|
+
if (reason == null && large) reason = largeNote;
|
|
566
|
+
if (reason == null && tooBig) reason = tr("chat.pvAttOver");
|
|
567
|
+
run.hidden = !a.ok || going || inPanel || !!reading || !!large || tooBig;
|
|
568
|
+
anywayB.hidden = !a.ok || !large;
|
|
569
|
+
if (toPanel) toPanel.hidden = inPanel || !!reading || tooBig;
|
|
570
|
+
stopB.hidden = !going && !reading;
|
|
571
|
+
banner.hidden = !going || P().bannerAcked();
|
|
572
|
+
note.textContent = reason != null ? reason : reading ? tr("chat.pvLoading") : going ? (state === "starting" ? tr("chat.pvStarting") : "") : inPanel ? tr("chat.pvInPanel") : a.ok ? "" : a.reason;
|
|
573
|
+
optB.textContent = P().optedIn() ? tr("chat.pvDisallow") : tr("chat.pvAllow");
|
|
574
|
+
neverB.textContent = P().never() ? tr("chat.pvNeverUndo") : tr("chat.pvNever");
|
|
575
|
+
};
|
|
576
|
+
/** Over 8 MB: the card asks, and "Preview anyway" runs it where `target` ("card" | "panel") says. */
|
|
577
|
+
const askLarge = (target, reason) => { large = target; largeNote = reason; refresh(reason); };
|
|
578
|
+
/**
|
|
579
|
+
* The HTML, then `then(code)`: at once for a fence or an attachment already read; otherwise read it first — a large
|
|
580
|
+
* one only once `anyway` (its listed size asks before anything is fetched).
|
|
581
|
+
*/
|
|
582
|
+
const withCode = async (then, target = "card", anyway = false) => {
|
|
583
|
+
if (code != null) { then(code); return; }
|
|
584
|
+
if (reading) return;
|
|
585
|
+
const mine = reading = ++reads;
|
|
586
|
+
refresh();
|
|
587
|
+
const r = await loadHtmlAttachment(att, undefined, { anyway });
|
|
588
|
+
if (reading !== mine || !ph.isConnected) return;
|
|
589
|
+
reading = 0;
|
|
590
|
+
if (!r.ok && r.reason === "large") { askLarge(target, trf("chat.pvLarge", sizeLabel(r.size))); return; }
|
|
591
|
+
if (!r.ok && r.reason === "over") { tooBig = true; refresh(); return; }
|
|
592
|
+
if (!r.ok) { refresh(attReason(r.reason)); return; }
|
|
593
|
+
code = r.code;
|
|
594
|
+
refresh();
|
|
595
|
+
then(code);
|
|
596
|
+
};
|
|
597
|
+
const ui = { state: (name, reason) => {
|
|
598
|
+
state = name === "starting" || name === "running" ? name : "idle";
|
|
599
|
+
if (name === "large") { askLarge("card", reason || ""); return; }
|
|
600
|
+
large = null; refresh(reason || null);
|
|
601
|
+
} };
|
|
602
|
+
// Nothing is read for Preview on a device that does not allow previews: the button is not even shown there.
|
|
603
|
+
// A Preview click is newer than any Open in panel still reading (#1704): it claims the panel's turn too — and, its
|
|
604
|
+
// own HTML read, starts only if no newer click claimed meanwhile and no preview was started since (the panel's own
|
|
605
|
+
// Preview): a late read never undoes a newer choice. A close is neither, so it does not drop this click.
|
|
606
|
+
const preview = (anyway) => {
|
|
607
|
+
if (!P().availability().ok || reading) return; // this card already reading: that click stays the one
|
|
608
|
+
const mine = panel ? (panel.claim(), panel.claims()) : 0, started = P().startCount();
|
|
609
|
+
withCode((c) => {
|
|
610
|
+
if ((panel && panel.claims() !== mine) || P().startCount() !== started) { refresh(); return; }
|
|
611
|
+
if (anyway) P().start(key, holder, c, ui, { anyway: true }); else P().start(key, holder, c, ui);
|
|
612
|
+
}, "card", anyway);
|
|
613
|
+
};
|
|
614
|
+
run.onclick = () => preview(false);
|
|
615
|
+
stopB.onclick = () => { if (reading) { reading = 0; refresh(); } else P().stop(key, "stopped", ""); };
|
|
616
|
+
// #1704: this click runs it in the panel, as Preview there would — a running preview moves (it stops here and
|
|
617
|
+
// starts there), an idle one starts there. Whether it can run is the panel's to say: a device that has not allowed
|
|
618
|
+
// previews is offered Allow first (and allowing runs it); where previews are off, nothing runs.
|
|
619
|
+
// An attachment's HTML is read first. The click claims the panel at once, so a newer click (even one still reading)
|
|
620
|
+
// wins: if the panel changed (another click, block, close) or any preview started meanwhile, this one is dropped —
|
|
621
|
+
// a late read never undoes a newer click.
|
|
622
|
+
const openInPanel = (anyway) => {
|
|
623
|
+
if (reading) return; // this card already reading: that click stays the one
|
|
624
|
+
if (P().running(key)) P().stop(key, "stopped", "");
|
|
625
|
+
const at = panel.claim(), started = P().startCount();
|
|
626
|
+
withCode((c) => {
|
|
627
|
+
if (panel.version() !== at || P().startCount() !== started) { refresh(); return; }
|
|
628
|
+
large = null; refresh();
|
|
629
|
+
panel.open({ key, instance: x.instance, msgKey: msgKey(x), ...(att ? { att } : { n: source.n }), code: c, sender: x.sender, ts: x.ts, ...(anyway ? { anyway: true } : {}) }, { run: true });
|
|
630
|
+
}, "panel", anyway);
|
|
631
|
+
};
|
|
632
|
+
if (toPanel) toPanel.onclick = () => openInPanel(false);
|
|
633
|
+
anywayB.onclick = () => { const where = large; large = null; if (where === "panel" && toPanel) openInPanel(true); else preview(true); };
|
|
634
|
+
if (dl) dl.onclick = () => opts.download(code);
|
|
635
|
+
ack.onclick = () => P().ackBanner(); // every card and the panel hear it (onChange)
|
|
636
|
+
optB.onclick = () => { menu.open = false; opts.setPreviewOptIn(!P().optedIn()); };
|
|
637
|
+
neverB.onclick = () => { menu.open = false; P().setNever(!P().never()); refreshCards(); };
|
|
638
|
+
cardRefresh.set(ph, refresh);
|
|
639
|
+
// #2053: a kept card is shown again later. A read still waiting when its chat left is dropped then: its answer —
|
|
640
|
+
// whenever it comes — starts nothing (no preview, no panel without a new click), and the card is not left Loading
|
|
641
|
+
// (shown again, it is refreshed: refreshCards).
|
|
642
|
+
cardDrop.set(ph, () => { reading = 0; });
|
|
643
|
+
refresh(stoppedKeys.includes(key) ? tr("chat.pvChanged") : null);
|
|
644
|
+
}
|
|
645
|
+
/** Re-read this device's preview choice on every card in this thread. */
|
|
646
|
+
function refreshCards() {
|
|
647
|
+
for (const node of list.querySelectorAll(".html-card[data-card], .html-card[data-att]")) { const f = cardRefresh.get(node); if (f) f(); }
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
// Near the bottom (48 px slack): where the READER counts as at the newest message — for the pill and for pinning on scroll.
|
|
651
|
+
const nearBottom = () => R().isNearBottom(scroller.scrollTop, scroller.clientHeight, scroller.scrollHeight);
|
|
652
|
+
// #1584: at the exact bottom — what a pinned view is kept at. The slack is the reader's, not the layout's: a dock that
|
|
653
|
+
// grew by less than 48 px (the working line, a command card, the composer shrinking after a send) used to leave the
|
|
654
|
+
// newest bubble clipped behind it, and the next growth past 48 px put the pill over it.
|
|
655
|
+
const atBottom = () => R().isNearBottom(scroller.scrollTop, scroller.clientHeight, scroller.scrollHeight, 1);
|
|
656
|
+
function reportJump() { opts.onJump({ show: !nearBottom(), unseen }); }
|
|
657
|
+
function toBottom() { snap(); scroller.scrollTop = scroller.scrollHeight; pinned = true; mark(); }
|
|
658
|
+
/**
|
|
659
|
+
* #2184: a thread's height is rarely a whole number of pixels, and a pinned view's offset is: so each time older messages
|
|
660
|
+
* were drawn above, the whole view moved by the change in that fraction (0.6–0.7 px — the faint jitter of a chat
|
|
661
|
+
* opened cold). A sub-pixel margin above the list keeps the thread's height whole, so the bottom never moves.
|
|
662
|
+
*/
|
|
663
|
+
// #2188: one read (the list's box excludes its own margin, so nothing is reset first) and a write only on a change —
|
|
664
|
+
// a reset-then-measure laid a 200-message thread out twice on every pinned draw (2.3 s at 4× CPU on a kept chat).
|
|
665
|
+
let snapped = null;
|
|
666
|
+
function snap() {
|
|
667
|
+
const h = list.getBoundingClientRect().height, f = h - Math.floor(h);
|
|
668
|
+
const want = f > 0.001 && f < 0.999 ? (1 - f) + "px" : "0px";
|
|
669
|
+
if (want !== snapped) { snapped = want; list.style.setProperty("--thread-snap", want); }
|
|
670
|
+
}
|
|
671
|
+
// Sizes change without a scroll event (layout after the render, the dock under the thread): while pinned, the view
|
|
672
|
+
// stays flush with the bottom; a reader who scrolled up is never moved.
|
|
673
|
+
const resized = () => { fitStatus(); if (pinned && !atBottom()) toBottom(); reportJump(); };
|
|
674
|
+
/**
|
|
675
|
+
* The user (hmsg-d6ac6dfb): a folded status line whose first two rows fit is plain two lines, no control; one with a
|
|
676
|
+
* row clipped at the width it is drawn at gets its "open" control (`overflows`). Measured after each draw and resize;
|
|
677
|
+
* a line with more than two rows always overflows (data-lines). Nothing laid out (no width): the guess it was drawn with.
|
|
678
|
+
*/
|
|
679
|
+
function fitStatus() {
|
|
680
|
+
for (const m of list.querySelectorAll(".msg.status-line.folded")) {
|
|
681
|
+
const btn = m.querySelector(".st-more");
|
|
682
|
+
if (!btn || btn.dataset.lines) continue;
|
|
683
|
+
let measured = false, over = false;
|
|
684
|
+
for (const row of [...m.querySelectorAll(".st-ln")].slice(0, STATUS_FOLD_LINES)) {
|
|
685
|
+
if (!row.clientWidth) continue;
|
|
686
|
+
measured = true;
|
|
687
|
+
if (row.scrollWidth > row.clientWidth + 1) over = true;
|
|
688
|
+
}
|
|
689
|
+
if (measured && m.classList.contains("overflows") !== over) m.classList.toggle("overflows", over);
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
let ro = typeof ResizeObserver === "function" ? new ResizeObserver(resized) : null;
|
|
693
|
+
if (ro) { ro.observe(list); ro.observe(scroller); }
|
|
694
|
+
// #2158: the reader's own hand on the thread — a wheel, a touch, the scrollbar (a pointer held), a scrolling key
|
|
695
|
+
// outside a text field — and the scroll that follows it until it has been quiet READER_QUIET_MS.
|
|
696
|
+
let inputAt = -Infinity, held = false;
|
|
697
|
+
const touched = () => { inputAt = performance.now(); };
|
|
698
|
+
const hold = (on) => () => { held = on; touched(); };
|
|
699
|
+
const onKey = (ev) => {
|
|
700
|
+
const el = ev.target;
|
|
701
|
+
if (SCROLL_KEYS.has(ev.key) && !(el && (el.isContentEditable || /^(INPUT|TEXTAREA|SELECT)$/.test(el.tagName || "")))) touched();
|
|
702
|
+
};
|
|
703
|
+
const onScroller = [["wheel", touched], ["touchstart", hold(true)], ["touchend", hold(false)], ["touchcancel", hold(false)], ["pointerdown", hold(true)]];
|
|
704
|
+
const onDocument = [["pointerup", hold(false)], ["pointercancel", hold(false)], ["keydown", onKey]];
|
|
705
|
+
/** Listen for the reader's input on `sc` (and its document: a pointer let go anywhere, a key), or stop. */
|
|
706
|
+
function watchReader(sc, on) {
|
|
707
|
+
const doc = sc.ownerDocument, how = on ? "addEventListener" : "removeEventListener";
|
|
708
|
+
for (const [type, fn] of onScroller) sc[how](type, fn, { passive: true });
|
|
709
|
+
if (doc) for (const [type, fn] of onDocument) doc[how](type, fn, true);
|
|
710
|
+
if (!on) held = false;
|
|
711
|
+
}
|
|
712
|
+
const readerActive = () => held || performance.now() - inputAt < READER_QUIET_MS;
|
|
713
|
+
watchReader(scroller, true);
|
|
714
|
+
|
|
715
|
+
/** Where drawing starts in `msgs`: at the oldest drawn message, or (none yet) the newest THREAD_TAIL. */
|
|
716
|
+
function startIndex() {
|
|
717
|
+
const i = firstKey === null ? -1 : msgs.findIndex((x) => msgKey(x) === firstKey);
|
|
718
|
+
// Whatever is drawn stays drawn: the list may have reordered (merged across fleet restarts) or lost its oldest to
|
|
719
|
+
// the cap — so the window starts at the earlier of the oldest drawn key and the first message still drawn.
|
|
720
|
+
const still = nodes.size ? msgs.findIndex((x) => nodes.has(msgKey(x))) : -1;
|
|
721
|
+
const at = [i, still].filter((n) => n >= 0);
|
|
722
|
+
return at.length ? Math.min(...at) : Math.max(0, msgs.length - THREAD_TAIL);
|
|
723
|
+
}
|
|
724
|
+
/** Draw the next chunk of older messages, keeping the reader where they were; then the next, until all are drawn. */
|
|
725
|
+
function grow() {
|
|
726
|
+
growTimer = growFrame = null;
|
|
727
|
+
if (disposed || parked) return; // the chat left: nothing more is drawn (a kept one fills in when shown again)
|
|
728
|
+
const at = startIndex();
|
|
729
|
+
if (at <= 0) return;
|
|
730
|
+
firstKey = msgKey(msgs[Math.max(0, at - THREAD_CHUNK)]);
|
|
731
|
+
const fromBottom = scroller.scrollHeight - scroller.scrollTop;
|
|
732
|
+
draw({ older: true }); // drawn above the reader: never "new below" (render alone counts those)
|
|
733
|
+
if (pinned) toBottom();
|
|
734
|
+
else { scroller.scrollTop = scroller.scrollHeight - fromBottom; mark(); }
|
|
735
|
+
scheduleGrow();
|
|
736
|
+
}
|
|
737
|
+
/** After the next frame has painted (the click's own frame stays light), draw a chunk more. */
|
|
738
|
+
function scheduleGrow() {
|
|
739
|
+
if (growTimer !== null || growFrame !== null || startIndex() <= 0) return;
|
|
740
|
+
const later = () => { growFrame = null; growTimer = setTimeout(grow, 0); };
|
|
741
|
+
if (typeof requestAnimationFrame === "function") growFrame = requestAnimationFrame(later); else later();
|
|
742
|
+
}
|
|
743
|
+
/** Everything drawn now (a remembered scroll position, a message to show that is not drawn yet). */
|
|
744
|
+
function growAll() {
|
|
745
|
+
if (startIndex() <= 0) return;
|
|
746
|
+
if (growFrame !== null && typeof cancelAnimationFrame === "function") cancelAnimationFrame(growFrame);
|
|
747
|
+
if (growTimer !== null) clearTimeout(growTimer);
|
|
748
|
+
growTimer = growFrame = null;
|
|
749
|
+
firstKey = msgs.length ? msgKey(msgs[0]) : null;
|
|
750
|
+
draw({ older: true });
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
/** Draw `next` (this instance's messages). `restore`: the scroll position to return to (null = the bottom). */
|
|
754
|
+
function render(next, renderOpts = {}) {
|
|
755
|
+
const stick = pinned || nearBottom();
|
|
756
|
+
msgs = next || [];
|
|
757
|
+
if (!msgs.length) {
|
|
758
|
+
for (const v of nodes.values()) stopIn(v.node);
|
|
759
|
+
nodes.clear();
|
|
760
|
+
firstKey = null;
|
|
761
|
+
armDay();
|
|
762
|
+
list.textContent = "";
|
|
763
|
+
opts.onEmpty(true);
|
|
764
|
+
return;
|
|
765
|
+
}
|
|
766
|
+
opts.onEmpty(false);
|
|
767
|
+
// A position to return to that is not the bottom needs the messages above it: all of them, at once (as before).
|
|
768
|
+
if ("restore" in renderOpts && renderOpts.restore != null) firstKey = msgKey(msgs[0]);
|
|
769
|
+
const added = draw({});
|
|
770
|
+
if (firstKey === null || startIndex() > 0) { firstKey = msgKey(msgs[startIndex()]); scheduleGrow(); }
|
|
771
|
+
if ("restore" in renderOpts) {
|
|
772
|
+
unseen = 0;
|
|
773
|
+
if (renderOpts.restore == null) toBottom();
|
|
774
|
+
else { scroller.scrollTop = renderOpts.restore; pinned = nearBottom(); mark(); }
|
|
775
|
+
} else if (stick) toBottom();
|
|
776
|
+
else unseen += added;
|
|
777
|
+
reportJump();
|
|
778
|
+
}
|
|
779
|
+
/** Put the messages from startIndex() on in the list (nodes reused when unchanged). Returns how many are new. */
|
|
780
|
+
function draw(drawOpts) {
|
|
781
|
+
const shown = msgs.slice(startIndex());
|
|
782
|
+
const keep = new Set(shown.map(msgKey));
|
|
783
|
+
for (const [k, v] of nodes) if (!keep.has(k)) { stopIn(v.node); v.node.remove(); nodes.delete(k); }
|
|
784
|
+
let prev = null, added = 0;
|
|
785
|
+
for (const x of shown) {
|
|
786
|
+
const key = msgKey(x);
|
|
787
|
+
let have = nodes.get(key);
|
|
788
|
+
// Filling in older messages: a node drawn from this very message object is current — not drawn again.
|
|
789
|
+
if (drawOpts.older && have && have.x === x) { prev = have.node; continue; }
|
|
790
|
+
const html = msgHtml(x);
|
|
791
|
+
if (!have || have.html !== html) {
|
|
792
|
+
const stopped = have ? stopIn(have.node) : [];
|
|
793
|
+
const node = msgNode(html, x, stopped);
|
|
794
|
+
if (have) have.node.replaceWith(node); else added++;
|
|
795
|
+
have = { html, node, x }; nodes.set(key, have);
|
|
796
|
+
} else { have.x = x; timeLabel(have.node); }
|
|
797
|
+
const want = prev ? prev.nextSibling : list.firstChild;
|
|
798
|
+
// A node already in the list is being moved: its preview stops first. A new node (still in its template) has none.
|
|
799
|
+
if (have.node !== want) { if (have.node.parentNode === list) stopIn(have.node); list.insertBefore(have.node, want); }
|
|
800
|
+
prev = have.node;
|
|
801
|
+
}
|
|
802
|
+
armDay();
|
|
803
|
+
armPolls();
|
|
804
|
+
rove();
|
|
805
|
+
progress();
|
|
806
|
+
fitStatus();
|
|
807
|
+
return added;
|
|
808
|
+
}
|
|
809
|
+
/**
|
|
810
|
+
* #2272 (review): one message in the thread is in the Tab order — the last one focused while it is still drawn, else
|
|
811
|
+
* the newest; ↑/↓ move between messages (chat-msg-actions.js). The rest are -1, so Tab passes the thread in one step.
|
|
812
|
+
*/
|
|
813
|
+
let roving = null;
|
|
814
|
+
function rove(key) {
|
|
815
|
+
if (key !== undefined) roving = key;
|
|
816
|
+
const at = (roving && nodes.has(roving) ? nodes.get(roving).node : null) || [...list.querySelectorAll(".msg[data-key]")].at(-1) || null;
|
|
817
|
+
for (const el of list.querySelectorAll('.msg[data-key][tabindex="0"]')) if (el !== at) el.setAttribute("tabindex", "-1");
|
|
818
|
+
if (at) at.setAttribute("tabindex", "0");
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
function onScroll() {
|
|
822
|
+
// #2158: the thread got shorter under a pinned view (text settling on a cold load, a card) and the browser clamped
|
|
823
|
+
// scrollTop to its new end — for the view as tall as it was then; the view got shorter too before WebKit sent that
|
|
824
|
+
// clamp's scroll event (it comes before the ResizeObserver). A view resting exactly at the bottom of the view as it
|
|
825
|
+
// was at the last mark, with no hand of the reader's on the thread, was put there by the browser: still pinned, back
|
|
826
|
+
// at the bottom. Geometry alone can't tell (a reader's move up by the dock's growth lands there too): any input of
|
|
827
|
+
// theirs, and the scroll after it, is theirs and unpins as before. The page's own scrolls (reveal) have no input but
|
|
828
|
+
// don't land there.
|
|
829
|
+
const reader = readerActive();
|
|
830
|
+
if (reader) touched(); // the reader's scroll goes on (smooth, momentum): still theirs while it does
|
|
831
|
+
else if (pinned && !atBottom() && Math.abs(scroller.scrollHeight - lastView - scroller.scrollTop) <= 1) toBottom();
|
|
832
|
+
const bottom = nearBottom(), top = scroller.scrollTop;
|
|
833
|
+
// #2087: pinned only at the exact bottom — a reader still wheeling through the last 48 px was pinned there, so
|
|
834
|
+
// the next resize (a picture, a card, the dock) pulled the view the rest of the way down under them. Near the bottom
|
|
835
|
+
// still counts for a new message (render follows it) and for the pill; nothing else moves a view the reader moves.
|
|
836
|
+
if (atBottom()) pinned = true;
|
|
837
|
+
else if (top < lastTop) pinned = false;
|
|
838
|
+
mark();
|
|
839
|
+
if (bottom) unseen = 0;
|
|
840
|
+
reportJump();
|
|
841
|
+
return bottom ? null : scroller.scrollTop;
|
|
842
|
+
}
|
|
843
|
+
function jumpLatest() { toBottom(); unseen = 0; reportJump(); }
|
|
844
|
+
/** Show one message (the panel's "from …" link, #1481): scrolled into view and briefly marked. */
|
|
845
|
+
function reveal(key) {
|
|
846
|
+
if (!nodes.has(key) && msgs.some((x) => msgKey(x) === key)) growAll(); // an older message not drawn yet
|
|
847
|
+
const have = nodes.get(key); if (!have) return false;
|
|
848
|
+
if (typeof have.node.scrollIntoView === "function") have.node.scrollIntoView({ block: "center" });
|
|
849
|
+
have.node.classList.add("flash");
|
|
850
|
+
setTimeout(() => { have.node.classList.remove("flash"); }, 1600);
|
|
851
|
+
return true;
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
async function copyFrom(button, text) {
|
|
855
|
+
const ok = await opts.copyText(text);
|
|
856
|
+
if (button.classList.contains("icon-only")) {
|
|
857
|
+
button.classList.add(ok ? "done" : "fail");
|
|
858
|
+
button.title = ok ? tr("chat.copied") : tr("chat.copyFailed"); button.setAttribute("aria-label", button.title);
|
|
859
|
+
setTimeout(() => { button.classList.remove("done", "fail"); button.title = tr("chat.copyMessage"); button.setAttribute("aria-label", button.title); }, 1200);
|
|
860
|
+
return;
|
|
861
|
+
}
|
|
862
|
+
const was = button.dataset.label || button.textContent; button.dataset.label = was;
|
|
863
|
+
button.textContent = ok ? tr("chat.copied") : tr("chat.copyFailed");
|
|
864
|
+
setTimeout(() => { button.textContent = was; }, 1200);
|
|
865
|
+
}
|
|
866
|
+
/**
|
|
867
|
+
* A chat image: a plain left click (or Enter on it) shows it in the page — this message's images, from the one
|
|
868
|
+
* clicked. With a modifier key the link does what the browser does (a new tab, a new window, a download); a middle
|
|
869
|
+
* click is not a click event at all. True when it was an image (whether or not it opened).
|
|
870
|
+
*/
|
|
871
|
+
function onImageClick(e) {
|
|
872
|
+
const img = e.target && e.target.closest ? e.target.closest("a.att-img") : null;
|
|
873
|
+
if (!img || !list.contains(img)) return false;
|
|
874
|
+
if (opts.openImage && !e.defaultPrevented && (e.button ?? 0) === 0 && !e.ctrlKey && !e.metaKey && !e.shiftKey && !e.altKey) {
|
|
875
|
+
const links = [...(img.closest(".atts") || img).querySelectorAll("a.att-img")];
|
|
876
|
+
if (opts.openImage(links.length ? links : [img], img)) e.preventDefault();
|
|
877
|
+
}
|
|
878
|
+
return true;
|
|
879
|
+
}
|
|
880
|
+
/** The thread's own buttons (Copy, Wrap, fold), delegated from the thread element. */
|
|
881
|
+
function onClick(e) {
|
|
882
|
+
if (onImageClick(e)) return;
|
|
883
|
+
const el = e.target && e.target.closest ? e.target.closest("[data-act]") : null;
|
|
884
|
+
if (!el || !list.contains(el)) {
|
|
885
|
+
// A folded status line opens by a click on its text too (not on a link in it); its button is the keyboard's way.
|
|
886
|
+
const text = e.target && e.target.closest ? e.target.closest(".msg.status-line.folded.overflows .st-text") : null;
|
|
887
|
+
const btn = text && !e.target.closest("a") ? text.parentNode.querySelector(".st-more") : null;
|
|
888
|
+
if (btn && list.contains(btn)) onClick({ target: btn });
|
|
889
|
+
return;
|
|
890
|
+
}
|
|
891
|
+
const act = el.dataset.act;
|
|
892
|
+
if (act === "copyMsg") { const x = msgs.find(y => msgKey(y) === el.dataset.arg); if (x) copyFrom(el, x.text); }
|
|
893
|
+
else if (act === "copyCode") { const pre = el.closest(".codeblock")?.querySelector("pre"); if (pre) copyFrom(el, pre.textContent); }
|
|
894
|
+
else if (act === "toggleFold") {
|
|
895
|
+
const box = el.closest(".codeblock, .msg.status-line"); if (!box) return;
|
|
896
|
+
const folded = box.classList.toggle("folded");
|
|
897
|
+
if (box.classList.contains("status-line")) {
|
|
898
|
+
// A status line's own label: short at the end of line 2 while folded, its full name for a screen reader.
|
|
899
|
+
el.setAttribute("aria-expanded", folded ? "false" : "true");
|
|
900
|
+
el.setAttribute("aria-label", !folded ? tr("chat.showLess") : el.dataset.lines ? trf("chat.showAll", el.dataset.lines) : tr("chat.showMore"));
|
|
901
|
+
el.textContent = !folded ? tr("chat.showLess") : el.dataset.lines ? trf("chat.showAllShort", el.dataset.lines) : tr("chat.showMoreShort");
|
|
902
|
+
return;
|
|
903
|
+
}
|
|
904
|
+
el.textContent = !folded ? tr("chat.showLess") : el.dataset.lines ? trf("chat.showAll", el.dataset.lines) : tr("chat.showMore");
|
|
905
|
+
} else if (act === "toggleWrap") opts.toggleWrap();
|
|
906
|
+
else if (act === "replyButton") {
|
|
907
|
+
const m = /^([0-9a-f]{32}):(\d{1,2})$/.exec(el.dataset.arg || "");
|
|
908
|
+
if (m && opts.clickReplyButton) opts.clickReplyButton(m[1], Number(m[2]));
|
|
909
|
+
} else if (act === "showButtons") { const x = msgs.find(y => y.buttons && y.buttons.id === el.dataset.arg); if (x) reveal(msgKey(x)); }
|
|
910
|
+
else if (act === "reply") { const x = msgs.find(y => msgKey(y) === el.dataset.arg); if (x) opts.startReply?.(x); }
|
|
911
|
+
else if (act === "jumpQuote") void jumpTo(el.dataset.arg);
|
|
912
|
+
else if (act === "retryOut") opts.retryOutgoing?.(el.dataset.arg); // a failed send, again
|
|
913
|
+
else if (act === "putBackOut") opts.putBackOutgoing?.(el.dataset.arg); // back in the composer, to change it
|
|
914
|
+
else if (act === "deleteOut") opts.deleteOutgoing?.(el.dataset.arg);
|
|
915
|
+
}
|
|
916
|
+
/**
|
|
917
|
+
* #2272: show the message a reply quotes, flashed — drawn now, or after the chat reads back the longest history the
|
|
918
|
+
* server keeps; otherwise it says it is no longer there.
|
|
919
|
+
*/
|
|
920
|
+
async function jumpTo(key) {
|
|
921
|
+
if (typeof key !== "string" || !key) return;
|
|
922
|
+
if (reveal(key)) return;
|
|
923
|
+
const found = opts.findQuoted ? await opts.findQuoted(key) : false;
|
|
924
|
+
if (!found || !reveal(key)) opts.notice?.(tr("chat.replyGone"));
|
|
925
|
+
}
|
|
926
|
+
/** #2272: a drawn message's row (for the message menu and gestures), or undefined. */
|
|
927
|
+
function messageOf(key) { return msgs.find(y => msgKey(y) === key); }
|
|
928
|
+
|
|
929
|
+
/** The thread goes (the chat left, or another instance opened): every preview in it stops first. */
|
|
930
|
+
/**
|
|
931
|
+
* #2053: kept for a later visit — the chat left as it always did (every preview stops: "leave"), but its drawn nodes
|
|
932
|
+
* stay. Nothing of it runs meanwhile: no timer (day labels, polls, filling in), no size observer; the
|
|
933
|
+
* caller drops its subscriptions. Shown again: attach, then render — only what changed is drawn.
|
|
934
|
+
*/
|
|
935
|
+
function park() {
|
|
936
|
+
parked = true;
|
|
937
|
+
watchReader(scroller, false);
|
|
938
|
+
if (P()) P().stopAll("leave");
|
|
939
|
+
for (const node of list.querySelectorAll(".html-card[data-att], .text-card[data-att]")) { const f = cardDrop.get(node); if (f) f(); }
|
|
940
|
+
if (ro) { ro.disconnect(); ro = null; }
|
|
941
|
+
if (growFrame !== null && typeof cancelAnimationFrame === "function") cancelAnimationFrame(growFrame);
|
|
942
|
+
if (growTimer !== null) clearTimeout(growTimer);
|
|
943
|
+
growTimer = growFrame = null;
|
|
944
|
+
if (offDay) { offDay(); offDay = null; }
|
|
945
|
+
if (pollTick) { clearInterval(pollTick); pollTick = null; }
|
|
946
|
+
}
|
|
947
|
+
/** In view in `sc` (the view's scroller): sizes watched there from now on; the caller restores the position. */
|
|
948
|
+
function attach(sc) {
|
|
949
|
+
if (!parked) watchReader(scroller, false); // moved from the scroller it was made with
|
|
950
|
+
parked = false;
|
|
951
|
+
scroller = sc;
|
|
952
|
+
watchReader(sc, true);
|
|
953
|
+
if (ro) ro.disconnect();
|
|
954
|
+
ro = typeof ResizeObserver === "function" ? new ResizeObserver(resized) : null;
|
|
955
|
+
if (ro) { ro.observe(list); ro.observe(sc); }
|
|
956
|
+
}
|
|
957
|
+
/** `own`: only this thread's previews stop (a thread let go while another chat is in view); else every one, as leaving. */
|
|
958
|
+
function dispose(own = false) {
|
|
959
|
+
disposed = true;
|
|
960
|
+
if (!parked) watchReader(scroller, false);
|
|
961
|
+
if (ro) ro.disconnect();
|
|
962
|
+
if (P()) { if (own) P().stopIn(list); else P().stopAll("leave"); }
|
|
963
|
+
nodes.clear();
|
|
964
|
+
armDay();
|
|
965
|
+
armPolls();
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
return { render, progress, onScroll, onClick, jumpLatest, reveal, jumpTo, messageOf, rove, refreshCards, park, attach, dispose, nodes, get unseen() { return unseen; } };
|
|
969
|
+
}
|