diff --git a/apps/extension/package.json b/apps/extension/package.json index 387c09f..5c74033 100644 --- a/apps/extension/package.json +++ b/apps/extension/package.json @@ -1,6 +1,6 @@ { "name": "@dockmark/extension", - "version": "1.5.1", + "version": "1.5.2", "private": true, "type": "module", "scripts": { diff --git a/apps/extension/public/newtab-ux.js b/apps/extension/public/newtab-ux.js index 9bc3244..03cf05e 100644 --- a/apps/extension/public/newtab-ux.js +++ b/apps/extension/public/newtab-ux.js @@ -29,14 +29,42 @@ }; } + function queryTerms(value) { + return String(value || "").trim().toLocaleLowerCase().split(/\s+/).filter(Boolean); + } + + function valuesMatchQuery(query, values) { + const terms = queryTerms(query); + if (!terms.length) return true; + const haystack = values.map((value) => String(value || "")).join("\n").toLocaleLowerCase(); + return terms.every((term) => haystack.includes(term)); + } + + function matchingSessionItem(query, items) { + return items.find((item) => valuesMatchQuery(query, [item?.title, item?.url])) || null; + } + function sessionResult(query, session, index) { const items = asArray(session?.items).filter((item) => isHttpUrl(item?.url)); - const source = typeof session?.sourceDevice === "string" && session.sourceDevice.trim() - ? ` · ${session.sourceDevice.trim()}` + const sourceDevice = typeof session?.sourceDevice === "string" ? session.sourceDevice.trim() : ""; + const searchValues = [ + session?.name, + sourceDevice, + ...items.flatMap((item) => [item?.title, item?.url]), + ]; + if (!valuesMatchQuery(query, searchValues)) return null; + + const matchedItem = matchingSessionItem(query, items); + const source = sourceDevice ? ` · ${sourceDevice}` : ""; + const itemMatch = matchedItem + ? ` · ${matchedItem.title || displayHost(matchedItem.url)} · ${matchedItem.url}` : ""; - const subtitle = `${items.length} tab${items.length === 1 ? "" : "s"}${source}`; - const score = searchScore(query, session?.name, subtitle, 390); - if (score < 0) return null; + const subtitle = `${items.length} tab${items.length === 1 ? "" : "s"}${source}${itemMatch}`; + + let score = searchScore(query, session?.name, sourceDevice, 430); + if (score < 0 && matchedItem) score = searchScore(query, matchedItem?.title, matchedItem?.url, 400); + if (score < 0) score = 390; + return { kind: "session", title: session.name || "Session", diff --git a/apps/extension/wxt.config.ts b/apps/extension/wxt.config.ts index fcdb2f5..08ea44c 100644 --- a/apps/extension/wxt.config.ts +++ b/apps/extension/wxt.config.ts @@ -5,7 +5,7 @@ export default defineConfig({ modules: ["@wxt-dev/module-react"], manifest: ({ browser }) => ({ name: "Dockmark", - version: "1.5.1", + version: "1.5.2", description: "Browser bridge for Dockmark tabs, reviewed Capture Inbox, sessions, Smart Collection-aware local-first search, reviewed bookmark sync, local health checks and device pairing.", icons: { 16: "icons/dockmark-16.png", diff --git a/apps/web/src/extension-distribution.ts b/apps/web/src/extension-distribution.ts index 15e7146..a6cad1f 100644 --- a/apps/web/src/extension-distribution.ts +++ b/apps/web/src/extension-distribution.ts @@ -1,4 +1,4 @@ -export const EXTENSION_RELEASE_VERSION = "1.5.1"; +export const EXTENSION_RELEASE_VERSION = "1.5.2"; // Fill these when browser-store listings are public. The UI will promote the // store action while keeping manual downloads visible. diff --git a/scripts/chromium-newtab-ux-smoke.mjs b/scripts/chromium-newtab-ux-smoke.mjs index 0328500..bf2ca27 100644 --- a/scripts/chromium-newtab-ux-smoke.mjs +++ b/scripts/chromium-newtab-ux-smoke.mjs @@ -171,21 +171,38 @@ try { } if (requestUrl.pathname === "/api/sessions") { return json({ - sessions: [{ - id: "session-ux", - name: "Morning Session", - sourceDevice: "Smoke browser", - createdAt: now, - updatedAt: now, - items: [{ - id: "session-item-ux", - sessionId: "session-ux", - title: "Session target", - url: `${origin}/session`, - pinned: false, - position: 0, - }], - }], + sessions: [ + { + id: "session-ux", + name: "Morning Session", + sourceDevice: "Smoke browser", + createdAt: now, + updatedAt: now, + items: [{ + id: "session-item-ux", + sessionId: "session-ux", + title: "Session target", + url: `${origin}/session`, + pinned: false, + position: 0, + }], + }, + { + id: "session-campus", + name: "Campus Tools", + sourceDevice: "Web", + createdAt: now, + updatedAt: now, + items: [{ + id: "session-item-campus", + sessionId: "session-campus", + title: "学生电子邮箱服务中心 — 北京信息科技大学 BIST", + url: "https://mail.bist.edu.cn/mail/", + pinned: false, + position: 0, + }], + }, + ], }); } return json({}); @@ -219,11 +236,24 @@ try { assert.match(await sessionRow.innerText(), /1 tab/); assert.match(await sessionRow.innerText(), /Smoke browser/); + await search.fill("学生电子"); + const campusByTitle = page.locator("#command-results .result-row").filter({ hasText: "Campus Tools" }).first(); + await campusByTitle.waitFor(); + assert.match(await campusByTitle.innerText(), /学生电子邮箱服务中心/); + + await search.fill("mail.bist"); + const campusByUrl = page.locator("#command-results .result-row").filter({ hasText: "Campus Tools" }).first(); + await campusByUrl.waitFor(); + assert.match(await campusByUrl.innerText(), /mail\.bist\.edu\.cn\/mail/); + + await search.fill("bist.edu.cn/mail"); + await page.locator("#command-results .result-row").filter({ hasText: "Campus Tools" }).first().waitFor(); + const cachedSessionNames = await page.evaluate(async () => { const stored = await chrome.storage.local.get("dockmarkNewTabCacheV1"); return (stored.dockmarkNewTabCacheV1?.sessions ?? []).map((session) => session.name); }); - assert.deepEqual(cachedSessionNames, ["Morning Session"]); + assert.deepEqual(cachedSessionNames, ["Morning Session", "Campus Tools"]); await search.fill("Cached Target"); const foregroundPromise = context.waitForEvent("page"); @@ -254,7 +284,8 @@ try { console.log("✓ New Tab suggests bare ! search-engine shortcuts and Enter accepts them"); console.log("✓ New Tab auto refresh hydrates and caches Sessions on the first real launcher load"); - console.log("✓ New Tab searches and restores a cloud-refreshed Session"); + console.log("✓ New Tab searches Sessions by name, tab title and partial tab URL"); + console.log("✓ New Tab restores a cloud-refreshed Session"); console.log("✓ Shift+Enter opens a selected URL in a foreground tab"); console.log("✓ Ctrl/Command+Enter opens a selected URL in a background tab"); console.log("✓ / focuses the launcher input from the New Tab page");