Skip to content

Commit 9828b8d

Browse files
committed
support open message
1 parent eea41dd commit 9828b8d

File tree

13 files changed

+170
-21
lines changed

13 files changed

+170
-21
lines changed

special-pages/pages/history/app/HistoryProvider.js

Lines changed: 45 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import { useContext } from 'preact/hooks';
33
import { useSignalEffect } from '@preact/signals';
44
import { paramsToQuery } from './history.service.js';
55
import { OVERSCAN_AMOUNT } from './constants.js';
6+
import { usePlatformName } from './types.js';
7+
import { eventToTarget } from '../../../shared/handlers.js';
68

79
// Create the context
810
const HistoryServiceContext = createContext({
@@ -20,6 +22,7 @@ const HistoryServiceContext = createContext({
2022
* @param {import("preact").ComponentChild} props.children - The child components that will consume the history service context.
2123
*/
2224
export function HistoryServiceProvider({ service, initial, children }) {
25+
const platFormName = usePlatformName();
2326
useSignalEffect(() => {
2427
// Add a listener for the 'search-commit' event
2528
window.addEventListener('search-commit', (/** @type {CustomEvent<{params: URLSearchParams}>} */ event) => {
@@ -57,30 +60,55 @@ export function HistoryServiceProvider({ service, initial, children }) {
5760
function handler(/** @type {MouseEvent} */ event) {
5861
if (!(event.target instanceof Element)) return;
5962
const btn = /** @type {HTMLButtonElement|null} */ (event.target.closest('button'));
60-
if (btn?.dataset.titleMenu) {
61-
event.stopImmediatePropagation();
62-
event.preventDefault();
63-
return confirm(`todo: title menu for ${btn.dataset.titleMenu}`);
64-
}
65-
if (btn?.dataset.rowMenu) {
66-
event.stopImmediatePropagation();
63+
const anchor = /** @type {HTMLButtonElement|null} */ (event.target.closest('a[href][data-url]'));
64+
if (btn) {
65+
if (btn?.dataset.titleMenu) {
66+
event.stopImmediatePropagation();
67+
event.preventDefault();
68+
return confirm(`todo: title menu for ${btn.dataset.titleMenu}`);
69+
}
70+
if (btn?.dataset.rowMenu) {
71+
event.stopImmediatePropagation();
72+
event.preventDefault();
73+
return confirm(`todo: row menu for ${btn.dataset.rowMenu}`);
74+
}
75+
if (btn?.dataset.deleteRange) {
76+
event.stopImmediatePropagation();
77+
event.preventDefault();
78+
return confirm(`todo: delete range for ${btn.dataset.deleteRange}`);
79+
}
80+
if (btn?.dataset.deleteAll) {
81+
event.stopImmediatePropagation();
82+
event.preventDefault();
83+
return confirm(`todo: delete all`);
84+
}
85+
} else if (anchor) {
86+
const url = anchor.dataset.url;
87+
if (!url) return;
6788
event.preventDefault();
68-
return confirm(`todo: row menu for ${btn.dataset.rowMenu}`);
69-
}
70-
if (btn?.dataset.deleteRange) {
7189
event.stopImmediatePropagation();
72-
event.preventDefault();
73-
return confirm(`todo: delete range for ${btn.dataset.deleteRange}`);
74-
}
75-
if (btn?.dataset.deleteAll) {
76-
event.stopImmediatePropagation();
77-
event.preventDefault();
78-
return confirm(`todo: delete all`);
90+
const target = eventToTarget(event, platFormName);
91+
service.openUrl(url, target);
92+
return;
7993
}
8094
return null;
8195
}
8296
document.addEventListener('click', handler);
97+
98+
const handleAuxClick = (event) => {
99+
const anchor = /** @type {HTMLButtonElement|null} */ (event.target.closest('a[href][data-url]'));
100+
const url = anchor?.dataset.url;
101+
if (anchor && url && event.button === 1) {
102+
event.preventDefault();
103+
event.stopImmediatePropagation();
104+
const target = eventToTarget(event, platFormName);
105+
service.openUrl(url, target);
106+
}
107+
};
108+
document.addEventListener('auxclick', handleAuxClick);
109+
83110
return () => {
111+
document.removeEventListener('auxclick', handleAuxClick);
84112
document.removeEventListener('click', handler);
85113
};
86114
});

special-pages/pages/history/app/components/App.module.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ body {
1111
.layout {
1212
display: grid;
1313
grid-template-columns: 250px 1fr;
14-
grid-template-rows: max-content auto;
14+
grid-template-rows: max-content 1fr;
1515
grid-template-areas:
1616
'aside header'
1717
'aside main';

special-pages/pages/history/app/components/Item.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,9 @@ export const Item = memo(
2929
</div>
3030
)}
3131
<div class={cn(styles.row, kind === END_KIND && styles.last)} tabindex={0}>
32-
<span class={styles.entryTitle}>{title}</span>
32+
<a href={url} data-url={url} class={styles.entryLink}>
33+
{title}
34+
</a>
3335
<span class={styles.url}>{url}</span>
3436
<span class={styles.time}>{dateTimeOfDay}</span>
3537
<button class={styles.dots} data-row-menu={id}>

special-pages/pages/history/app/components/Item.module.css

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,15 @@
4343
}
4444
}
4545

46-
.entryTitle {
46+
.entryLink {
4747
white-space: nowrap;
4848
font-weight: 510;
4949
flex-shrink: 1;
5050
min-width: 0;
5151
overflow: hidden;
5252
text-overflow: ellipsis;
53+
text-decoration: none;
54+
color: inherit;
5355
}
5456

5557
.url {

special-pages/pages/history/app/history.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,19 @@ Response, note: always return the same query I sent:
9999

100100
## Notifications
101101

102+
### `open`
103+
- {@link "History Messages".OpenNotification}
104+
- Sent when a user clicks a link, sends {@link "History Messages".OpenNotification}
105+
- Target is one of {@link "History Messages".OpenTarget}
106+
107+
example payload
108+
```json
109+
{
110+
"url": "https://example.com/path",
111+
"target": "same-tab"
112+
}
113+
```
114+
102115
### `reportInitException`
103116
{@link "History Messages".ReportInitExceptionNotification}
104117

special-pages/pages/history/app/history.service.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,14 @@ export class HistoryService {
8585
this.query.triggerFetch(query);
8686
// console.log('next query', query);
8787
}
88+
89+
/**
90+
* @param {string} url
91+
* @param {import('../types/history.js').OpenTarget} target
92+
*/
93+
openUrl(url, target) {
94+
this.history.messaging.notify('open', { url, target });
95+
}
8896
}
8997

9098
/**

special-pages/pages/history/app/types.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,7 @@ export const MessagingContext = createContext(/** @type {import("../src/index.js
1919
export const useMessaging = () => useContext(MessagingContext);
2020
export const SettingsContext = createContext(new Settings({ platform: { name: 'macos' } }));
2121
export const useSettings = () => useContext(SettingsContext);
22+
23+
export function usePlatformName() {
24+
return useContext(SettingsContext).platform.name;
25+
}

special-pages/pages/history/integration-tests/history.page.js

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,4 +176,38 @@ export class HistoryTestPage {
176176
});
177177
expect(scrollPosition).toBeTruthy();
178178
}
179+
180+
async opensLinks() {
181+
const { page } = this;
182+
const link = page.locator('a[href][data-url]').nth(0);
183+
await link.click();
184+
await link.click({ modifiers: ['Meta'] });
185+
await link.click({ modifiers: ['Shift'] });
186+
await link.click({ button: 'middle' });
187+
await this._opensMainLink();
188+
}
189+
async _opensMainLink() {
190+
const calls = await this.mocks.waitForCallCount({ method: 'open', count: 3 });
191+
const url = 'https://www.youtube.com/watch?v=75Mw8r5gW8E';
192+
193+
expect(calls[0].payload.params).toStrictEqual({
194+
url,
195+
target: 'same-tab',
196+
});
197+
198+
expect(calls[1].payload.params).toStrictEqual({
199+
url,
200+
target: 'new-tab',
201+
});
202+
203+
expect(calls[2].payload.params).toStrictEqual({
204+
url,
205+
target: 'new-window',
206+
});
207+
208+
expect(calls[3].payload.params).toStrictEqual({
209+
url,
210+
target: 'new-window',
211+
});
212+
}
179213
}

special-pages/pages/history/integration-tests/history.spec.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,4 +86,9 @@ test.describe('history', () => {
8686
// ensure it's a full reset
8787
await hp.didMakeNthQuery({ nth: 2, query: { term: '' } });
8888
});
89+
test('opening links', async ({ page }, workerInfo) => {
90+
const hp = HistoryTestPage.create(page, workerInfo).withEntries(5);
91+
await hp.openPage({});
92+
await hp.opensLinks();
93+
});
8994
});
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"$schema": "http://json-schema.org/draft-07/schema#",
3+
"title": "History Open Action",
4+
"type": "object",
5+
"required": [
6+
"target",
7+
"url"
8+
],
9+
"properties": {
10+
"url": {
11+
"description": "The url to open",
12+
"type": "string"
13+
},
14+
"target": {
15+
"$ref": "./types/open-target.json"
16+
}
17+
}
18+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"$schema": "http://json-schema.org/draft-07/schema#",
3+
"title": "Open Target",
4+
"type": "string",
5+
"enum": ["same-tab", "new-tab", "new-window"]
6+
}

special-pages/pages/history/types/history.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
* @module History Messages
77
*/
88

9+
export type OpenTarget = "same-tab" | "new-tab" | "new-window";
910
export type Range =
1011
| "all"
1112
| "today"
@@ -25,9 +26,23 @@ export type QueryKind = SearchTerm | DomainFilter | RangeFilter;
2526
* Requests, Notifications and Subscriptions from the History feature
2627
*/
2728
export interface HistoryMessages {
28-
notifications: ReportInitExceptionNotification | ReportPageExceptionNotification;
29+
notifications: OpenNotification | ReportInitExceptionNotification | ReportPageExceptionNotification;
2930
requests: GetRangesRequest | InitialSetupRequest | QueryRequest;
3031
}
32+
/**
33+
* Generated from @see "../messages/open.notify.json"
34+
*/
35+
export interface OpenNotification {
36+
method: "open";
37+
params: HistoryOpenAction;
38+
}
39+
export interface HistoryOpenAction {
40+
/**
41+
* The url to open
42+
*/
43+
url: string;
44+
target: OpenTarget;
45+
}
3146
/**
3247
* Generated from @see "../messages/reportInitException.notify.json"
3348
*/

special-pages/shared/handlers.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/**
2+
* @param {MouseEvent} event
3+
* @param {ImportMeta['platform']} platformName
4+
* @return {'new-tab' | 'new-window' | 'same-tab'}
5+
*/
6+
export function eventToTarget(event, platformName) {
7+
const isControlClick = platformName === 'macos' ? event.metaKey : event.ctrlKey;
8+
if (isControlClick) {
9+
return 'new-tab';
10+
} else if (event.shiftKey || event.button === 1 /* middle click */) {
11+
return 'new-window';
12+
}
13+
return 'same-tab';
14+
}

0 commit comments

Comments
 (0)