Skip to content

Commit 1f1ca8e

Browse files
wikirbyclaude
andcommitted
Add region overlay instruction bar, mode button tooltips, notebook refresh fix
Region overlay: Shadow DOM instruction bar with i18n text + "Back (Esc)" button, CSS-isolated from page styles. Escape/Back stays in region mode instead of switching to full page. Worker injects i18n strings via window.__regionStrings before overlay script. Renderer: add mode button tooltips using existing i18n keys. Remove token expiry pre-check from notebook fetch so newly created sections appear. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent c30de2f commit 1f1ca8e

4 files changed

Lines changed: 75 additions & 17 deletions

File tree

docs/unified-window-plan.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,8 @@ Plain HTML + TypeScript — no Mithril dependency:
214214
### Region Capture
215215
Standalone `regionOverlay.ts` injected directly into original tab via `scripting.executeScript`. No Mithril, no clipper.tsx reactivation.
216216
- **Overlay**: Full-viewport div with crosshair cursor, canvas-based dark overlay with hole-punch selection
217-
- **Selection**: Mouse drag draws rectangle. Min 5px. Esc cancels.
217+
- **Instruction bar**: Centered pill at top with i18n instruction text + "Back (Esc)" button. Uses Shadow DOM for CSS isolation from page styles. Hides during drag, reappears on too-small selection. i18n strings passed from renderer → worker → `window.__regionStrings` injection before overlay script.
218+
- **Selection**: Mouse drag draws rectangle. Min 5px. Esc/Back cancels (stays in region mode, shows thumbnails or "Add another region").
218219
- **Message format**: JSON string via `chrome.runtime.sendMessage` (required by offscreen.ts message handler)
219220
- **Capture**: Worker captures original tab as JPEG 95% via `captureVisibleTab`, sends full image + coords via port
220221
- **Crop**: Renderer crops using canvas with DPR handling, converts to JPEG 95%

src/scripts/extensions/regionOverlay.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,41 @@
1717

1818
// Selection border drawn on canvas (no separate div — avoids alignment gaps)
1919

20+
// Instruction bar — Shadow DOM isolates from page CSS
21+
let instrHost = document.createElement("div");
22+
instrHost.style.cssText = "position:absolute;top:16px;left:0;right:0;z-index:1;pointer-events:none;";
23+
let shadow = instrHost.attachShadow({ mode: "closed" });
24+
shadow.innerHTML = '<style>'
25+
+ ':host { all: initial; }'
26+
+ '.wrap { display:flex;justify-content:center; }'
27+
+ '.bar { display:flex;align-items:center;gap:16px;padding:10px 20px;'
28+
+ 'background:rgba(0,0,0,0.75);border:1px solid rgba(255,255,255,0.3);'
29+
+ 'border-radius:8px;font:14px/1 -apple-system,Segoe UI,sans-serif;color:#fff;'
30+
+ 'pointer-events:auto;user-select:none;white-space:nowrap; }'
31+
+ '.text { opacity:0.9; }'
32+
+ '.back-btn { padding:6px 14px;background:rgba(255,255,255,0.15);color:#fff;'
33+
+ 'border:1px solid rgba(255,255,255,0.4);border-radius:4px;'
34+
+ 'font:13px/1 -apple-system,Segoe UI,sans-serif;cursor:pointer;outline:none;'
35+
+ 'transition:background 0.15s; }'
36+
+ '.back-btn:hover { background:rgba(255,255,255,0.3); }'
37+
+ '</style>'
38+
+ '<div class="wrap"><div class="bar">'
39+
+ '<span class="text"></span>'
40+
+ '<button class="back-btn"></button>'
41+
+ '</div></div>';
42+
43+
let strings = (window as any).__regionStrings || {};
44+
let instrText = shadow.querySelector(".text") as HTMLElement;
45+
instrText.textContent = strings.instruction || "Drag a selection with the mouse, and then release to capture.";
46+
let cancelBtn = shadow.querySelector(".back-btn") as HTMLButtonElement;
47+
cancelBtn.textContent = (strings.back || "Back") + " (Esc)";
48+
cancelBtn.addEventListener("click", function(e) {
49+
e.stopPropagation();
50+
cleanup();
51+
chrome.runtime.sendMessage(JSON.stringify({ action: "regionCancelled" }));
52+
});
53+
root.appendChild(instrHost);
54+
2055
document.body.appendChild(root);
2156

2257
let ctx = canvas.getContext("2d")!;
@@ -62,8 +97,10 @@
6297
draw();
6398

