Skip to content

Commit

Permalink
history: support single row context menu (#1482)
Browse files Browse the repository at this point in the history
* history: support single row context menu

* docs
  • Loading branch information
shakyShane authored Feb 11, 2025
1 parent 6bc691c commit a07216a
Show file tree
Hide file tree
Showing 11 changed files with 163 additions and 8 deletions.
14 changes: 11 additions & 3 deletions special-pages/pages/history/app/HistoryProvider.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,9 @@ export function HistoryServiceProvider({ service, initial, children }) {
if (btn?.dataset.rowMenu) {
event.stopImmediatePropagation();
event.preventDefault();
return confirm(`todo: row menu for ${btn.dataset.rowMenu}`);
// eslint-disable-next-line promise/prefer-await-to-then
service.entriesMenu([btn.value], [Number(btn.dataset.index)]).catch(console.error);
return;
}
if (btn?.dataset.deleteRange) {
event.stopImmediatePropagation();
Expand Down Expand Up @@ -120,6 +122,7 @@ export function HistoryServiceProvider({ service, initial, children }) {

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

for (const [selector, valueFn] of Object.entries(actions)) {
Expand All @@ -129,8 +132,13 @@ export function HistoryServiceProvider({ service, initial, children }) {
if (value) {
event.preventDefault();
event.stopImmediatePropagation();
// eslint-disable-next-line promise/prefer-await-to-then
service.menuTitle(value).catch(console.error);
if (match.dataset.sectionTitle) {
// eslint-disable-next-line promise/prefer-await-to-then
service.menuTitle(value).catch(console.error);
} else if (match.dataset.historyEntry) {
// eslint-disable-next-line promise/prefer-await-to-then
service.entriesMenu([value], [Number(match.dataset.index)]).catch(console.error);
}
}
break;
}
Expand Down
7 changes: 4 additions & 3 deletions special-pages/pages/history/app/components/Item.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,9 @@ export const Item = memo(
* @param {number} props.kind - The kind or type of the item that determines its visual style.
* @param {string} props.dateRelativeDay - The relative day information to display (shown when kind is equal to TITLE_KIND).
* @param {string} props.dateTimeOfDay - the time of day, like 11.00am.
* @param {number} props.index - original index
*/
function Item({ id, url, domain, title, kind, dateRelativeDay, dateTimeOfDay }) {
function Item({ id, url, domain, title, kind, dateRelativeDay, dateTimeOfDay, index }) {
const { t } = useTypedTranslation();
return (
<Fragment>
Expand All @@ -36,13 +37,13 @@ export const Item = memo(
</button>
</div>
)}
<div class={cn(styles.row, kind === END_KIND && styles.last)} tabindex={0}>
<div class={cn(styles.row, kind === END_KIND && styles.last)} tabindex={0} data-history-entry={id}>
<a href={url} data-url={url} class={styles.entryLink}>
{title}
</a>
<span class={styles.domain}>{domain}</span>
<span class={styles.time}>{dateTimeOfDay}</span>
<button class={styles.dots} data-row-menu={id}>
<button class={styles.dots} data-row-menu data-index={index} value={id}>
<Dots />
</button>
</div>
Expand Down
1 change: 1 addition & 0 deletions special-pages/pages/history/app/components/Results.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export function Results({ results }) {
title={item.title}
dateRelativeDay={item.dateRelativeDay}
dateTimeOfDay={item.dateTimeOfDay}
index={index}
/>
</li>
);
Expand Down
30 changes: 30 additions & 0 deletions special-pages/pages/history/app/history.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,36 @@ Sent when a right-click is issued on a section title (or when the three-dots but
}
```

### `entries_menu`
{@link "History Messages".EntriesMenuRequest}

Sent when a right-click is issued on a section title (or when the three-dots button is clicked)

**Types:**
- Parameters: {@link "History Messages".EntriesMenuParams}
- Response: {@link "History Messages".EntriesMenuResponse}

**params**
```json
{
"ids": ["abc", "def"]
}
```

**response, if deleted**
```json
{
"action": "delete"
}
```

**response, otherwise**
```json
{
"action": "none"
}
```

## Notifications

### `open`
Expand Down
26 changes: 25 additions & 1 deletion special-pages/pages/history/app/history.service.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export class HistoryService {
},
}).withUpdater((old, next, trigger) => {
if (trigger === 'manual') {
// console.log('manual trigger, always accepting next:', next);
console.log('manual trigger, always accepting next:', next);
return next;
}
if (eq(old.info.query, next.info.query)) {
Expand Down Expand Up @@ -102,6 +102,30 @@ export class HistoryService {
return this.ranges.onData(({ data, source }) => cb(data));
}

/**
* @param {string[]} ids
* @param {number[]} indexes
*/
async entriesMenu(ids, indexes) {
const response = await this.history.messaging.request('entries_menu', { ids });
if (response.action === 'none') return;
if (response.action !== 'delete') return;
this.query.update((old) => {
const inverted = indexes.sort((a, b) => b - a);
const removed = [];
const next = old.results.slice();

// remove items in reverse that that splice works multiple times
for (let i = 0; i < inverted.length; i++) {
removed.push(next.splice(inverted[i], 1));
}

/** @type {QueryData} */
const nextStats = { ...old, results: next };
return nextStats;
});
}

/**
* @param {string} dateRelativeDay
*/
Expand Down
12 changes: 12 additions & 0 deletions special-pages/pages/history/app/mocks/mock-transport.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,18 @@ export function mockTransport() {
const msg = /** @type {any} */ (_msg);

switch (msg.method) {
case 'entries_menu': {
console.log('📤 [entries_menu]: ', JSON.stringify(msg.params));
// prettier-ignore
const lines = [
`entries_menu: ${JSON.stringify(msg.params)}`,
`To simulate deleting these items, press confirm`
].join('\n');
if (confirm(lines)) {
return Promise.resolve({ action: 'delete' });
}
return Promise.resolve({ action: 'none' });
}
case 'title_menu': {
console.log('📤 [deleteRange]: ', JSON.stringify(msg.params));
// prettier-ignore
Expand Down
26 changes: 26 additions & 0 deletions special-pages/pages/history/integration-tests/history.page.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { perPlatform } from 'injected/integration-test/type-helpers.mjs';
import { Mocks } from '../../../shared/mocks.js';
import { expect } from '@playwright/test';
import { generateSampleData } from '../app/mocks/history.mocks.js';

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

/**
* @param {import('../types/history.ts').DeleteRangeResponse} resp
*/
async deletesFromHistoryEntry(resp) {
const { page } = this;

// Handle dialog interaction based on response action
if (resp.action === 'delete') {
page.on('dialog', (dialog) => {
return dialog.accept();
});
} else {
page.on('dialog', (dialog) => dialog.dismiss());
}
// console.log(data[0].title);
const data = generateSampleData({ count: this.entries, offset: 0 });
const first = data[0];
const row = page.getByText(first.title);
await row.hover();
await page.locator(`[data-row-menu][value=${data[0].id}]`).click();

const calls = await this.mocks.waitForCallCount({ method: 'entries_menu', count: 1 });
expect(calls[0].payload.params).toStrictEqual({ ids: [data[0].id] });
}

async rightClicksSectionTitle() {
const { page } = this;
const title = page.getByRole('list').getByText('Today');
Expand Down
5 changes: 5 additions & 0 deletions special-pages/pages/history/integration-tests/history.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -118,4 +118,9 @@ test.describe('history', () => {
await hp.openPage({});
await hp.rightClicksSectionTitle();
});
test('3 dots menu on history entry', async ({ page }, workerInfo) => {
const hp = HistoryTestPage.create(page, workerInfo).withEntries(2000);
await hp.openPage({});
await hp.deletesFromHistoryEntry({ action: 'delete' });
});
});
17 changes: 17 additions & 0 deletions special-pages/pages/history/messages/entries_menu.request.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"title": "Entries Menu Params",
"required": [
"ids"
],
"properties": {
"ids": {
"type": "array",
"items": {
"type": "string"
}
}
}
}

11 changes: 11 additions & 0 deletions special-pages/pages/history/messages/entries_menu.response.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"required": ["action"],
"properties": {
"action": {
"$ref": "types/action-response.json"
}
}
}

22 changes: 21 additions & 1 deletion special-pages/pages/history/types/history.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,13 @@ export type RelativeDay = string;
*/
export interface HistoryMessages {
notifications: OpenNotification | ReportInitExceptionNotification | ReportPageExceptionNotification;
requests: DeleteRangeRequest | GetRangesRequest | InitialSetupRequest | QueryRequest | TitleMenuRequest;
requests:
| DeleteRangeRequest
| EntriesMenuRequest
| GetRangesRequest
| InitialSetupRequest
| QueryRequest
| TitleMenuRequest;
}
/**
* Generated from @see "../messages/open.notify.json"
Expand Down Expand Up @@ -89,6 +95,20 @@ export interface DeleteRangeParams {
export interface DeleteRangeResponse {
action: ActionResponse;
}
/**
* Generated from @see "../messages/entries_menu.request.json"
*/
export interface EntriesMenuRequest {
method: "entries_menu";
params: EntriesMenuParams;
result: EntriesMenuResponse;
}
export interface EntriesMenuParams {
ids: string[];
}
export interface EntriesMenuResponse {
action: ActionResponse;
}
/**
* Generated from @see "../messages/getRanges.request.json"
*/
Expand Down

0 comments on commit a07216a

Please sign in to comment.