@oar-sh/oar 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/extensions/web-relay/README.md +190 -0
- package/.github/extensions/web-relay/extension.mjs +972 -0
- package/.github/extensions/web-relay/model-api/model-switching.mjs +338 -0
- package/.github/extensions/web-relay/polling/heartbeat.mjs +55 -0
- package/.github/extensions/web-relay/polling/polling-loop.mjs +1105 -0
- package/.github/extensions/web-relay/runtime/api-client.mjs +34 -0
- package/.github/extensions/web-relay/runtime/banner-state.mjs +126 -0
- package/.github/extensions/web-relay/runtime/config-loader.mjs +104 -0
- package/.github/extensions/web-relay/runtime/debug-log.mjs +27 -0
- package/.github/extensions/web-relay/runtime/send-and-wait-errors.mjs +170 -0
- package/.github/extensions/web-relay/runtime/session-binding.mjs +78 -0
- package/.github/extensions/web-relay/runtime/session-io.mjs +349 -0
- package/.github/extensions/web-relay/runtime/session-join-retry.mjs +44 -0
- package/.github/extensions/web-relay/runtime/session-registry.mjs +29 -0
- package/.github/extensions/web-relay/runtime/session-runtime-manager.mjs +156 -0
- package/.github/extensions/web-relay/runtime/session-sync-bridge.mjs +74 -0
- package/.github/extensions/web-relay/runtime/worker-websocket-link.mjs +325 -0
- package/.github/extensions/web-relay/runtime/workspace-root.mjs +55 -0
- package/.github/extensions/web-relay/server-lifecycle/managed-server.mjs +386 -0
- package/.github/extensions/web-relay/skills/prompt-context.mjs +160 -0
- package/.github/extensions/web-relay/skills/question-bridge.mjs +141 -0
- package/.github/extensions/web-relay/skills/question-routing-hooks.mjs +463 -0
- package/.github/extensions/web-relay/skills/reasoning-stream.mjs +285 -0
- package/.github/extensions/web-relay/skills/subagent-lifecycle.mjs +345 -0
- package/.github/extensions/web-relay/skills/tool-activity.mjs +243 -0
- package/.github/extensions/web-relay/utils/process-utils.mjs +25 -0
- package/.github/extensions/web-relay/utils/token-generation.mjs +58 -0
- package/CHANGELOG.md +25 -0
- package/LICENSE +21 -0
- package/README.md +717 -0
- package/bin/bat/oar.bat +46 -0
- package/bin/oar.js +665 -0
- package/package.json +68 -0
- package/server/README.md +1379 -0
- package/server/claude-worker/claude-attachments.mjs +134 -0
- package/server/claude-worker/claude-preview-tool.mjs +57 -0
- package/server/claude-worker/claude-sdk-adapter.mjs +420 -0
- package/server/claude-worker/claude-session-process.mjs +2391 -0
- package/server/claude-worker/claude-session-test-harness.mjs +236 -0
- package/server/claude-worker/claude-session-worker.mjs +186 -0
- package/server/claude-worker/claude-transcript-relocator.mjs +189 -0
- package/server/claude-worker/claude-turn-publisher.mjs +291 -0
- package/server/claude-worker/fixtures/workflow-run-record.json +1 -0
- package/server/claude-worker/sdk-message-normalizer.mjs +629 -0
- package/server/claude-worker/workflow-progress-digest.mjs +269 -0
- package/server/copilot-sdk-runtime.mjs +126 -0
- package/server/copilot-worker/copilot-attachments.mjs +97 -0
- package/server/copilot-worker/copilot-byok-provider.mjs +121 -0
- package/server/copilot-worker/copilot-continuation-signals.mjs +349 -0
- package/server/copilot-worker/copilot-plan-board.mjs +110 -0
- package/server/copilot-worker/copilot-prompt-context.mjs +86 -0
- package/server/copilot-worker/copilot-question-bridge.mjs +216 -0
- package/server/copilot-worker/copilot-sdk-adapter.mjs +494 -0
- package/server/copilot-worker/copilot-sdk-event-normalizer.mjs +800 -0
- package/server/copilot-worker/copilot-sdk-session-process.mjs +1729 -0
- package/server/copilot-worker/copilot-sdk-session-worker.mjs +167 -0
- package/server/copilot-worker/copilot-sdk-test-harness.mjs +329 -0
- package/server/copilot-worker/fixtures/abort-turn.json +588 -0
- package/server/copilot-worker/fixtures/ask-user-turn.json +1154 -0
- package/server/copilot-worker/fixtures/background-timer-continuation.json +445 -0
- package/server/copilot-worker/fixtures/background-timer-turn.json +497 -0
- package/server/copilot-worker/fixtures/happy-turn.json +566 -0
- package/server/copilot-worker/fixtures/quota-turn.json +647 -0
- package/server/copilot-worker/fixtures/reasoning-turn.json +625 -0
- package/server/copilot-worker/fixtures/resume-turn.json +700 -0
- package/server/copilot-worker/fixtures/subagent-turn.json +1378 -0
- package/server/copilot-worker/fixtures/tool-permission-turn.json +1293 -0
- package/server/cursor-worker/cursor-ask-user-tool.mjs +83 -0
- package/server/cursor-worker/cursor-attachments.mjs +88 -0
- package/server/cursor-worker/cursor-context-usage.mjs +85 -0
- package/server/cursor-worker/cursor-preview-tool.mjs +44 -0
- package/server/cursor-worker/cursor-sdk-adapter.mjs +554 -0
- package/server/cursor-worker/cursor-session-worker.mjs +145 -0
- package/server/cursor-worker/cursor-subagent-roster.mjs +81 -0
- package/server/cursor-worker/cursor-turn-runner.mjs +717 -0
- package/server/cursor-worker/sdk-message-normalizer.mjs +485 -0
- package/server/db-schema.mjs +932 -0
- package/server/features.mjs +106 -0
- package/server/grok-worker/acp-client.mjs +434 -0
- package/server/grok-worker/acp-host-services.mjs +277 -0
- package/server/grok-worker/grok-context-usage.mjs +77 -0
- package/server/grok-worker/grok-sdk-adapter.mjs +334 -0
- package/server/grok-worker/grok-session-worker.mjs +145 -0
- package/server/grok-worker/grok-turn-runner.mjs +523 -0
- package/server/grok-worker/sdk-message-normalizer.mjs +311 -0
- package/server/migrations/0001-add-structured-answer.mjs +80 -0
- package/server/migrations/0002-recent-workspace-roots-path-key.mjs +121 -0
- package/server/migrations/0003-push-subscriptions.mjs +87 -0
- package/server/public/app/action-confirmations.js +267 -0
- package/server/public/app/activity-replay-state.mjs +149 -0
- package/server/public/app/api-client.js +1082 -0
- package/server/public/app/ask-user-view.js +545 -0
- package/server/public/app/attachments-view.js +2112 -0
- package/server/public/app/auto-compact-window-options.mjs +59 -0
- package/server/public/app/background-tasks-view.mjs +494 -0
- package/server/public/app/bootstrap.js +4347 -0
- package/server/public/app/claude-auth-ui.js +394 -0
- package/server/public/app/claude-thinking-options.mjs +38 -0
- package/server/public/app/cli-install-ui.js +455 -0
- package/server/public/app/code-copy.mjs +85 -0
- package/server/public/app/composer-attachment-cache.mjs +151 -0
- package/server/public/app/composer-control-state.mjs +107 -0
- package/server/public/app/composer-paste.mjs +132 -0
- package/server/public/app/context-tier-options.mjs +68 -0
- package/server/public/app/context-usage-view.mjs +338 -0
- package/server/public/app/conversation-draft-timestamp-utils.mjs +11 -0
- package/server/public/app/conversation-preferences.mjs +91 -0
- package/server/public/app/conversation-provider-indicator.mjs +88 -0
- package/server/public/app/conversation-view.js +2898 -0
- package/server/public/app/cwd-menu-placement.mjs +85 -0
- package/server/public/app/cwd-picker.js +605 -0
- package/server/public/app/emoji-view.js +101 -0
- package/server/public/app/external-link-policy.mjs +80 -0
- package/server/public/app/file-preview-navigation.mjs +51 -0
- package/server/public/app/font-scaling.js +262 -0
- package/server/public/app/git-changes-view.js +339 -0
- package/server/public/app/git-diff-model.mjs +109 -0
- package/server/public/app/grok-auth-ui.js +387 -0
- package/server/public/app/image-downscale.mjs +174 -0
- package/server/public/app/in-flight-snapshot.mjs +40 -0
- package/server/public/app/infinite-loader.js +164 -0
- package/server/public/app/journal-view.js +1141 -0
- package/server/public/app/known-cwd-options.mjs +41 -0
- package/server/public/app/launch-cwd-path.mjs +23 -0
- package/server/public/app/live-message-dedupe.mjs +57 -0
- package/server/public/app/message-search-view.js +242 -0
- package/server/public/app/model-selector-options.mjs +31 -0
- package/server/public/app/model-vision-support.mjs +56 -0
- package/server/public/app/new-conversation-model-choice.mjs +67 -0
- package/server/public/app/plan-usage-view.mjs +307 -0
- package/server/public/app/preview-cards.mjs +273 -0
- package/server/public/app/preview-command.mjs +103 -0
- package/server/public/app/push-settings.js +370 -0
- package/server/public/app/pwa-install.js +456 -0
- package/server/public/app/question-schema-view.mjs +71 -0
- package/server/public/app/reasoning-effort-labels.mjs +42 -0
- package/server/public/app/relay-board-view.js +117 -0
- package/server/public/app/relay-dot-state.mjs +69 -0
- package/server/public/app/relay-error-ctas.mjs +52 -0
- package/server/public/app/relay-prompt-sanitizer.mjs +114 -0
- package/server/public/app/relay-thoughts.mjs +98 -0
- package/server/public/app/relay-watchdog-policy.mjs +29 -0
- package/server/public/app/repo-browser-preferences.mjs +79 -0
- package/server/public/app/repo-browser-tree-state.mjs +97 -0
- package/server/public/app/router.js +657 -0
- package/server/public/app/selection-guard.mjs +93 -0
- package/server/public/app/settings-modal.js +1484 -0
- package/server/public/app/settings-tabs.js +129 -0
- package/server/public/app/slash-autocomplete.mjs +188 -0
- package/server/public/app/slash-commands.mjs +183 -0
- package/server/public/app/socket-handlers.js +703 -0
- package/server/public/app/status-store.mjs +242 -0
- package/server/public/app/status-view.mjs +315 -0
- package/server/public/app/store.js +1454 -0
- package/server/public/app/stream-state.mjs +73 -0
- package/server/public/app/streaming-dom-patch.mjs +23 -0
- package/server/public/app/sync-outbox.mjs +208 -0
- package/server/public/app/tap-actions.js +62 -0
- package/server/public/app/thread-order.mjs +54 -0
- package/server/public/app/tmux-inspector-view.js +422 -0
- package/server/public/app/transcript-separators.mjs +282 -0
- package/server/public/app-icon-192.png +0 -0
- package/server/public/app-icon-512.png +0 -0
- package/server/public/app-icon.svg +4 -0
- package/server/public/favicon.ico +0 -0
- package/server/public/index.html +5743 -0
- package/server/public/manifest.webmanifest +32 -0
- package/server/public/sw.js +307 -0
- package/server/relay-exit-codes.mjs +4 -0
- package/server/relay-self-restart.mjs +170 -0
- package/server/relay-tools.md +27 -0
- package/server/relay.mjs +1026 -0
- package/server/repositories/image-conversation-repository.mjs +317 -0
- package/server/repositories/message-repository.mjs +434 -0
- package/server/repositories/question-repository.mjs +161 -0
- package/server/repositories/session-repository.mjs +320 -0
- package/server/repositories/share-visibility-statements.mjs +24 -0
- package/server/routes/ask-user-routes.mjs +292 -0
- package/server/routes/cache-routes.mjs +44 -0
- package/server/routes/git-routes.mjs +92 -0
- package/server/routes/messages-routes-test-harness.mjs +111 -0
- package/server/routes/messages-routes.mjs +7107 -0
- package/server/routes/preview-routes.mjs +108 -0
- package/server/routes/push-routes.mjs +59 -0
- package/server/routes/relay-board-routes.mjs +268 -0
- package/server/routes/sessions-routes.mjs +6150 -0
- package/server/server-runtime.mjs +7463 -0
- package/server/server.js +24 -0
- package/server/services/ask-user-routing-service.mjs +117 -0
- package/server/services/cache-rebuild-service.mjs +236 -0
- package/server/services/claude-auth-service.mjs +578 -0
- package/server/services/claude-context-usage.mjs +240 -0
- package/server/services/claude-session-root-service.mjs +228 -0
- package/server/services/cli-install-service.mjs +1152 -0
- package/server/services/cli-process-runner.mjs +181 -0
- package/server/services/cloudflared-tunnel-service.mjs +358 -0
- package/server/services/context-snapshot-service.mjs +499 -0
- package/server/services/context-usage-view.mjs +160 -0
- package/server/services/conversation-preferences-service.mjs +208 -0
- package/server/services/cursor-dashboard-usage.mjs +217 -0
- package/server/services/cursor-session-root-service.mjs +62 -0
- package/server/services/delete-archive-service.mjs +154 -0
- package/server/services/drives-path-helpers.mjs +66 -0
- package/server/services/fixtures/claude-auth-stub.sh +62 -0
- package/server/services/fixtures/cli-install-stub.sh +75 -0
- package/server/services/fixtures/grok-stub.sh +86 -0
- package/server/services/generated-image-cleanup-service.mjs +114 -0
- package/server/services/git-changes-service.mjs +195 -0
- package/server/services/github-billing-usage.mjs +133 -0
- package/server/services/grok-auth-service.mjs +593 -0
- package/server/services/grok-billing-usage.mjs +120 -0
- package/server/services/image-context-service.mjs +106 -0
- package/server/services/image-operation-service.mjs +135 -0
- package/server/services/keyed-mutex-service.mjs +82 -0
- package/server/services/mime-sniffer.mjs +118 -0
- package/server/services/oar-cli-helpers.mjs +109 -0
- package/server/services/oar-state-migration-service.mjs +243 -0
- package/server/services/plan-usage-claude.mjs +387 -0
- package/server/services/plan-usage-contract.mjs +271 -0
- package/server/services/plan-usage-copilot.mjs +421 -0
- package/server/services/plan-usage-cursor.mjs +566 -0
- package/server/services/plan-usage-grok.mjs +436 -0
- package/server/services/plan-usage-service.mjs +487 -0
- package/server/services/preview-health-probe.mjs +82 -0
- package/server/services/preview-proxy-server.mjs +498 -0
- package/server/services/preview-registry-service.mjs +457 -0
- package/server/services/preview-static-handler.mjs +237 -0
- package/server/services/provider-model-selection.mjs +91 -0
- package/server/services/provider-reasoning-effort.mjs +94 -0
- package/server/services/push-active-device-service.mjs +57 -0
- package/server/services/push-dispatch-service.mjs +459 -0
- package/server/services/rate-limit-service.mjs +63 -0
- package/server/services/relay-bridge-owner-service.mjs +84 -0
- package/server/services/relay-cli-launcher-service.mjs +209 -0
- package/server/services/relay-prompt-sanitizer.mjs +137 -0
- package/server/services/relay-queue-gate-service.mjs +38 -0
- package/server/services/relay-restart-orchestrator-service.mjs +935 -0
- package/server/services/relay-singleton-guard.mjs +124 -0
- package/server/services/safe-served-content.mjs +57 -0
- package/server/services/sdk-session-import-service.mjs +271 -0
- package/server/services/sdk-session-sync-service.mjs +207 -0
- package/server/services/session-history-refresh-service.mjs +497 -0
- package/server/services/session-transcript-service.mjs +419 -0
- package/server/services/session-worker-launch-service.mjs +786 -0
- package/server/services/session-worker-process-service.mjs +319 -0
- package/server/services/session-worker-registry-service.mjs +194 -0
- package/server/services/session-worker-stop-service.mjs +141 -0
- package/server/services/session-worker-supervisor-service.mjs +898 -0
- package/server/services/session-worker-websocket-service.mjs +483 -0
- package/server/services/ssh-tunnel-manager-service.mjs +423 -0
- package/server/services/status-event-service.mjs +158 -0
- package/server/services/tmux-inspector-access-policy.mjs +77 -0
- package/server/services/tmux-inspector-socket-service.mjs +191 -0
- package/server/services/tmux-inspector-stream-service.mjs +552 -0
- package/server/services/tunnel-worker-path-guard.mjs +107 -0
- package/server/services/upload-sweep.mjs +60 -0
- package/server/services/usage-snapshot-helpers.mjs +209 -0
- package/server/services/windows-autostart-service.mjs +115 -0
- package/server/services/workspace-root-defaults-service.mjs +34 -0
- package/server/services/workspace-root-path-policy.mjs +196 -0
- package/server/services/workspace-root-relaunch-service.mjs +132 -0
- package/server/services/workspace-symlink-guard.mjs +54 -0
- package/server/tools/capture-proxy.mjs +173 -0
- package/server/tty-console-bootstrap.mjs +75 -0
- package/server/uploads/README.md +36 -0
- package/server/uploads/image_input.md +540 -0
- package/server/windows-terminal-launcher.mjs +29 -0
- package/server/workspace-root.mjs +87 -0
- package/shared/ask-user-bridge.mjs +131 -0
- package/shared/auto-compact-window.mjs +116 -0
- package/shared/background-task-timeout.mjs +52 -0
- package/shared/claude-thinking.mjs +101 -0
- package/shared/context-window-fallbacks.mjs +148 -0
- package/shared/control-poller.mjs +115 -0
- package/shared/empty-turn-completion.mjs +20 -0
- package/shared/model-catalog-freshness.mjs +11 -0
- package/shared/model-descriptors.mjs +118 -0
- package/shared/model-id.mjs +147 -0
- package/shared/model-refresh.mjs +24 -0
- package/shared/openai-reasoning.mjs +13 -0
- package/shared/plan-lines.mjs +13 -0
- package/shared/preview-instructions.mjs +77 -0
- package/shared/preview-tool-core.mjs +191 -0
- package/shared/provider-routing.mjs +29 -0
- package/shared/question-schema.mjs +315 -0
- package/shared/question-timeout.mjs +18 -0
- package/shared/stream-emit-gating.mjs +28 -0
- package/shared/subagent-run-id.mjs +17 -0
- package/shared/thought-cap.mjs +13 -0
- package/shared/tool-activity.mjs +98 -0
- package/shared/turn-ceiling.mjs +53 -0
- package/shared/worker-bootstrap.mjs +44 -0
- package/shared/worker-crash-guard.mjs +60 -0
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { autoResize, isMobileComposerViewport } from './store.js';
|
|
2
|
+
|
|
3
|
+
const LAST_EMOJI_STORAGE_KEY = 'copilot_last_emoji';
|
|
4
|
+
const DEFAULT_EMOJI = '🙂';
|
|
5
|
+
const EMOJI_CHOICES = ['🙂', '😀', '😁', '😂', '😊', '😍', '😘', '😎', '🤔', '🙌', '👍', '👏', '🎉', '❤️', '🔥', '✅', '👀', '🤝', '🙏', '💡', '🚀', '✨', '🧠', '🛠️'];
|
|
6
|
+
|
|
7
|
+
let pickerOpen = false;
|
|
8
|
+
|
|
9
|
+
function getLastEmoji() {
|
|
10
|
+
const saved = String(localStorage.getItem(LAST_EMOJI_STORAGE_KEY) || '').trim();
|
|
11
|
+
return saved || DEFAULT_EMOJI;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function setLastEmoji(emoji) {
|
|
15
|
+
const value = String(emoji || '').trim();
|
|
16
|
+
if (!value) return;
|
|
17
|
+
localStorage.setItem(LAST_EMOJI_STORAGE_KEY, value);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function updateEmojiButton() {
|
|
21
|
+
const button = document.getElementById('emoji-btn');
|
|
22
|
+
if (!button) return;
|
|
23
|
+
button.textContent = getLastEmoji();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function closeEmojiPicker() {
|
|
27
|
+
const popup = document.getElementById('emoji-popup');
|
|
28
|
+
if (!popup) return;
|
|
29
|
+
popup.classList.remove('visible');
|
|
30
|
+
popup.setAttribute('aria-hidden', 'true');
|
|
31
|
+
pickerOpen = false;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function insertEmojiAtCaret(emoji) {
|
|
35
|
+
const input = document.getElementById('msg-input');
|
|
36
|
+
if (!input) return;
|
|
37
|
+
const value = String(input.value || '');
|
|
38
|
+
const start = Number.isFinite(input.selectionStart) ? input.selectionStart : value.length;
|
|
39
|
+
const end = Number.isFinite(input.selectionEnd) ? input.selectionEnd : value.length;
|
|
40
|
+
input.value = `${value.slice(0, start)}${emoji}${value.slice(end)}`;
|
|
41
|
+
const nextCaret = start + emoji.length;
|
|
42
|
+
input.selectionStart = nextCaret;
|
|
43
|
+
input.selectionEnd = nextCaret;
|
|
44
|
+
autoResize(input);
|
|
45
|
+
input.focus();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function handleEmojiChoice(emoji) {
|
|
49
|
+
const value = String(emoji || '').trim();
|
|
50
|
+
if (!value) return;
|
|
51
|
+
setLastEmoji(value);
|
|
52
|
+
updateEmojiButton();
|
|
53
|
+
insertEmojiAtCaret(value);
|
|
54
|
+
closeEmojiPicker();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function ensureEmojiChoices() {
|
|
58
|
+
const popup = document.getElementById('emoji-popup');
|
|
59
|
+
if (!popup || popup.dataset.bound === '1') return;
|
|
60
|
+
popup.dataset.bound = '1';
|
|
61
|
+
popup.innerHTML = EMOJI_CHOICES
|
|
62
|
+
.map((emoji) => `<button class="emoji-choice" type="button" data-emoji="${emoji}" aria-label="Insert ${emoji}">${emoji}</button>`)
|
|
63
|
+
.join('');
|
|
64
|
+
popup.addEventListener('click', (event) => {
|
|
65
|
+
const target = event.target?.closest?.('.emoji-choice');
|
|
66
|
+
if (!target) return;
|
|
67
|
+
handleEmojiChoice(target.getAttribute('data-emoji'));
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function toggleEmojiPicker() {
|
|
72
|
+
if (isMobileComposerViewport()) return;
|
|
73
|
+
const popup = document.getElementById('emoji-popup');
|
|
74
|
+
if (!popup) return;
|
|
75
|
+
if (pickerOpen) {
|
|
76
|
+
closeEmojiPicker();
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
popup.classList.add('visible');
|
|
80
|
+
popup.setAttribute('aria-hidden', 'false');
|
|
81
|
+
pickerOpen = true;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function initEmojiPicker() {
|
|
85
|
+
ensureEmojiChoices();
|
|
86
|
+
updateEmojiButton();
|
|
87
|
+
if (document.body.dataset.emojiPickerBound === '1') return;
|
|
88
|
+
document.body.dataset.emojiPickerBound = '1';
|
|
89
|
+
document.addEventListener('keydown', (event) => {
|
|
90
|
+
if (event.key === 'Escape') closeEmojiPicker();
|
|
91
|
+
});
|
|
92
|
+
document.addEventListener('pointerdown', (event) => {
|
|
93
|
+
if (!pickerOpen) return;
|
|
94
|
+
const popup = document.getElementById('emoji-popup');
|
|
95
|
+
const button = document.getElementById('emoji-btn');
|
|
96
|
+
const target = event.target;
|
|
97
|
+
if (popup?.contains(target) || button?.contains(target)) return;
|
|
98
|
+
closeEmojiPicker();
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
const EXTERNAL_LINK_PROTOCOLS = new Set(['http:', 'https:']);
|
|
2
|
+
|
|
3
|
+
export function isExternalNavigationHref(rawHref, baseHref) {
|
|
4
|
+
const href = String(rawHref || '').trim();
|
|
5
|
+
if (!href || href.startsWith('#')) return false;
|
|
6
|
+
try {
|
|
7
|
+
const url = new URL(href, baseHref);
|
|
8
|
+
return EXTERNAL_LINK_PROTOCOLS.has(url.protocol);
|
|
9
|
+
} catch {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function isInAppLink(anchor) {
|
|
15
|
+
return anchor.matches('a.workspace-file-link[data-workspace-path]')
|
|
16
|
+
|| Boolean(anchor.closest('.file-preview-markdown'));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function openExternalNavigation(url, onFallback = null) {
|
|
20
|
+
const opened = window.open(url, '_blank');
|
|
21
|
+
if (opened) opened.opener = null;
|
|
22
|
+
if (!opened && typeof onFallback === 'function') onFallback(url);
|
|
23
|
+
return !!opened;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function secureExternalLink(anchor, baseHref, onFallback) {
|
|
27
|
+
if (
|
|
28
|
+
!(anchor instanceof HTMLAnchorElement)
|
|
29
|
+
|| anchor.hasAttribute('download')
|
|
30
|
+
|| isInAppLink(anchor)
|
|
31
|
+
|| !isExternalNavigationHref(anchor.getAttribute('href'), baseHref)
|
|
32
|
+
) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (anchor.target !== '_blank') anchor.target = '_blank';
|
|
37
|
+
const relTokens = new Set(String(anchor.rel || '').split(/\s+/).filter(Boolean));
|
|
38
|
+
relTokens.add('noopener');
|
|
39
|
+
relTokens.add('noreferrer');
|
|
40
|
+
anchor.rel = Array.from(relTokens).join(' ');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function installExternalLinkPolicy({ documentRef = document, onFallback = null } = {}) {
|
|
44
|
+
if (!documentRef || documentRef.documentElement?.dataset.externalLinkPolicy === 'installed') return;
|
|
45
|
+
documentRef.documentElement.dataset.externalLinkPolicy = 'installed';
|
|
46
|
+
|
|
47
|
+
const secureLinksIn = (root) => {
|
|
48
|
+
if (root instanceof HTMLAnchorElement) {
|
|
49
|
+
secureExternalLink(root, documentRef.baseURI, onFallback);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
if (!(root instanceof Element)) return;
|
|
53
|
+
root.querySelectorAll('a[href]').forEach((anchor) => secureExternalLink(anchor, documentRef.baseURI, onFallback));
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
secureLinksIn(documentRef.documentElement);
|
|
57
|
+
documentRef.addEventListener('click', (event) => {
|
|
58
|
+
const target = event.target instanceof Element ? event.target : event.target?.parentElement;
|
|
59
|
+
const anchor = target?.closest?.('a[href]');
|
|
60
|
+
if (!anchor || isInAppLink(anchor)) return;
|
|
61
|
+
secureExternalLink(anchor, documentRef.baseURI, onFallback);
|
|
62
|
+
if (!isExternalNavigationHref(anchor.getAttribute('href'), documentRef.baseURI)) return;
|
|
63
|
+
if (event.defaultPrevented || event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;
|
|
64
|
+
event.preventDefault();
|
|
65
|
+
openExternalNavigation(anchor.href, onFallback);
|
|
66
|
+
}, true);
|
|
67
|
+
|
|
68
|
+
const observer = new MutationObserver((records) => {
|
|
69
|
+
for (const record of records) {
|
|
70
|
+
if (record.type === 'attributes') secureLinksIn(record.target);
|
|
71
|
+
for (const node of record.addedNodes) secureLinksIn(node);
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
observer.observe(documentRef.documentElement, {
|
|
75
|
+
subtree: true,
|
|
76
|
+
childList: true,
|
|
77
|
+
attributes: true,
|
|
78
|
+
attributeFilter: ['class', 'download', 'href', 'target'],
|
|
79
|
+
});
|
|
80
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
const EXTERNAL_SCHEME = /^[a-z][a-z\d+.-]*:/i;
|
|
2
|
+
|
|
3
|
+
function decodeFragment(value) {
|
|
4
|
+
try {
|
|
5
|
+
return decodeURIComponent(value);
|
|
6
|
+
} catch {
|
|
7
|
+
return value;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function resolveRelativePath(currentPath, hrefPath) {
|
|
12
|
+
const baseParts = String(currentPath || '').replace(/\\/g, '/').split('/').filter(Boolean);
|
|
13
|
+
baseParts.pop();
|
|
14
|
+
const targetParts = String(hrefPath || '').replace(/\\/g, '/').split('/');
|
|
15
|
+
for (const part of targetParts) {
|
|
16
|
+
if (!part || part === '.') continue;
|
|
17
|
+
if (part === '..') {
|
|
18
|
+
if (!baseParts.length) return '';
|
|
19
|
+
baseParts.pop();
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
baseParts.push(part);
|
|
23
|
+
}
|
|
24
|
+
return baseParts.join('/');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function resolveFilePreviewLink(rawHref, currentPath) {
|
|
28
|
+
const href = String(rawHref || '').trim();
|
|
29
|
+
if (!href) return { kind: 'none' };
|
|
30
|
+
if (href.startsWith('#')) return { kind: 'fragment', fragment: decodeFragment(href.slice(1)) };
|
|
31
|
+
if (href.startsWith('//') || EXTERNAL_SCHEME.test(href)) return { kind: 'external', href };
|
|
32
|
+
|
|
33
|
+
const hashIndex = href.indexOf('#');
|
|
34
|
+
const pathPart = hashIndex >= 0 ? href.slice(0, hashIndex) : href;
|
|
35
|
+
const fragment = hashIndex >= 0 ? decodeFragment(href.slice(hashIndex + 1)) : '';
|
|
36
|
+
if (pathPart.startsWith('/')) return { kind: 'none' };
|
|
37
|
+
|
|
38
|
+
const path = resolveRelativePath(currentPath, pathPart);
|
|
39
|
+
return path ? { kind: 'file', path, fragment } : { kind: 'none' };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function markdownHeadingId(text) {
|
|
43
|
+
return String(text || '')
|
|
44
|
+
.normalize('NFKD')
|
|
45
|
+
.replace(/[\u0300-\u036f]/g, '')
|
|
46
|
+
.toLowerCase()
|
|
47
|
+
.trim()
|
|
48
|
+
.replace(/[^\w\s-]/g, '')
|
|
49
|
+
.replace(/[\s-]+/g, '-')
|
|
50
|
+
.replace(/^-+|-+$/g, '');
|
|
51
|
+
}
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
const FONT_SCALE_STORAGE_KEY = 'copilot_font_scale';
|
|
2
|
+
const FONT_SCALE_MIN = 0.5;
|
|
3
|
+
const FONT_SCALE_MAX = 1.5;
|
|
4
|
+
const FONT_SCALE_DEFAULT = 1;
|
|
5
|
+
const FONT_SCALE_WHEEL_STEP_MIN = 0.01;
|
|
6
|
+
const FONT_SCALE_WHEEL_STEP_MAX = 0.2;
|
|
7
|
+
const FONT_SCALE_PILL_VISIBLE_MS = 900;
|
|
8
|
+
|
|
9
|
+
let fontScaleValue = FONT_SCALE_DEFAULT;
|
|
10
|
+
let fontScaleIndicatorTimer = null;
|
|
11
|
+
let fontScalePinchState = {
|
|
12
|
+
active: false,
|
|
13
|
+
startDistance: 0,
|
|
14
|
+
startScale: FONT_SCALE_DEFAULT,
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
function readLocalStorage(key) {
|
|
18
|
+
try {
|
|
19
|
+
return localStorage.getItem(key);
|
|
20
|
+
} catch {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function writeLocalStorage(key, value) {
|
|
26
|
+
try {
|
|
27
|
+
localStorage.setItem(key, value);
|
|
28
|
+
return true;
|
|
29
|
+
} catch {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function clampFontScale(value) {
|
|
35
|
+
if (value == null) return FONT_SCALE_DEFAULT;
|
|
36
|
+
const text = typeof value === 'string' ? value.trim() : value;
|
|
37
|
+
if (text === '') return FONT_SCALE_DEFAULT;
|
|
38
|
+
const numeric = Number(text);
|
|
39
|
+
if (!Number.isFinite(numeric)) return FONT_SCALE_DEFAULT;
|
|
40
|
+
return Math.min(FONT_SCALE_MAX, Math.max(FONT_SCALE_MIN, numeric));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function readStoredFontScale() {
|
|
44
|
+
return clampFontScale(readLocalStorage(FONT_SCALE_STORAGE_KEY));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function normalizeFontScaleSelectValue(raw = '') {
|
|
48
|
+
const text = String(raw || '').trim();
|
|
49
|
+
if (!text) return null;
|
|
50
|
+
const numeric = Number(text);
|
|
51
|
+
if (!Number.isFinite(numeric)) return null;
|
|
52
|
+
if (numeric > 3) return clampFontScale(numeric / 100);
|
|
53
|
+
return clampFontScale(numeric);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function getMessageViewportAnchor() {
|
|
57
|
+
const container = document.getElementById('messages');
|
|
58
|
+
if (!container) return null;
|
|
59
|
+
const containerRect = container.getBoundingClientRect();
|
|
60
|
+
const messages = Array.from(container.querySelectorAll('.msg[data-message-id]'));
|
|
61
|
+
for (const message of messages) {
|
|
62
|
+
const rect = message.getBoundingClientRect();
|
|
63
|
+
if (rect.bottom <= containerRect.top || rect.top >= containerRect.bottom) continue;
|
|
64
|
+
const messageId = String(message.dataset.messageId || '').trim();
|
|
65
|
+
if (!messageId) continue;
|
|
66
|
+
return {
|
|
67
|
+
messageId,
|
|
68
|
+
offsetTop: rect.top - containerRect.top,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function restoreMessageViewportAnchor(anchor) {
|
|
75
|
+
if (!anchor?.messageId) return;
|
|
76
|
+
const container = document.getElementById('messages');
|
|
77
|
+
if (!container) return;
|
|
78
|
+
const messageId = String(anchor.messageId || '').trim();
|
|
79
|
+
if (!messageId) return;
|
|
80
|
+
const message = Array.from(container.querySelectorAll('.msg[data-message-id]'))
|
|
81
|
+
.find((node) => String(node?.dataset?.messageId || '').trim() === messageId);
|
|
82
|
+
if (!message) return;
|
|
83
|
+
const containerRect = container.getBoundingClientRect();
|
|
84
|
+
const rect = message.getBoundingClientRect();
|
|
85
|
+
const delta = rect.top - containerRect.top - Number(anchor.offsetTop || 0);
|
|
86
|
+
if (Math.abs(delta) > 0.5) {
|
|
87
|
+
container.scrollTop += delta;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function syncFontScaleSelect() {
|
|
92
|
+
const select = document.getElementById('font-scale-select');
|
|
93
|
+
if (!select) return;
|
|
94
|
+
const currentPercent = Math.round(clampFontScale(fontScaleValue) * 100);
|
|
95
|
+
const candidate = String(currentPercent);
|
|
96
|
+
if (Array.from(select.options).some((option) => option.value === candidate)) {
|
|
97
|
+
select.value = candidate;
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
const dynamicValue = String(currentPercent);
|
|
101
|
+
const dynamicLabel = `${currentPercent}%`;
|
|
102
|
+
let dynamicOption = select.querySelector('option[data-dynamic-font-scale="1"]');
|
|
103
|
+
if (!dynamicOption) {
|
|
104
|
+
dynamicOption = document.createElement('option');
|
|
105
|
+
dynamicOption.setAttribute('data-dynamic-font-scale', '1');
|
|
106
|
+
select.appendChild(dynamicOption);
|
|
107
|
+
}
|
|
108
|
+
dynamicOption.value = dynamicValue;
|
|
109
|
+
dynamicOption.textContent = dynamicLabel;
|
|
110
|
+
select.value = dynamicValue;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function setFontScale(nextScale, { persist = true, preserveMessageAnchor = true } = {}) {
|
|
114
|
+
const normalized = clampFontScale(nextScale);
|
|
115
|
+
if (Math.abs(normalized - fontScaleValue) <= 0.0001) {
|
|
116
|
+
if (persist) writeLocalStorage(FONT_SCALE_STORAGE_KEY, String(normalized));
|
|
117
|
+
syncFontScaleSelect();
|
|
118
|
+
return normalized;
|
|
119
|
+
}
|
|
120
|
+
const anchor = preserveMessageAnchor ? getMessageViewportAnchor() : null;
|
|
121
|
+
fontScaleValue = normalized;
|
|
122
|
+
document.documentElement.style.setProperty('--font-scale', normalized.toFixed(4));
|
|
123
|
+
document.documentElement.style.setProperty('--font-scale-percent', `${Math.round(normalized * 100)}%`);
|
|
124
|
+
if (persist) writeLocalStorage(FONT_SCALE_STORAGE_KEY, String(normalized));
|
|
125
|
+
syncFontScaleSelect();
|
|
126
|
+
if (anchor) {
|
|
127
|
+
requestAnimationFrame(() => {
|
|
128
|
+
restoreMessageViewportAnchor(anchor);
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
return normalized;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function showFontScaleIndicator(scaleValue) {
|
|
135
|
+
const indicator = document.getElementById('font-scale-indicator');
|
|
136
|
+
if (!indicator) return;
|
|
137
|
+
const normalized = clampFontScale(scaleValue);
|
|
138
|
+
indicator.textContent = `${Math.round(normalized * 100)}%`;
|
|
139
|
+
indicator.hidden = false;
|
|
140
|
+
indicator.classList.add('visible');
|
|
141
|
+
if (fontScaleIndicatorTimer) clearTimeout(fontScaleIndicatorTimer);
|
|
142
|
+
fontScaleIndicatorTimer = setTimeout(() => {
|
|
143
|
+
indicator.classList.remove('visible');
|
|
144
|
+
window.setTimeout(() => {
|
|
145
|
+
if (!indicator.classList.contains('visible')) indicator.hidden = true;
|
|
146
|
+
}, 180);
|
|
147
|
+
fontScaleIndicatorTimer = null;
|
|
148
|
+
}, FONT_SCALE_PILL_VISIBLE_MS);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function isImageZoomGestureTarget(target) {
|
|
152
|
+
if (!(target instanceof Element)) return false;
|
|
153
|
+
return !!target.closest('#file-preview-body.image-zoom-mode');
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function pinchDistance(touchA, touchB) {
|
|
157
|
+
return Math.hypot(touchA.clientX - touchB.clientX, touchA.clientY - touchB.clientY);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function normalizeWheelDeltaPixels(event) {
|
|
161
|
+
const deltaY = Number(event?.deltaY);
|
|
162
|
+
if (!Number.isFinite(deltaY) || deltaY === 0) return 0;
|
|
163
|
+
const deltaMode = Number(event?.deltaMode || 0);
|
|
164
|
+
if (deltaMode === 1) return deltaY * 16; // lines -> approx pixels
|
|
165
|
+
if (deltaMode === 2) return deltaY * (window.innerHeight || 800); // pages -> pixels
|
|
166
|
+
return deltaY; // already pixels
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function onGlobalFontScaleWheel(event) {
|
|
170
|
+
if (!(event.ctrlKey || event.metaKey)) return;
|
|
171
|
+
if (isImageZoomGestureTarget(event.target)) return;
|
|
172
|
+
event.preventDefault();
|
|
173
|
+
const deltaPixels = normalizeWheelDeltaPixels(event);
|
|
174
|
+
if (!Number.isFinite(deltaPixels) || deltaPixels === 0) return;
|
|
175
|
+
const direction = deltaPixels < 0 ? 1 : -1;
|
|
176
|
+
// Slow wheel gestures move by 1%, while faster spins accelerate.
|
|
177
|
+
const magnitude = Math.min(
|
|
178
|
+
FONT_SCALE_WHEEL_STEP_MAX,
|
|
179
|
+
Math.max(FONT_SCALE_WHEEL_STEP_MIN, Math.abs(deltaPixels) / 10_000),
|
|
180
|
+
);
|
|
181
|
+
const nextScale = setFontScale(fontScaleValue + (direction * magnitude), { persist: true, preserveMessageAnchor: true });
|
|
182
|
+
showFontScaleIndicator(nextScale);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function onGlobalFontScaleTouchStart(event) {
|
|
186
|
+
if (event.touches.length !== 2) return;
|
|
187
|
+
if (isImageZoomGestureTarget(event.target)) {
|
|
188
|
+
fontScalePinchState.active = false;
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
const t0 = event.touches[0];
|
|
192
|
+
const t1 = event.touches[1];
|
|
193
|
+
fontScalePinchState = {
|
|
194
|
+
active: true,
|
|
195
|
+
startDistance: pinchDistance(t0, t1),
|
|
196
|
+
startScale: fontScaleValue,
|
|
197
|
+
};
|
|
198
|
+
event.preventDefault();
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function onGlobalFontScaleTouchMove(event) {
|
|
202
|
+
if (!fontScalePinchState.active) return;
|
|
203
|
+
if (event.touches.length !== 2) {
|
|
204
|
+
fontScalePinchState.active = false;
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
if (isImageZoomGestureTarget(event.target)) {
|
|
208
|
+
fontScalePinchState.active = false;
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
event.preventDefault();
|
|
212
|
+
const t0 = event.touches[0];
|
|
213
|
+
const t1 = event.touches[1];
|
|
214
|
+
const distance = pinchDistance(t0, t1);
|
|
215
|
+
if (!fontScalePinchState.startDistance || !Number.isFinite(distance) || distance <= 0) return;
|
|
216
|
+
const ratio = distance / fontScalePinchState.startDistance;
|
|
217
|
+
const nextScale = fontScalePinchState.startScale * ratio;
|
|
218
|
+
const appliedScale = setFontScale(nextScale, { persist: true, preserveMessageAnchor: true });
|
|
219
|
+
showFontScaleIndicator(appliedScale);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function onGlobalFontScaleTouchEnd(event) {
|
|
223
|
+
if (event.touches.length < 2) {
|
|
224
|
+
fontScalePinchState.active = false;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function populateFontScaleSelect() {
|
|
229
|
+
const select = document.getElementById('font-scale-select');
|
|
230
|
+
if (!select || select.dataset.populated === '1') return;
|
|
231
|
+
select.dataset.populated = '1';
|
|
232
|
+
for (let value = 50; value <= 150; value += 10) {
|
|
233
|
+
const option = document.createElement('option');
|
|
234
|
+
option.value = String(value);
|
|
235
|
+
option.textContent = `${value}%`;
|
|
236
|
+
select.appendChild(option);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
export function initFontScaling() {
|
|
241
|
+
populateFontScaleSelect();
|
|
242
|
+
const inlineScale = Number(document.documentElement.style.getPropertyValue('--font-scale'));
|
|
243
|
+
const initialScale = Number.isFinite(inlineScale) ? clampFontScale(inlineScale) : readStoredFontScale();
|
|
244
|
+
setFontScale(initialScale, { persist: false, preserveMessageAnchor: false });
|
|
245
|
+
if (!window.__fontScaleGestureHandlersBound) {
|
|
246
|
+
window.__fontScaleGestureHandlersBound = true;
|
|
247
|
+
window.addEventListener('wheel', onGlobalFontScaleWheel, { passive: false, capture: true });
|
|
248
|
+
window.addEventListener('touchstart', onGlobalFontScaleTouchStart, { passive: false, capture: true });
|
|
249
|
+
window.addEventListener('touchmove', onGlobalFontScaleTouchMove, { passive: false, capture: true });
|
|
250
|
+
window.addEventListener('touchend', onGlobalFontScaleTouchEnd, { passive: true, capture: true });
|
|
251
|
+
window.addEventListener('touchcancel', onGlobalFontScaleTouchEnd, { passive: true, capture: true });
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
export function updateFontScaleFromSelect(rawValue) {
|
|
256
|
+
const next = normalizeFontScaleSelectValue(rawValue);
|
|
257
|
+
if (next == null) {
|
|
258
|
+
syncFontScaleSelect();
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
setFontScale(next, { persist: true, preserveMessageAnchor: true });
|
|
262
|
+
}
|