Merge pull request 'Open Web Push digests in the mobile Updates inbox' (#648) from timmy/647-push-updates-inbox into main
This commit is contained in:
commit
75da76c6f5
|
|
@ -927,6 +927,7 @@
|
||||||
else if (item.kind === 'issue') openIssueSheet(item, issueTrigger);
|
else if (item.kind === 'issue') openIssueSheet(item, issueTrigger);
|
||||||
else if (item.kind === 'pull') openPullSheet(item, pullTrigger);
|
else if (item.kind === 'pull') openPullSheet(item, pullTrigger);
|
||||||
},
|
},
|
||||||
|
onQueue: openWorkQueueRoute,
|
||||||
onClose: () => {
|
onClose: () => {
|
||||||
if (selectedIssue) closeIssueSheet(false);
|
if (selectedIssue) closeIssueSheet(false);
|
||||||
if (selectedPull) closePullSheet(false);
|
if (selectedPull) closePullSheet(false);
|
||||||
|
|
@ -5521,25 +5522,41 @@
|
||||||
document.querySelectorAll('[data-work-filter]').forEach(button => {
|
document.querySelectorAll('[data-work-filter]').forEach(button => {
|
||||||
button.setAttribute('aria-pressed', String(button.dataset.workFilter === selectedWorkFilter));
|
button.setAttribute('aria-pressed', String(button.dataset.workFilter === selectedWorkFilter));
|
||||||
button.addEventListener('click', () => {
|
button.addEventListener('click', () => {
|
||||||
const leavingUpdates = selectedWorkFilter === 'update' && button.dataset.workFilter !== 'update';
|
selectWorkQueue(button.dataset.workFilter);
|
||||||
selectedWorkFilter = button.dataset.workFilter;
|
|
||||||
if (leavingUpdates && notificationSelection.snapshot().active) notificationSelection.cancel();
|
|
||||||
savedWorkFilter = selectedWorkFilter;
|
|
||||||
launchFilterResolved = true;
|
|
||||||
try {
|
|
||||||
sessionStorage.setItem(WORK_FILTER_KEY, selectedWorkFilter);
|
|
||||||
} catch (e) {
|
|
||||||
console.warn('Could not persist My Work filter', e);
|
|
||||||
}
|
|
||||||
document.querySelectorAll('[data-work-filter]').forEach(item =>
|
|
||||||
item.setAttribute('aria-pressed', String(item === button))
|
|
||||||
);
|
|
||||||
const selectedCount = button.querySelector('[data-work-count]').textContent;
|
|
||||||
qs('#active-work-queue').textContent = button.firstChild.textContent.trim() + ' (' + selectedCount + ')';
|
|
||||||
renderMyWork();
|
|
||||||
updateWorkPaginationControls();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
function selectWorkQueue(filter, { preserveRoute = false } = {}) {
|
||||||
|
const button = qs('[data-work-filter="' + filter + '"]');
|
||||||
|
if (!button) return false;
|
||||||
|
const leavingUpdates = selectedWorkFilter === 'update' && filter !== 'update';
|
||||||
|
selectedWorkFilter = filter;
|
||||||
|
if (leavingUpdates && notificationSelection.snapshot().active) notificationSelection.cancel();
|
||||||
|
savedWorkFilter = selectedWorkFilter;
|
||||||
|
launchFilterResolved = true;
|
||||||
|
try {
|
||||||
|
sessionStorage.setItem(WORK_FILTER_KEY, selectedWorkFilter);
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('Could not persist My Work filter', e);
|
||||||
|
}
|
||||||
|
document.querySelectorAll('[data-work-filter]').forEach(item =>
|
||||||
|
item.setAttribute('aria-pressed', String(item === button))
|
||||||
|
);
|
||||||
|
const selectedCount = button.querySelector('[data-work-count]').textContent;
|
||||||
|
qs('#active-work-queue').textContent = button.firstChild.textContent.trim() + ' (' + selectedCount + ')';
|
||||||
|
renderMyWork();
|
||||||
|
updateWorkPaginationControls();
|
||||||
|
if (!preserveRoute && filter === 'update' && window.location.hash !== '#/my-work/updates') {
|
||||||
|
window.history.pushState({ workQueue:'update' }, '', '#/my-work/updates');
|
||||||
|
} else if (!preserveRoute && filter !== 'update' && window.location.hash === '#/my-work/updates') {
|
||||||
|
window.history.replaceState(null, '', window.location.pathname + window.location.search);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
function openWorkQueueRoute(filter) {
|
||||||
|
if (!selectWorkQueue(filter, { preserveRoute:true })) return;
|
||||||
|
qs('#my-work').scrollIntoView({block:'start'});
|
||||||
|
qs('#my-work').focus();
|
||||||
|
}
|
||||||
function openDeliveryReceiptRoute() {
|
function openDeliveryReceiptRoute() {
|
||||||
if (window.location.hash !== '#/my-work/drafts') return;
|
if (window.location.hash !== '#/my-work/drafts') return;
|
||||||
qs('[data-work-filter="draft"]').click();
|
qs('[data-work-filter="draft"]').click();
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,9 @@
|
||||||
function parse(fragment) {
|
function parse(fragment) {
|
||||||
const parts = String(fragment || '').split('/');
|
const parts = String(fragment || '').split('/');
|
||||||
if (parts[0] !== '#' || parts[1] !== 'my-work') return null;
|
if (parts[0] !== '#' || parts[1] !== 'my-work') return null;
|
||||||
|
if (parts[2] === 'updates' && parts.length === 3) {
|
||||||
|
return { kind: 'queue', filter: 'update' };
|
||||||
|
}
|
||||||
if (parts[2] === 'update' && parts.length === 4) {
|
if (parts[2] === 'update' && parts.length === 4) {
|
||||||
const notificationId = positiveInteger(parts[3]);
|
const notificationId = positiveInteger(parts[3]);
|
||||||
return notificationId ? { kind: 'update', notification_id: notificationId } : null;
|
return notificationId ? { kind: 'update', notification_id: notificationId } : null;
|
||||||
|
|
@ -38,6 +41,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
function sameRoute(item, route) {
|
function sameRoute(item, route) {
|
||||||
|
if (route.kind === 'queue') return false;
|
||||||
if (route.kind === 'update') {
|
if (route.kind === 'update') {
|
||||||
return Number(item.notification_id) === route.notification_id;
|
return Number(item.notification_id) === route.notification_id;
|
||||||
}
|
}
|
||||||
|
|
@ -48,6 +52,7 @@
|
||||||
|
|
||||||
function createController({
|
function createController({
|
||||||
location, history, eventTarget, onOpen, onClose, onInvalid,
|
location, history, eventTarget, onOpen, onClose, onInvalid,
|
||||||
|
onQueue = function () {},
|
||||||
resolve, onResolving = function () {}, onError = function () {},
|
resolve, onResolving = function () {}, onError = function () {},
|
||||||
}) {
|
}) {
|
||||||
let items = [];
|
let items = [];
|
||||||
|
|
@ -100,6 +105,16 @@
|
||||||
active = '';
|
active = '';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (route.kind === 'queue') {
|
||||||
|
if (!ready) return;
|
||||||
|
resolution += 1;
|
||||||
|
resolving = '';
|
||||||
|
if (active && active !== fragment) onClose();
|
||||||
|
if (active === fragment) return;
|
||||||
|
active = fragment;
|
||||||
|
onQueue(route.filter);
|
||||||
|
return;
|
||||||
|
}
|
||||||
const item = items.find(candidate => sameRoute(candidate, route));
|
const item = items.find(candidate => sameRoute(candidate, route));
|
||||||
if (!item) {
|
if (!item) {
|
||||||
if (ready) resolveMissing(fragment, route);
|
if (ready) resolveMissing(fragment, route);
|
||||||
|
|
|
||||||
|
|
@ -395,6 +395,49 @@ process.stdout.write(JSON.stringify({{calls, hash:location.hash}}));
|
||||||
assert output["hash"] == "#/my-work/review/stackchain/dashboard/10"
|
assert output["hash"] == "#/my-work/review/stackchain/dashboard/10"
|
||||||
|
|
||||||
|
|
||||||
|
def test_updates_inbox_route_survives_hydration_and_detail_back_navigation():
|
||||||
|
script = f"""
|
||||||
|
const routes = require({json.dumps(str(WORK_ROUTE))});
|
||||||
|
const listeners = {{}};
|
||||||
|
const location = {{hash:'#/my-work/updates'}};
|
||||||
|
const calls = [];
|
||||||
|
const stack = ['#/my-work/updates'];
|
||||||
|
let cursor = 0;
|
||||||
|
const history = {{
|
||||||
|
pushState(state, _, hash) {{ stack.splice(cursor + 1); stack.push(hash); cursor += 1; location.hash = hash; }},
|
||||||
|
replaceState(state, _, hash) {{ stack[cursor] = hash; location.hash = hash; }},
|
||||||
|
back() {{ cursor -= 1; location.hash = stack[cursor]; listeners.popstate(); }},
|
||||||
|
}};
|
||||||
|
const controller = routes.createController({{
|
||||||
|
location, history,
|
||||||
|
eventTarget: {{addEventListener(name, fn) {{ listeners[name] = fn; }}}},
|
||||||
|
onQueue: queue => calls.push(['queue', queue]),
|
||||||
|
onOpen: item => calls.push(['open', item.notification_id]),
|
||||||
|
onClose: () => calls.push(['close']),
|
||||||
|
onInvalid: () => calls.push(['invalid']),
|
||||||
|
}});
|
||||||
|
controller.start();
|
||||||
|
controller.setItems([]);
|
||||||
|
controller.open({{kind:'update', notification_id:42}});
|
||||||
|
controller.close();
|
||||||
|
process.stdout.write(JSON.stringify({{calls, hash:location.hash, parsed:routes.parse(location.hash)}}));
|
||||||
|
"""
|
||||||
|
result = subprocess.run(
|
||||||
|
["node", "-e", script], check=True, capture_output=True, text=True
|
||||||
|
)
|
||||||
|
|
||||||
|
assert json.loads(result.stdout) == {
|
||||||
|
"calls": [
|
||||||
|
["queue", "update"],
|
||||||
|
["open", 42],
|
||||||
|
["close"],
|
||||||
|
["queue", "update"],
|
||||||
|
],
|
||||||
|
"hash": "#/my-work/updates",
|
||||||
|
"parsed": {"kind": "queue", "filter": "update"},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def test_work_route_controller_resolves_cold_routes_without_erasing_the_fragment():
|
def test_work_route_controller_resolves_cold_routes_without_erasing_the_fragment():
|
||||||
script = f"""
|
script = f"""
|
||||||
const routes = require({json.dumps(str(WORK_ROUTE))});
|
const routes = require({json.dumps(str(WORK_ROUTE))});
|
||||||
|
|
@ -507,6 +550,10 @@ async def test_dashboard_wires_addressable_work_sheets_back_navigation_and_share
|
||||||
assert html.count('class="share-work-route"') == 4
|
assert html.count('class="share-work-route"') == 4
|
||||||
assert 'createWorkRoute.share(window.location.href, navigator, navigator.clipboard)' in html
|
assert 'createWorkRoute.share(window.location.href, navigator, navigator.clipboard)' in html
|
||||||
assert 'Route unavailable · this item is no longer in My Work.' in html
|
assert 'Route unavailable · this item is no longer in My Work.' in html
|
||||||
|
assert "onQueue: openWorkQueueRoute" in html
|
||||||
|
assert "selectWorkQueue(filter, { preserveRoute:true })" in html
|
||||||
|
assert "filter === 'update' && window.location.hash !== '#/my-work/updates'" in html
|
||||||
|
assert "window.history.pushState({ workQueue:'update' }, '', '#/my-work/updates')" in html
|
||||||
|
|
||||||
|
|
||||||
def test_my_work_queue_prioritizes_labels_then_reviews_and_keeps_repo_identity():
|
def test_my_work_queue_prioritizes_labels_then_reviews_and_keeps_repo_identity():
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user