Replay recent notification history
This commit is contained in:
@@ -91,17 +91,6 @@ BarWidget {
|
||||
}
|
||||
}
|
||||
|
||||
// Service-side IPC (omarchy-shell notifications showHistory) flips
|
||||
// historyOpenRequested; we toggle our local popup state from here so the
|
||||
// keybind path lands in the same PopupCard the click path uses.
|
||||
Connections {
|
||||
target: root.notificationService
|
||||
ignoreUnknownSignals: true
|
||||
function onHistoryOpenRequested() {
|
||||
root.popupOpen = true
|
||||
}
|
||||
}
|
||||
|
||||
PopupCard {
|
||||
id: popup
|
||||
anchorItem: button
|
||||
|
||||
@@ -143,6 +143,37 @@ function parseHistory(raw, normalUrgency, historyCap) {
|
||||
}
|
||||
}
|
||||
|
||||
function recentHistoryRows(pending, past, limit, normalUrgency) {
|
||||
var max = limit === undefined || limit === null ? 5 : Number(limit)
|
||||
if (isNaN(max)) max = 5
|
||||
max = Math.max(0, max)
|
||||
|
||||
var values = []
|
||||
function collect(rows) {
|
||||
var source = Array.isArray(rows) ? rows : []
|
||||
for (var i = 0; i < source.length; i++) {
|
||||
if (source[i]) values.push(source[i])
|
||||
}
|
||||
}
|
||||
collect(pending)
|
||||
collect(past)
|
||||
|
||||
var keep = {}
|
||||
for (var j = 0; j < values.length; j++) {
|
||||
var row = values[j]
|
||||
var key = row.originalId
|
||||
if (key === undefined || key === null) key = row.id
|
||||
if (key === undefined || key === null) key = "_" + j
|
||||
var prior = keep[key]
|
||||
if (!prior || (row.timestamp || 0) >= (prior.timestamp || 0)) keep[key] = row
|
||||
}
|
||||
|
||||
var out = []
|
||||
for (var id in keep) out.push(historyEntry(keep[id], normalUrgency))
|
||||
out.sort(function(a, b) { return (b.timestamp || 0) - (a.timestamp || 0) })
|
||||
return out.slice(0, max)
|
||||
}
|
||||
|
||||
function dumpRows(rows) {
|
||||
var values = Array.isArray(rows) ? rows : []
|
||||
var out = []
|
||||
@@ -205,6 +236,7 @@ if (typeof module !== "undefined") {
|
||||
historyEntry: historyEntry,
|
||||
dedupeByOriginalId: dedupeByOriginalId,
|
||||
parseHistory: parseHistory,
|
||||
recentHistoryRows: recentHistoryRows,
|
||||
dumpRows: dumpRows,
|
||||
popupPlacement: popupPlacement,
|
||||
imageExtension: imageExtension
|
||||
|
||||
@@ -43,10 +43,6 @@ Item {
|
||||
readonly property int liveBarSize: shell && shell.bar && !shell.bar.barHidden ? Math.max(0, shell.bar.barSize) : defaultBarSize
|
||||
readonly property int barClearance: liveBarSize + Style.gapsOut
|
||||
|
||||
// Fired by IPC (`omarchy-shell notifications showHistory`) so the
|
||||
// bar widget can drop its PopupCard from the same anchor a click would.
|
||||
signal historyOpenRequested()
|
||||
|
||||
// PersistentProperties handles in-process QML reloads. The on-disk
|
||||
// notifications.json file is the cross-restart backstop — its `dnd` key
|
||||
// is hydrated into persisted.doNotDisturb on startup and written back via
|
||||
@@ -91,6 +87,7 @@ Item {
|
||||
ListModel { id: pastModel }
|
||||
|
||||
readonly property int historyCap: 100
|
||||
readonly property int historyReplayLimit: 5
|
||||
property var imageCacheQueue: []
|
||||
|
||||
readonly property int lowPopupDuration: 5000
|
||||
@@ -302,6 +299,31 @@ Item {
|
||||
while (popupModel.count > 0) dismissPopup(0)
|
||||
}
|
||||
|
||||
function rowsFromModel(model) {
|
||||
var rows = []
|
||||
for (var i = 0; i < model.count; i++) {
|
||||
var entry = model.get(i)
|
||||
if (entry) rows.push(snapshotFromRow(entry))
|
||||
}
|
||||
return rows
|
||||
}
|
||||
|
||||
function showRecentHistory() {
|
||||
var rows = NotificationLogic.recentHistoryRows(
|
||||
rowsFromModel(pendingModel),
|
||||
rowsFromModel(pastModel),
|
||||
service.historyReplayLimit,
|
||||
NotificationUrgency.Normal)
|
||||
|
||||
if (rows.length === 0) return "none"
|
||||
|
||||
clearPopups()
|
||||
for (var i = 0; i < rows.length; i++) {
|
||||
popupModel.append(rows[i])
|
||||
}
|
||||
return "ok"
|
||||
}
|
||||
|
||||
function dismissPending(index) {
|
||||
if (index < 0 || index >= pendingModel.count) return
|
||||
var entry = pendingModel.get(index)
|
||||
@@ -629,8 +651,7 @@ Item {
|
||||
}
|
||||
|
||||
function showHistory(): string {
|
||||
service.historyOpenRequested()
|
||||
return "ok"
|
||||
return service.showRecentHistory()
|
||||
}
|
||||
|
||||
// `clear` empties the past tab (the "I already saw these" bucket).
|
||||
|
||||
@@ -5,6 +5,7 @@ set -euo pipefail
|
||||
source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh"
|
||||
|
||||
run_node_test <<'JS'
|
||||
const fs = require('fs')
|
||||
const notifications = requireFromRoot('shell/plugins/notifications/NotificationLogic.js')
|
||||
|
||||
assert(notifications.isChromiumDerived('Brave Browser', ''), 'notifications detect chromium-derived apps by name')
|
||||
@@ -159,7 +160,44 @@ assertDeepEqual(
|
||||
)
|
||||
assert(notifications.parseHistory('{', 1, 100).error, 'notifications flag invalid history JSON')
|
||||
|
||||
const recentRows = notifications.recentHistoryRows(
|
||||
[
|
||||
{ id: 1, originalId: 10, summary: 'pending-old', timestamp: 100 },
|
||||
{ id: 2, originalId: 11, summary: 'pending-new', timestamp: 700 },
|
||||
{ id: 3, originalId: 12, summary: 'pending-mid', timestamp: 300 }
|
||||
],
|
||||
[
|
||||
{ id: 4, originalId: 13, summary: 'past-newest', timestamp: 900 },
|
||||
{ id: 5, originalId: 14, summary: 'past-second', timestamp: 800 },
|
||||
{ id: 6, originalId: 10, summary: 'past-replaced', timestamp: 200 },
|
||||
{ id: 7, originalId: 15, summary: 'past-extra', timestamp: 50 }
|
||||
],
|
||||
5,
|
||||
1
|
||||
)
|
||||
assertDeepEqual(
|
||||
recentRows.map(row => row.summary),
|
||||
['past-newest', 'past-second', 'pending-new', 'pending-mid', 'past-replaced'],
|
||||
'notifications pick the last five history rows across pending and past'
|
||||
)
|
||||
assertEqual(recentRows.length, 5, 'notifications history replay is capped at five rows')
|
||||
|
||||
assertEqual(notifications.imageExtension('/tmp/screenshot.PNG'), 'png', 'notifications normalize image extensions')
|
||||
assertEqual(notifications.imageExtension('/tmp/no-extension'), 'png', 'notifications default missing image extension')
|
||||
assertEqual(notifications.imageExtension('/tmp/archive.reallylong'), 'png', 'notifications reject suspicious image extensions')
|
||||
|
||||
const serviceQml = fs.readFileSync(path.join(root, 'shell/plugins/notifications/Service.qml'), 'utf8')
|
||||
const barWidgetQml = fs.readFileSync(path.join(root, 'shell/plugins/notifications/BarWidget.qml'), 'utf8')
|
||||
assert(
|
||||
/readonly property int historyReplayLimit: 5/.test(serviceQml),
|
||||
'notifications service limits history replay to five rows'
|
||||
)
|
||||
assert(
|
||||
/function showHistory\(\): string \{\s*return service\.showRecentHistory\(\)\s*\}/.test(serviceQml),
|
||||
'notifications history IPC replays recent notifications'
|
||||
)
|
||||
assert(
|
||||
!barWidgetQml.includes('historyOpenRequested'),
|
||||
'notifications history IPC does not depend on the bar widget'
|
||||
)
|
||||
JS
|
||||
|
||||
Reference in New Issue
Block a user