6499
function onMouseDown(e: MouseEvent) {
100+
if (e.composedPath().indexOf(instrHost) !== -1) { return; }
65101
e.preventDefault();
66102
dragging = true;
103+
instrHost.style.display = "none";
67104
startX = e.clientX;
68105
startY = e.clientY;
69106
endX = startX;
@@ -93,6 +130,7 @@
93130

94131
if (w < 5 || h < 5) {
95132
// Too small — reset and let user try again
133+
instrHost.style.display = "";
96134
draw();
97135
return;
98136
}

src/scripts/extensions/webExtensionBase/webExtensionWorker.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -675,16 +675,24 @@ export class WebExtensionWorker extends ExtensionWorkerBase<W3CTab, number> {
675675

676676
if (message.action === "startRegion") {
677677
// Focus original tab, inject standalone overlay, listen for result
678-
let regionTabId = this.tab.id;
678+
let regionTabId = this.tab.id as number;
679679
let regionWindowId = 0;
680680
WebExtension.browser.tabs.get(regionTabId, (t: any) => {
681681
if (!t || !t.windowId) { return; }
682682
regionWindowId = t.windowId;
683683
WebExtension.browser.windows.update(regionWindowId, { focused: true }, () => {
684684
if (WebExtension.browser.runtime.lastError) { /* ignore */ }
685+
// Inject i18n strings before overlay script so it can read them
686+
let regionStrings = message.regionStrings || {};
685687
WebExtension.browser.scripting.executeScript({
686688
target: { tabId: regionTabId },
687-
files: ["regionOverlay.js"]
689+
func: function(s: any) { (window as any).__regionStrings = s; },
690+
args: [regionStrings]
691+
}, () => {
692+
WebExtension.browser.scripting.executeScript({
693+
target: { tabId: regionTabId },
694+
files: ["regionOverlay.js"]
695+
});
688696
});
689697
});
690698
});

src/scripts/renderer.ts

Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,17 @@ document.querySelectorAll(".mode-btn").forEach((btn) => {
145145
});
146146
titleField.placeholder = strings.titlePlaceholder;
147147
noteField.placeholder = strings.notePlaceholder;
148+
// Mode button tooltips (matches old modeButton.tsx tooltip pattern)
149+
let tooltipMap: any = {
150+
fullpage: loc("WebClipper.ClipType.ScreenShot.Button.Tooltip", "Take a screenshot of the whole page, just like you see it."),
151+
article: loc("WebClipper.ClipType.Button.Tooltip", "Clip just the {0} in an easy-to-read format.").replace("{0}", strings.modeArticle.toLowerCase()),
152+
bookmark: loc("WebClipper.ClipType.Bookmark.Button.Tooltip", "Clip just the title, thumbnail, synopsis, and link."),
153+
region: loc("WebClipper.ClipType.Region.Button.Tooltip", "Take a screenshot of the part of the page you'll select.")
154+
};
155+
document.querySelectorAll(".mode-btn").forEach((btn) => {
156+
let mode = btn.getAttribute("data-mode");
157+
if (mode && tooltipMap[mode]) { (btn as HTMLElement).title = tooltipMap[mode]; }
158+
});
148159
let sourceLabelEl = document.getElementById("source-label");
149160
if (sourceLabelEl) { sourceLabelEl.textContent = strings.sourceLabel; }
150161
// Field labels
@@ -440,12 +451,10 @@ async function fetchFreshNotebooks() {
440451
if (!userInfoRaw) { return; }
441452
let userInfo = JSON.parse(userInfoRaw);
442453
let accessToken = userInfo && userInfo.data ? userInfo.data.accessToken : "";
443-
let lastUpdated = userInfo ? userInfo.lastUpdated : 0;
444-
let tokenExp = userInfo && userInfo.data ? userInfo.data.accessTokenExpiration : 0;
445454
if (!accessToken) { return; }
446-
// accessTokenExpiration is relative (seconds until expiry), not absolute
447-
// Matches CachedHttp.valueHasExpired: (lastUpdated + expiration*1000 - 180000) < Date.now()
448-
if (tokenExp && lastUpdated && (lastUpdated + tokenExp * 1000 - 180000) < Date.now()) { return; }
455+
// Don't skip on token expiry — try the fetch anyway. The API will return 401 if
456+
// truly expired, and we silently keep cached data. Skipping here caused stale
457+
// notebook lists (newly created sections wouldn't appear).
449458

450459
let apiUrl = "https://www.onenote.com/api/v1.0/me/notes/notebooks"
451460
+ "?$expand=sections,sectionGroups($expand=sections,sectionGroups)";
@@ -872,7 +881,13 @@ function startRegionCapture() {
872881
statusText.textContent = loc("WebClipper.ClipType.Region.ProgressLabel", "Select a region on the page...");
873882
saveBtn.disabled = true;
874883
// Keep previewContainer visible (display:block) to hold flex space — sidebar stays right
875-
safeSend({ action: "startRegion" });
884+
safeSend({
885+
action: "startRegion",
886+
regionStrings: {
887+
instruction: loc("WebClipper.Label.RegionSelectionMouseInstruction", "Drag a selection with the mouse, and then release to capture."),
888+
back: loc("WebClipper.Action.BackToHome", "Back")
889+
}
890+
});
876891
}
877892

878893
function switchToRegion() {
@@ -1438,14 +1453,10 @@ port.onMessage.addListener((message: any) => {
14381453
}
14391454

14401455
if (message.action === "regionCancelled") {
1441-
if (fullPageComplete) {
1442-
document.querySelectorAll(".mode-btn").forEach((b) => b.classList.remove("selected"));
1443-
let fpBtn = document.querySelector('.mode-btn[data-mode="fullpage"]');
1444-
if (fpBtn) { fpBtn.classList.add("selected"); }
1445-
switchToFullPage();
1446-
} else {
1447-
capturePanel.style.display = "none";
1448-
}
1456+
// Stay in region mode — user cancelled one selection, not the mode itself.
1457+
// Show thumbnails (or "Add another region" button if empty).
1458+
capturePanel.style.display = "none";
1459+
renderRegionThumbnails();
14491460
}
14501461

14511462
if (message.action === "saveResult") {

0 commit comments

Comments
 (0)