This commit is contained in:
120
apps/browser-ext/popup.js
Normal file
120
apps/browser-ext/popup.js
Normal file
@@ -0,0 +1,120 @@
|
||||
// Waggle Companion popup — talks to background.js via chrome.runtime.sendMessage,
|
||||
// background talks to the local Waggle sidecar at 127.0.0.1:3333. The popup
|
||||
// itself never makes network requests so we don't pay the CORS preflight tax
|
||||
// from an extension origin.
|
||||
|
||||
const $ = (id) => document.getElementById(id);
|
||||
|
||||
const dot = $('dot');
|
||||
const statusText = $('status-text');
|
||||
const workspaceNameEl = $('workspace-name');
|
||||
const btnSelection = $('save-selection');
|
||||
const btnPage = $('save-page');
|
||||
const btnOpen = $('open-waggle');
|
||||
const toast = $('toast');
|
||||
|
||||
let cachedSelection = '';
|
||||
let cachedPageMeta = null;
|
||||
let toastTimer = null;
|
||||
|
||||
function showToast(msg, kind = '', options = {}) {
|
||||
if (toastTimer) clearTimeout(toastTimer);
|
||||
toast.textContent = msg;
|
||||
toast.className = kind;
|
||||
if (!options.sticky && (kind === 'ok' || kind === 'err')) {
|
||||
toastTimer = setTimeout(() => {
|
||||
if (toast.textContent === msg) {
|
||||
toast.textContent = '';
|
||||
toast.className = '';
|
||||
}
|
||||
}, 3500);
|
||||
}
|
||||
}
|
||||
|
||||
function isSetupError(msg) {
|
||||
return /allowlisted|paired|pairing/i.test(msg);
|
||||
}
|
||||
|
||||
function formatMemoryDestination(reply) {
|
||||
const workspaceName = typeof reply?.activeWorkspaceName === 'string'
|
||||
? reply.activeWorkspaceName.trim()
|
||||
: '';
|
||||
const workspaceId = typeof reply?.activeWorkspaceId === 'string'
|
||||
? reply.activeWorkspaceId.trim()
|
||||
: typeof reply?.activeWorkspace === 'string'
|
||||
? reply.activeWorkspace.trim()
|
||||
: '';
|
||||
if (workspaceName) return workspaceName;
|
||||
if (workspaceId && workspaceId !== 'local-default' && workspaceId !== 'default-workspace') {
|
||||
return `Workspace id: ${workspaceId}`;
|
||||
}
|
||||
return 'Personal memory';
|
||||
}
|
||||
|
||||
async function refreshHealth() {
|
||||
try {
|
||||
const reply = await chrome.runtime.sendMessage({ type: 'health' });
|
||||
if (reply?.ok) {
|
||||
dot.className = 'dot connected';
|
||||
statusText.textContent = 'Connected';
|
||||
// textContent (not innerHTML) — workspace names are user-controlled
|
||||
// and could otherwise be XSS sinks in the extension context.
|
||||
workspaceNameEl.textContent = formatMemoryDestination(reply);
|
||||
} else {
|
||||
throw new Error(reply?.error || 'No response');
|
||||
}
|
||||
} catch (err) {
|
||||
dot.className = 'dot disconnected';
|
||||
statusText.textContent = 'Not connected';
|
||||
workspaceNameEl.textContent = 'Unavailable';
|
||||
const msg = err?.message || 'Start Waggle desktop on this machine, then re-open this popup.';
|
||||
showToast(msg, 'err', { sticky: true });
|
||||
}
|
||||
}
|
||||
|
||||
async function readActiveTab() {
|
||||
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
|
||||
if (!tab?.id) return null;
|
||||
try {
|
||||
const res = await chrome.tabs.sendMessage(tab.id, { type: 'extract' });
|
||||
cachedSelection = res?.selection ?? '';
|
||||
cachedPageMeta = res?.page ?? null;
|
||||
btnSelection.disabled = !cachedSelection;
|
||||
} catch {
|
||||
// Content script unavailable (e.g. on chrome:// pages) — disable buttons gracefully.
|
||||
btnSelection.disabled = true;
|
||||
btnPage.disabled = true;
|
||||
showToast('Waggle cannot read this browser page. Open a normal webpage, then try again.', 'err', { sticky: true });
|
||||
}
|
||||
}
|
||||
|
||||
async function save(kind) {
|
||||
const isSelection = kind === 'selection';
|
||||
const text = isSelection ? cachedSelection : (cachedPageMeta?.text || '');
|
||||
if (!text) { showToast('Nothing to save.', 'err'); return; }
|
||||
const url = cachedPageMeta?.url || '';
|
||||
const title = cachedPageMeta?.title || '';
|
||||
const prefix = isSelection ? 'Selection from' : 'Saved page';
|
||||
const content = `${prefix} ${title || url}\n\n${text}`.slice(0, 16000);
|
||||
showToast(`Saving ${isSelection ? 'selection' : 'page'}…`);
|
||||
const reply = await chrome.runtime.sendMessage({
|
||||
type: 'save-memory',
|
||||
content,
|
||||
source: 'import',
|
||||
importance: isSelection ? 'normal' : 'low',
|
||||
url, title,
|
||||
});
|
||||
if (reply?.saved) {
|
||||
showToast(reply.duplicate ? 'Already in memory.' : 'Saved to Waggle memory ✓', 'ok');
|
||||
} else {
|
||||
const msg = reply?.error || 'Save failed.';
|
||||
showToast(msg, 'err', { sticky: isSetupError(msg) });
|
||||
}
|
||||
}
|
||||
|
||||
btnSelection.addEventListener('click', () => save('selection'));
|
||||
btnPage.addEventListener('click', () => save('page'));
|
||||
btnOpen.addEventListener('click', () => chrome.tabs.create({ url: 'http://127.0.0.1:3333' }));
|
||||
|
||||
refreshHealth();
|
||||
readActiveTab();
|
||||
Reference in New Issue
Block a user