Skip to content

Commit 0c7164d

Browse files
committed
reintroduce popup with host permission for now
1 parent dd63d95 commit 0c7164d

File tree

8 files changed

+425
-39
lines changed

8 files changed

+425
-39
lines changed

components/BlueskySettings.tsx

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
autoFetchEnabled,
44
addDomainToWhitelist,
55
removeDomainFromWhitelist,
6+
showQuotePopupOnSelection,
67
} from "@/lib/settings";
78
import { WhitelistedSitesManager } from "./WhitelistedSitesManager";
89
import { currentDomain, isWhitelisted } from "@/lib/messaging";
@@ -12,6 +13,10 @@ const handleAutoFetchToggle = () => {
1213
autoFetchEnabled.value = !autoFetchEnabled.value;
1314
};
1415

16+
const handleShowQuotePopupToggle = () => {
17+
showQuotePopupOnSelection.value = !showQuotePopupOnSelection.value;
18+
};
19+
1520
export function BlueskySettings() {
1621
const showWhitelistManager = useSignal(false);
1722
const handleWhitelistToggle = async () => {
@@ -103,6 +108,39 @@ export function BlueskySettings() {
103108
</div>
104109
</div>
105110
)}
111+
112+
{/* New toggle for showQuotePopupOnSelection */}
113+
<div className="flex items-center justify-between mb-2 gap-1 pt-4 border-t border-gray-200 dark:border-gray-700 mt-4">
114+
<div>
115+
<label
116+
htmlFor="show-quote-popup"
117+
className="text-sm font-medium text-gray-900 dark:text-gray-100"
118+
>
119+
Show quote popup on selection
120+
</label>
121+
<p className="text-xs text-gray-500 dark:text-gray-400">
122+
{showQuotePopupOnSelection.value
123+
? "Popup will show on text selection"
124+
: "Popup will not show on text selection"}
125+
</p>
126+
</div>
127+
<label className="relative inline-flex items-center cursor-pointer">
128+
<input
129+
type="checkbox"
130+
id="show-quote-popup"
131+
className="sr-only"
132+
checked={showQuotePopupOnSelection.value}
133+
onChange={handleShowQuotePopupToggle}
134+
/>
135+
<div
136+
className={`w-9 h-5 rounded-full transition ${showQuotePopupOnSelection.value
137+
? "bg-green-600"
138+
: "bg-gray-300 dark:bg-gray-600"
139+
} after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-4 after:w-4 after:transition-all ${showQuotePopupOnSelection.value ? "after:translate-x-4" : ""
140+
}`}
141+
/>
142+
</label>
143+
</div>
106144
</div>
107145

108146
{/* Whitelisted Sites Manager Modal */}

components/Sidebar.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ function SidebarBody() {
3434
queryKey: ['posts', currentUrl.value],
3535
queryFn: async ({ signal }) => {
3636
if (!currentUrl.value) return [];
37-
// console.log("[Sidebar] fetching posts");
3837
return (await searchBskyPosts(currentUrl.value, { signal })) || [];
3938
},
4039
enabled: isSearchableUrl.value && autoFetchEnabled.value && isWhitelisted.value,

components/Sidepanel.tsx

Lines changed: 58 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { render } from "preact";
22
import { Sidebar } from "@/components/Sidebar";
33
import { currentUrl, quotedSelection } from "@/lib/messaging";
4+
import { showQuotePopupOnSelection } from "@/lib/settings";
45
import "@/lib/styles.css";
56
import { queryClient, appPersister } from "@/lib/queryClient";
67
import { QueryClientProvider } from "@tanstack/react-query";
@@ -18,38 +19,47 @@ export async function setupTabListener() {
1819
try {
1920
const currentWindow = await browser.windows.getCurrent();
2021
extensionWindowId = currentWindow.id;
21-
22-
const [tab] = await browser.tabs.query({ active: true, windowId: extensionWindowId });
22+
23+
const [tab] = await browser.tabs.query({
24+
active: true,
25+
windowId: extensionWindowId,
26+
});
2327
if (tab?.id && tab.url) {
2428
activeTabId = tab.id;
2529
currentUrl.value = tab.url;
2630
}
27-
28-
browser.tabs.onActivated.addListener(async (activeInfo: Browser.tabs.TabActiveInfo) => {
29-
if (activeInfo.windowId === extensionWindowId) {
30-
activeTabId = activeInfo.tabId;
31-
const tab = await browser.tabs.get(activeInfo.tabId);
32-
if (tab.url) {
33-
currentUrl.value = tab.url;
34-
} else if (tab.pendingUrl) {
35-
currentUrl.value = tab.pendingUrl;
36-
} else {
37-
currentUrl.value = "";
31+
32+
browser.tabs.onActivated.addListener(
33+
async (activeInfo: Browser.tabs.TabActiveInfo) => {
34+
if (activeInfo.windowId === extensionWindowId) {
35+
activeTabId = activeInfo.tabId;
36+
const tab = await browser.tabs.get(activeInfo.tabId);
37+
if (tab.url) {
38+
currentUrl.value = tab.url;
39+
} else if (tab.pendingUrl) {
40+
currentUrl.value = tab.pendingUrl;
41+
} else {
42+
currentUrl.value = "";
43+
}
3844
}
39-
}
40-
});
41-
42-
browser.tabs.onUpdated.addListener((tabId: number, changeInfo: Browser.tabs.TabChangeInfo) => {
43-
if (tabId === activeTabId && changeInfo.url) {
44-
currentUrl.value = changeInfo.url;
45-
}
46-
});
45+
},
46+
);
4747

48-
browser.runtime.onMessage.addListener((message: ContentScriptSelectionMessage) => {
49-
if (message.from === "content" && message.type === "SELECTION") {
50-
quotedSelection.value = message.data.selection || null;
51-
}
52-
});
48+
browser.tabs.onUpdated.addListener(
49+
(tabId: number, changeInfo: Browser.tabs.TabChangeInfo) => {
50+
if (tabId === activeTabId && changeInfo.url) {
51+
currentUrl.value = changeInfo.url;
52+
}
53+
},
54+
);
55+
56+
browser.runtime.onMessage.addListener(
57+
(message: ContentScriptSelectionMessage) => {
58+
if (message.from === "content" && message.type === "SELECTION") {
59+
quotedSelection.value = message.data.selection || null;
60+
}
61+
},
62+
);
5363
} catch (error) {
5464
console.error("Error setting up tab listener:", error);
5565
}
@@ -64,6 +74,26 @@ function App() {
6474
);
6575
}
6676

77+
browser.runtime.onMessage.addListener(
78+
(
79+
message: {
80+
type: "PING_SIDEPANEL";
81+
from: "content";
82+
},
83+
sender: Browser.runtime.MessageSender,
84+
sendResponse: (response?: object) => void,
85+
) => {
86+
if (message.type === "PING_SIDEPANEL") {
87+
if (showQuotePopupOnSelection.value) {
88+
sendResponse({ type: "PONG_SIDEPANEL", from: "sidepanel", showPopup: true });
89+
} else {
90+
sendResponse({ type: "PONG_SIDEPANEL", from: "sidepanel", showPopup: false });
91+
}
92+
return true;
93+
}
94+
},
95+
);
96+
6797
(async () => {
6898
try {
6999
await setupTabListener();
@@ -72,5 +102,5 @@ function App() {
72102
} catch (error) {
73103
console.error("Error during initial setup or restore:", error);
74104
}
75-
render(<App/>, document.getElementById("app")!);
76-
})();
105+
render(<App />, document.getElementById("app")!);
106+
})();
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import { useSignal, type Signal } from "@preact/signals";
2+
import { useEffect } from "preact/hooks";
3+
4+
interface SelectionPopupProps {
5+
position: Signal<{ top: number; left: number }>;
6+
title: string;
7+
sendSelection: () => void;
8+
}
9+
10+
// dev: shortcut is the first letter of the title
11+
const SelectionPopup = ({ position, title, sendSelection }: SelectionPopupProps) => {
12+
const isAnimating = useSignal(false);
13+
14+
useEffect(() => {
15+
const onKeyDown = (event: KeyboardEvent, title: string) => {
16+
if (event.metaKey || event.ctrlKey || event.altKey || event.shiftKey) {
17+
return;
18+
}
19+
20+
if (event.key.toLowerCase() === title[0].toLowerCase()) {
21+
event.preventDefault();
22+
event.stopPropagation();
23+
isAnimating.value = true;
24+
setTimeout(() => {
25+
sendSelection();
26+
isAnimating.value = false;
27+
}, 150);
28+
}
29+
};
30+
31+
document.addEventListener("keydown", (event) => onKeyDown(event, title));
32+
return () => {
33+
document.removeEventListener("keydown", (event) => onKeyDown(event, title));
34+
};
35+
}, [title, isAnimating, sendSelection]);
36+
37+
return (
38+
<div
39+
className={`font-sans leading-normal text-[12px] absolute z-10 flex items-center gap-1 rounded-lg border p-1 whitespace-nowrap min-w-max transition-all duration-150 ease-out
40+
${isAnimating.value ? 'mt-1 bg-blue-100 dark:bg-blue-700/50' : 'bg-white dark:bg-gray-800 border-blue-200 dark:border-blue-300/30'}`}
41+
style={{
42+
top:0,
43+
left:0,
44+
transform: `translate(${position.value.left}px, ${position.value.top}px)`,
45+
userSelect: "none",
46+
}}
47+
>
48+
<button
49+
className="inline-flex flex-shrink-0 items-center gap-1 rounded px-1.5 py-1 text-gray-700 hover:bg-blue-100
50+
dark:text-gray-300 dark:hover:bg-blue-700/50"
51+
onMouseDown={(e) => {
52+
e.stopPropagation();
53+
isAnimating.value = true;
54+
setTimeout(() => {
55+
sendSelection();
56+
isAnimating.value = false;
57+
}, 150);
58+
}}
59+
>
60+
{title}
61+
<kbd
62+
className="flex items-center justify-center rounded-sm bg-gray-200 px-1 py-0.5 text-xs text-gray-900
63+
dark:bg-gray-600 dark:text-gray-100"
64+
>
65+
{title[0].toUpperCase()}
66+
</kbd>
67+
</button>
68+
</div>
69+
);
70+
};
71+
72+
export default SelectionPopup;

0 commit comments

Comments
 (0)