Skip to content

Commit ed79fbb

Browse files
committed
history: support single row context menu
1 parent 6bc691c commit ed79fbb

File tree

10 files changed

+132
-8
lines changed

10 files changed

+132
-8
lines changed

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

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,9 @@ export function HistoryServiceProvider({ service, initial, children }) {
7272
if (btn?.dataset.rowMenu) {
7373
event.stopImmediatePropagation();
7474
event.preventDefault();
75-
return confirm(`todo: row menu for ${btn.dataset.rowMenu}`);
75+
// eslint-disable-next-line promise/prefer-await-to-then
76+
service.entriesMenu([btn.value], [Number(btn.dataset.index)]).catch(console.error);
77+
return;
7678
}
7779
if (btn?.dataset.deleteRange) {
7880
event.stopImmediatePropagation();
@@ -120,6 +122,7 @@ export function HistoryServiceProvider({ service, initial, children }) {
120122

121123
const actions = {
122124
'[data-section-title]': (elem) => elem.querySelector('button')?.value,
125+
'[data-history-entry]': (elem) => elem.querySelector('button')?.value,
123126
};
124127

125128
for (const [selector, valueFn] of Object.entries(actions)) {
@@ -129,8 +132,13 @@ export function HistoryServiceProvider({ service, initial, children }) {
129132
if (value) {
130133
event.preventDefault();
131134
event.stopImmediatePropagation();
132-
// eslint-disable-next-line promise/prefer-await-to-then
133-
service.menuTitle(value).catch(console.error);
135+
if (match.dataset.sectionTitle) {
136+
// eslint-disable-next-line promise/prefer-await-to-then
137+
service.menuTitle(value).catch(console.error);
138+
} else if (match.dataset.historyEntry) {
139+
// eslint-disable-next-line promise/prefer-await-to-then
140+
service.entriesMenu([value], [Number(match.dataset.index)]).catch(console.error);
141+
}
134142
}
135143
break;
136144
}

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,9 @@ export const Item = memo(
1818
* @param {number} props.kind - The kind or type of the item that determines its visual style.
1919
* @param {string} props.dateRelativeDay - The relative day information to display (shown when kind is equal to TITLE_KIND).
2020
* @param {string} props.dateTimeOfDay - the time of day, like 11.00am.
21+
* @param {number} props.index - original index
2122
*/
22-
function Item({ id, url, domain, title, kind, dateRelativeDay, dateTimeOfDay }) {
23+
function Item({ id, url, domain, title, kind, dateRelativeDay, dateTimeOfDay, index }) {
2324
const { t } = useTypedTranslation();
2425
return (
2526
<Fragment>
@@ -36,13 +37,13 @@ export const Item = memo(
3637
</button>
3738
</div>
3839
)}
39-
<div class={cn(styles.row, kind === END_KIND && styles.last)} tabindex={0}>
40+
<div class={cn(styles.row, kind === END_KIND && styles.last)} tabindex={0} data-history-entry={id}>
4041
<a href={url} data-url={url} class={styles.entryLink}>
4142
{title}
4243
</a>
4344
<span class={styles.domain}>{domain}</span>
4445
<span class={styles.time}>{dateTimeOfDay}</span>
45-
<button class={styles.dots} data-row-menu={id}>
46+
<button class={styles.dots} data-row-menu data-index={index} value={id}>
4647
<Dots />
4748
</button>
4849
</div>

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ export function Results({ results }) {
3333
title={item.title}
3434
dateRelativeDay={item.dateRelativeDay}
3535
dateTimeOfDay={item.dateTimeOfDay}
36+
index={index}
3637
/>
3738
</li>
3839
);

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

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ export class HistoryService {
2626
},
2727
}).withUpdater((old, next, trigger) => {
2828
if (trigger === 'manual') {
29-
// console.log('manual trigger, always accepting next:', next);
29+
console.log('manual trigger, always accepting next:', next);
3030
return next;
3131
}
3232
if (eq(old.info.query, next.info.query)) {
@@ -102,6 +102,30 @@ export class HistoryService {
102102
return this.ranges.onData(({ data, source }) => cb(data));
103103
}
104104

105+
/**
106+
* @param {string[]} ids
107+
* @param {number[]} indexes
108+
*/
109+
async entriesMenu(ids, indexes) {
110+
const response = await this.history.messaging.request('entries_menu', { ids });
111+
if (response.action === 'none') return;
112+
if (response.action !== 'delete') return;
113+
this.query.update((old) => {
114+
const inverted = indexes.sort((a, b) => b - a);
115+
const removed = [];
116+
const next = old.results.slice();
117+
118+
// remove items in reverse that that splice works multiple times
119+
for (let i = 0; i < inverted.length; i++) {
120+
removed.push(next.splice(inverted[i], 1));
121+
}
122+
123+
/** @type {QueryData} */
124+
const nextStats = { ...old, results: next };
125+
return nextStats;
126+
});
127+
}
128+
105129
/**
106130
* @param {string} dateRelativeDay
107131
*/

special-pages/pages/history/app/mocks/mock-transport.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,18 @@ export function mockTransport() {
5858
const msg = /** @type {any} */ (_msg);
5959

6060
switch (msg.method) {
61+
case 'entries_menu': {
62+
console.log('📤 [entries_menu]: ', JSON.stringify(msg.params));
63+
// prettier-ignore
64+
const lines = [
65+
`entries_menu: ${JSON.stringify(msg.params)}`,
66+
`To simulate deleting these items, press confirm`
67+
].join('\n');
68+
if (confirm(lines)) {
69+
return Promise.resolve({ action: 'delete' });
70+
}
71+
return Promise.resolve({ action: 'none' });
72+
}
6173
case 'title_menu': {
6274
console.log('📤 [deleteRange]: ', JSON.stringify(msg.params));
6375
// prettier-ignore

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

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { perPlatform } from 'injected/integration-test/type-helpers.mjs';
22
import { Mocks } from '../../../shared/mocks.js';
33
import { expect } from '@playwright/test';
4+
import { generateSampleData } from '../app/mocks/history.mocks.js';
45

56
/**
67
* @typedef {import('injected/integration-test/type-helpers.mjs').Build} Build
@@ -298,6 +299,31 @@ export class HistoryTestPage {
298299
// await this.sideBarItemWasRemoved('Today');
299300
}
300301

302+
/**
303+
* @param {import('../types/history.ts').DeleteRangeResponse} resp
304+
*/
305+
async deletesFromHistoryEntry(resp) {
306+
const { page } = this;
307+
308+
// Handle dialog interaction based on response action
309+
if (resp.action === 'delete') {
310+
page.on('dialog', (dialog) => {
311+
return dialog.accept();
312+
});
313+
} else {
314+
page.on('dialog', (dialog) => dialog.dismiss());
315+
}
316+
// console.log(data[0].title);
317+
const data = generateSampleData({ count: this.entries, offset: 0 });
318+
const first = data[0];
319+
const row = page.getByText(first.title);
320+
await row.hover();
321+
await page.locator(`[data-row-menu][value=${data[0].id}]`).click();
322+
323+
const calls = await this.mocks.waitForCallCount({ method: 'entries_menu', count: 1 });
324+
expect(calls[0].payload.params).toStrictEqual({ ids: [data[0].id] });
325+
}
326+
301327
async rightClicksSectionTitle() {
302328
const { page } = this;
303329
const title = page.getByRole('list').getByText('Today');

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,4 +118,9 @@ test.describe('history', () => {
118118
await hp.openPage({});
119119
await hp.rightClicksSectionTitle();
120120
});
121+
test('3 dots menu on history entry', async ({ page }, workerInfo) => {
122+
const hp = HistoryTestPage.create(page, workerInfo).withEntries(2000);
123+
await hp.openPage({});
124+
await hp.deletesFromHistoryEntry({ action: 'delete' });
125+
});
121126
});
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"$schema": "http://json-schema.org/draft-07/schema#",
3+
"type": "object",
4+
"title": "Entries Menu Params",
5+
"required": [
6+
"ids"
7+
],
8+
"properties": {
9+
"ids": {
10+
"type": "array",
11+
"items": {
12+
"type": "string"
13+
}
14+
}
15+
}
16+
}
17+
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"$schema": "http://json-schema.org/draft-07/schema#",
3+
"type": "object",
4+
"required": ["action"],
5+
"properties": {
6+
"action": {
7+
"$ref": "types/action-response.json"
8+
}
9+
}
10+
}
11+

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

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,12 @@ export type RelativeDay = string;
3939
*/
4040
export interface HistoryMessages {
4141
notifications: OpenNotification | ReportInitExceptionNotification | ReportPageExceptionNotification;
42-
requests: DeleteRangeRequest | GetRangesRequest | InitialSetupRequest | QueryRequest | TitleMenuRequest;
42+
requests:
43+
| DeleteRangeRequest
44+
| EntriesMenuRequest
45+
| GetRangesRequest
46+
| InitialSetupRequest
47+
| QueryRequest | TitleMenuRequest;
4348
}
4449
/**
4550
* Generated from @see "../messages/open.notify.json"
@@ -89,6 +94,20 @@ export interface DeleteRangeParams {
8994
export interface DeleteRangeResponse {
9095
action: ActionResponse;
9196
}
97+
/**
98+
* Generated from @see "../messages/entries_menu.request.json"
99+
*/
100+
export interface EntriesMenuRequest {
101+
method: "entries_menu";
102+
params: EntriesMenuParams;
103+
result: EntriesMenuResponse;
104+
}
105+
export interface EntriesMenuParams {
106+
ids: string[];
107+
}
108+
export interface EntriesMenuResponse {
109+
action: ActionResponse;
110+
}
92111
/**
93112
* Generated from @see "../messages/getRanges.request.json"
94113
*/

0 commit comments

Comments
 (0)