@12-apps/notifications 4.9.0 → 4.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ADOPTING.md +60 -1
- package/README.md +73 -0
- package/dist/{chunk-BGI4BQ66.js → chunk-6W7INOYQ.js} +1 -1
- package/dist/chunk-6W7INOYQ.js.map +1 -0
- package/dist/{chunk-M5XTROYM.js → chunk-I5QUMTCN.js} +141 -17
- package/dist/chunk-I5QUMTCN.js.map +1 -0
- package/dist/chunk-RTURLH5U.js +33 -0
- package/dist/chunk-RTURLH5U.js.map +1 -0
- package/dist/{chunk-GGAZOLNN.js → chunk-SWOWHIFE.js} +2 -2
- package/dist/{chunk-KZG7QGFA.js → chunk-WVRODNXQ.js} +8 -4
- package/dist/chunk-WVRODNXQ.js.map +1 -0
- package/dist/{chunk-KU4FVOFA.js → chunk-ZIR3ILFH.js} +2 -2
- package/dist/chunk-ZY32PC34.js +231 -0
- package/dist/chunk-ZY32PC34.js.map +1 -0
- package/dist/{create-api-notifications-B-VMezVB.d.ts → create-api-notifications-CcPYrM3p.d.ts} +4 -4
- package/dist/{create-web-notifications-B-xQ6R0B.d.ts → create-web-notifications-DV3Y8k7e.d.ts} +89 -4
- package/dist/{generators-BsuXe8Mp.d.ts → generators-qAD4fNPq.d.ts} +1 -1
- package/dist/hono/index.d.ts +5 -5
- package/dist/hono/index.js +4 -3
- package/dist/hono/index.js.map +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.js +12 -2
- package/dist/{jobs-D64B_bg7.d.ts → jobs--fex87-q.d.ts} +1 -1
- package/dist/live-DYxEFO49.d.ts +158 -0
- package/dist/manifest/server.d.ts +6 -6
- package/dist/manifest/server.js +5 -4
- package/dist/manifest/server.js.map +1 -1
- package/dist/manifest/web.d.ts +4 -3
- package/dist/manifest/web.js +1 -1
- package/dist/{panel-UFXNO4AF.js → panel-OPB3DBLJ.js} +116 -61
- package/dist/panel-OPB3DBLJ.js.map +1 -0
- package/dist/{preferences-screen-V3ZBWP7A.js → preferences-screen-SXUIGECY.js} +2 -2
- package/dist/react/index.d.ts +119 -7
- package/dist/react/index.js +8 -2
- package/dist/server/index.d.ts +7 -7
- package/dist/server/index.js +5 -4
- package/dist/{types-BFmAY0P5.d.ts → types-BlqZkCWZ.d.ts} +10 -1
- package/dist/web-push/index.d.ts +2 -2
- package/dist/{web-push-D-ysQd5h.d.ts → web-push-Dnyaha2z.d.ts} +21 -1
- package/dist/{wire-_coV3tJk.d.ts → wire-BG1kuoXX.d.ts} +1 -1
- package/package.json +2 -2
- package/src/index.ts +10 -0
- package/src/live.ts +179 -0
- package/src/react/bell-button.tsx +112 -16
- package/src/react/create-web-notifications.tsx +40 -5
- package/src/react/index.ts +29 -0
- package/src/react/live-card.tsx +279 -0
- package/src/react/live-config.ts +79 -0
- package/src/react/live-section.tsx +179 -0
- package/src/react/live-seen.ts +138 -0
- package/src/react/panel-lazy.tsx +6 -0
- package/src/react/panel.tsx +144 -34
- package/src/react/relative-time.ts +14 -2
- package/src/server/transports/web-push.ts +22 -0
- package/src/types.ts +10 -1
- package/dist/chunk-BGI4BQ66.js.map +0 -1
- package/dist/chunk-KZG7QGFA.js.map +0 -1
- package/dist/chunk-M5XTROYM.js.map +0 -1
- package/dist/chunk-O5BVUXPO.js +0 -22
- package/dist/chunk-O5BVUXPO.js.map +0 -1
- package/dist/panel-UFXNO4AF.js.map +0 -1
- /package/dist/{chunk-GGAZOLNN.js.map → chunk-SWOWHIFE.js.map} +0 -0
- /package/dist/{chunk-KU4FVOFA.js.map → chunk-ZIR3ILFH.js.map} +0 -0
- /package/dist/{preferences-screen-V3ZBWP7A.js.map → preferences-screen-SXUIGECY.js.map} +0 -0
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
|
+
LiveSection,
|
|
2
3
|
relativeTime
|
|
3
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-ZY32PC34.js";
|
|
4
5
|
import {
|
|
5
6
|
BellIcon,
|
|
6
7
|
useInboxList
|
|
7
8
|
} from "./chunk-BW723CX2.js";
|
|
9
|
+
import "./chunk-RTURLH5U.js";
|
|
8
10
|
import {
|
|
9
11
|
__name
|
|
10
12
|
} from "./chunk-7QVYU63E.js";
|
|
@@ -105,14 +107,15 @@ function NotificationRow({
|
|
|
105
107
|
__name(NotificationRow, "NotificationRow");
|
|
106
108
|
|
|
107
109
|
// src/react/panel.tsx
|
|
108
|
-
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
110
|
+
import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
109
111
|
function PanelBody({
|
|
110
112
|
state,
|
|
111
113
|
messages,
|
|
112
114
|
onRetry,
|
|
113
115
|
onLoadMore,
|
|
114
116
|
onOpen,
|
|
115
|
-
onDelete
|
|
117
|
+
onDelete,
|
|
118
|
+
hasLive
|
|
116
119
|
}) {
|
|
117
120
|
if (state.status === "pending" || state.status === "idle") {
|
|
118
121
|
return /* @__PURE__ */ jsx2(
|
|
@@ -138,7 +141,7 @@ function PanelBody({
|
|
|
138
141
|
}
|
|
139
142
|
);
|
|
140
143
|
}
|
|
141
|
-
if (state.items.length === 0) {
|
|
144
|
+
if (state.items.length === 0 && !hasLive) {
|
|
142
145
|
return /* @__PURE__ */ jsx2(
|
|
143
146
|
EmptyState,
|
|
144
147
|
{
|
|
@@ -150,53 +153,118 @@ function PanelBody({
|
|
|
150
153
|
}
|
|
151
154
|
);
|
|
152
155
|
}
|
|
153
|
-
return
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
156
|
+
return (
|
|
157
|
+
// A stable anchor for the inbox half, present whether or not it has rows.
|
|
158
|
+
// The panel's claim is that what is HAPPENING sits above what has already
|
|
159
|
+
// happened, and until this existed the only thing below the live section to
|
|
160
|
+
// point at was the empty state — which is exactly what stops rendering when
|
|
161
|
+
// something is live.
|
|
162
|
+
/* @__PURE__ */ jsxs2(Box2, { "data-testid": "notifications-inbox", children: [
|
|
163
|
+
state.items.map((notification) => /* @__PURE__ */ jsx2(
|
|
164
|
+
NotificationRow,
|
|
165
|
+
{
|
|
166
|
+
notification,
|
|
167
|
+
messages,
|
|
168
|
+
onOpen,
|
|
169
|
+
onDelete
|
|
170
|
+
},
|
|
171
|
+
notification.id
|
|
172
|
+
)),
|
|
173
|
+
state.nextCursor ? /* @__PURE__ */ jsx2(Box2, { sx: { display: "flex", justifyContent: "center", py: 1.5 }, children: /* @__PURE__ */ jsx2(
|
|
174
|
+
Button2,
|
|
175
|
+
{
|
|
176
|
+
variant: "outline",
|
|
177
|
+
color: "neutral",
|
|
178
|
+
size: "sm",
|
|
179
|
+
disabled: state.loadingMore,
|
|
180
|
+
onClick: onLoadMore,
|
|
181
|
+
dataTestId: "notifications-load-more",
|
|
182
|
+
children: state.loadingMore ? messages.loadingMore : messages.loadMore
|
|
183
|
+
}
|
|
184
|
+
) }) : null
|
|
185
|
+
] })
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
__name(PanelBody, "PanelBody");
|
|
189
|
+
function usePanelOpeners(store, onClose, onNavigate) {
|
|
190
|
+
const follow = useCallback(
|
|
191
|
+
(link) => {
|
|
192
|
+
if (!link || !onNavigate) return;
|
|
193
|
+
onClose();
|
|
194
|
+
onNavigate(link);
|
|
195
|
+
},
|
|
196
|
+
[onClose, onNavigate]
|
|
197
|
+
);
|
|
198
|
+
return {
|
|
199
|
+
openNotification: useCallback(
|
|
200
|
+
(notification) => {
|
|
201
|
+
if (notification.readAt === null) store.markRead([notification.id]);
|
|
202
|
+
follow(notification.link);
|
|
161
203
|
},
|
|
162
|
-
|
|
163
|
-
)
|
|
164
|
-
|
|
204
|
+
[store, follow]
|
|
205
|
+
),
|
|
206
|
+
openLive: useCallback((activity) => follow(activity.link), [follow])
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
__name(usePanelOpeners, "usePanelOpeners");
|
|
210
|
+
function PanelInbox({
|
|
211
|
+
state,
|
|
212
|
+
messages,
|
|
213
|
+
store,
|
|
214
|
+
onOpen,
|
|
215
|
+
hasLive
|
|
216
|
+
}) {
|
|
217
|
+
const hasUnread = state.items.some((item) => item.readAt === null);
|
|
218
|
+
return /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
219
|
+
hasUnread ? /* @__PURE__ */ jsx2(Box2, { sx: { display: "flex", justifyContent: "flex-end", pb: 1 }, children: /* @__PURE__ */ jsx2(
|
|
165
220
|
Button2,
|
|
166
221
|
{
|
|
167
|
-
variant: "
|
|
168
|
-
color: "
|
|
169
|
-
size: "
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
222
|
+
variant: "ghost",
|
|
223
|
+
color: "primary",
|
|
224
|
+
size: "xs",
|
|
225
|
+
onClick: () => store.markAllRead(),
|
|
226
|
+
dataTestId: "notifications-mark-all-read",
|
|
227
|
+
children: messages.markAllRead
|
|
228
|
+
}
|
|
229
|
+
) }) : null,
|
|
230
|
+
/* @__PURE__ */ jsx2(
|
|
231
|
+
PanelBody,
|
|
232
|
+
{
|
|
233
|
+
state,
|
|
234
|
+
messages,
|
|
235
|
+
onRetry: () => store.invalidate(),
|
|
236
|
+
onLoadMore: () => store.loadMore(),
|
|
237
|
+
onOpen,
|
|
238
|
+
onDelete: (id) => store.remove(id),
|
|
239
|
+
hasLive
|
|
174
240
|
}
|
|
175
|
-
)
|
|
241
|
+
)
|
|
176
242
|
] });
|
|
177
243
|
}
|
|
178
|
-
__name(
|
|
244
|
+
__name(PanelInbox, "PanelInbox");
|
|
179
245
|
function NotificationsPanel({
|
|
180
246
|
open,
|
|
181
247
|
onClose,
|
|
182
248
|
onNavigate,
|
|
183
249
|
store,
|
|
184
|
-
messages
|
|
250
|
+
messages,
|
|
251
|
+
live,
|
|
252
|
+
liveSeen
|
|
185
253
|
}) {
|
|
186
254
|
const theme = useTheme();
|
|
187
255
|
const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
|
|
188
256
|
const state = useInboxList(store, open);
|
|
189
|
-
const openNotification =
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
257
|
+
const { openNotification, openLive } = usePanelOpeners(store, onClose, onNavigate);
|
|
258
|
+
const renderInbox = /* @__PURE__ */ __name((liveCount) => /* @__PURE__ */ jsx2(
|
|
259
|
+
PanelInbox,
|
|
260
|
+
{
|
|
261
|
+
state,
|
|
262
|
+
messages,
|
|
263
|
+
store,
|
|
264
|
+
onOpen: openNotification,
|
|
265
|
+
hasLive: liveCount > 0
|
|
266
|
+
}
|
|
267
|
+
), "renderInbox");
|
|
200
268
|
return /* @__PURE__ */ jsxs2(
|
|
201
269
|
Drawer,
|
|
202
270
|
{
|
|
@@ -208,30 +276,17 @@ function NotificationsPanel({
|
|
|
208
276
|
dataTestId: "notifications-panel",
|
|
209
277
|
children: [
|
|
210
278
|
/* @__PURE__ */ jsx2(DrawerHeader, { onClose, children: messages.panelTitle }),
|
|
211
|
-
/* @__PURE__ */
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
) }) : null,
|
|
223
|
-
/* @__PURE__ */ jsx2(
|
|
224
|
-
PanelBody,
|
|
225
|
-
{
|
|
226
|
-
state,
|
|
227
|
-
messages,
|
|
228
|
-
onRetry: () => store.invalidate(),
|
|
229
|
-
onLoadMore: () => store.loadMore(),
|
|
230
|
-
onOpen: openNotification,
|
|
231
|
-
onDelete: (id) => store.remove(id)
|
|
232
|
-
}
|
|
233
|
-
)
|
|
234
|
-
] })
|
|
279
|
+
/* @__PURE__ */ jsx2(DrawerContent, { children: live ? /* @__PURE__ */ jsx2(
|
|
280
|
+
LiveSection,
|
|
281
|
+
{
|
|
282
|
+
config: live,
|
|
283
|
+
messages,
|
|
284
|
+
active: open,
|
|
285
|
+
...onNavigate ? { onOpen: openLive } : {},
|
|
286
|
+
...liveSeen ? { seen: liveSeen } : {},
|
|
287
|
+
children: renderInbox
|
|
288
|
+
}
|
|
289
|
+
) : renderInbox(0) })
|
|
235
290
|
]
|
|
236
291
|
}
|
|
237
292
|
);
|
|
@@ -240,4 +295,4 @@ __name(NotificationsPanel, "NotificationsPanel");
|
|
|
240
295
|
export {
|
|
241
296
|
NotificationsPanel
|
|
242
297
|
};
|
|
243
|
-
//# sourceMappingURL=panel-
|
|
298
|
+
//# sourceMappingURL=panel-OPB3DBLJ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/react/panel.tsx","../src/react/row.tsx"],"sourcesContent":["/**\n * The notification-centre slide-over: newest-first list with unread styling,\n * per-item open (marks read + deep-links), soft delete, mark-all, empty /\n * loading / error states and a \"load more\" cursor pager.\n *\n * Rendering is app-agnostic — the host passes `onNavigate` (its router's\n * navigate) for deep links. Without one a link is simply not followed, which is\n * what lets the panel mount in a host that has no router at all.\n */\nimport { useCallback, type JSX } from 'react';\n\nimport { EmptyState } from '@12-apps/ui/data-display/EmptyState';\nimport { LoadingState } from '@12-apps/ui/data-display/LoadingState';\nimport { Button } from '@12-apps/ui/form/Button';\nimport { Drawer, DrawerContent, DrawerHeader } from '@12-apps/ui/layout/Drawer';\nimport { Box } from '@12-apps/ui/mui/Box';\nimport { useMediaQuery } from '@12-apps/ui/mui/useMediaQuery';\nimport { useTheme } from '@12-apps/ui/mui/styles';\n\nimport type { LiveActivity } from '../live';\nimport type { NotificationMessages } from '../messages';\nimport type { InboxNotification } from '../wire';\n\nimport { BellIcon } from './bell-icon';\nimport { useInboxList } from './hooks';\nimport type { InboxState, InboxStore } from './inbox-state';\nimport type { LiveActivitiesConfig } from './live-config';\nimport type { LiveSeenStore } from './live-seen';\nimport { LiveSection } from './live-section';\nimport { NotificationRow } from './row';\n\ninterface PanelBodyProps {\n state: InboxState;\n messages: NotificationMessages;\n onRetry: () => void;\n onLoadMore: () => void;\n onOpen: (notification: InboxNotification) => void;\n onDelete: (id: string) => void;\n /**\n * Whether the live section above has anything in it.\n *\n * The empty state is a CLAIM about the whole panel — \"nenhuma notificação\" —\n * and a live entry is a notification, so an inbox with no rows under a pinned\n * pedido is not empty. Without this the panel said both things at once.\n */\n hasLive: boolean;\n}\n\n/** The scrollable panel body: loading / error / empty / the list + pager. */\nfunction PanelBody({\n state,\n messages,\n onRetry,\n onLoadMore,\n onOpen,\n onDelete,\n hasLive,\n}: PanelBodyProps): JSX.Element {\n if (state.status === 'pending' || state.status === 'idle') {\n return (\n <LoadingState\n variant=\"spinner\"\n message={messages.loading}\n size=\"md\"\n dataTestId=\"notifications-loading\"\n />\n );\n }\n if (state.status === 'error') {\n return (\n <EmptyState\n variant=\"minimal\"\n title={messages.loadFailedTitle}\n description={messages.loadFailedBody}\n onRefresh={onRetry}\n refreshLabel={messages.retry}\n dataTestId=\"notifications-error\"\n />\n );\n }\n if (state.items.length === 0 && !hasLive) {\n return (\n <EmptyState\n variant=\"illustrated\"\n illustration={<BellIcon size={44} dim />}\n title={messages.emptyTitle}\n description={messages.emptyBody}\n dataTestId=\"notifications-empty\"\n />\n );\n }\n return (\n // A stable anchor for the inbox half, present whether or not it has rows.\n // The panel's claim is that what is HAPPENING sits above what has already\n // happened, and until this existed the only thing below the live section to\n // point at was the empty state — which is exactly what stops rendering when\n // something is live.\n <Box data-testid=\"notifications-inbox\">\n {state.items.map((notification) => (\n <NotificationRow\n key={notification.id}\n notification={notification}\n messages={messages}\n onOpen={onOpen}\n onDelete={onDelete}\n />\n ))}\n {state.nextCursor ? (\n <Box sx={{ display: 'flex', justifyContent: 'center', py: 1.5 }}>\n <Button\n variant=\"outline\"\n color=\"neutral\"\n size=\"sm\"\n disabled={state.loadingMore}\n onClick={onLoadMore}\n dataTestId=\"notifications-load-more\"\n >\n {state.loadingMore ? messages.loadingMore : messages.loadMore}\n </Button>\n </Box>\n ) : null}\n </Box>\n );\n}\n\n/**\n * The two open gestures, which are ONE deep-link path with a mark-read in front\n * of half of it.\n *\n * Lifted out of the component because both kinds of entry follow a link the\n * same way and a host with no router follows neither — one rule, stated once,\n * rather than the same three lines written twice.\n */\nfunction usePanelOpeners(\n store: InboxStore,\n onClose: () => void,\n onNavigate?: (link: string) => void,\n): {\n openNotification: (notification: InboxNotification) => void;\n openLive: (activity: LiveActivity) => void;\n} {\n const follow = useCallback(\n (link: string | null) => {\n if (!link || !onNavigate) return;\n onClose();\n onNavigate(link);\n },\n [onClose, onNavigate],\n );\n\n return {\n openNotification: useCallback(\n (notification: InboxNotification) => {\n if (notification.readAt === null) store.markRead([notification.id]);\n follow(notification.link);\n },\n [store, follow],\n ),\n openLive: useCallback((activity: LiveActivity) => follow(activity.link), [follow]),\n };\n}\n\nexport interface NotificationsPanelProps {\n open: boolean;\n onClose: () => void;\n /** Navigate to a notification's in-app link (the host's router). */\n onNavigate?: (link: string) => void;\n}\n\n/**\n * Everything BELOW the live section: the mark-all control and the list.\n *\n * Its own component rather than a block inside the panel because it is rendered\n * from two places — through `LiveSection`, and directly when the host turned\n * live activities off — and because the panel is at its line ceiling. \"Marcar\n * todas como lidas\" belongs to the INBOX and travels with it: a live entry has\n * nothing to mark.\n */\nfunction PanelInbox({\n state,\n messages,\n store,\n onOpen,\n hasLive,\n}: {\n state: InboxState;\n messages: NotificationMessages;\n store: InboxStore;\n onOpen: (notification: InboxNotification) => void;\n hasLive: boolean;\n}): JSX.Element {\n const hasUnread = state.items.some((item) => item.readAt === null);\n return (\n <>\n {hasUnread ? (\n <Box sx={{ display: 'flex', justifyContent: 'flex-end', pb: 1 }}>\n <Button\n variant=\"ghost\"\n color=\"primary\"\n size=\"xs\"\n onClick={() => store.markAllRead()}\n dataTestId=\"notifications-mark-all-read\"\n >\n {messages.markAllRead}\n </Button>\n </Box>\n ) : null}\n <PanelBody\n state={state}\n messages={messages}\n onRetry={() => store.invalidate()}\n onLoadMore={() => store.loadMore()}\n onOpen={onOpen}\n onDelete={(id) => store.remove(id)}\n hasLive={hasLive}\n />\n </>\n );\n}\n\nexport function NotificationsPanel({\n open,\n onClose,\n onNavigate,\n store,\n messages,\n live,\n liveSeen,\n}: NotificationsPanelProps & {\n store: InboxStore;\n messages: NotificationMessages;\n live?: LiveActivitiesConfig;\n liveSeen?: LiveSeenStore;\n}): JSX.Element {\n // `useTheme` from @mui/material/styles falls back to the DEFAULT theme when\n // no provider is mounted, where the callback form of `useMediaQuery` would\n // hand the callback a null theme and throw. A published component must render\n // in a host that has not wrapped it yet.\n const theme = useTheme();\n const isMobile = useMediaQuery(theme.breakpoints.down('sm'));\n const state = useInboxList(store, open);\n\n const { openNotification, openLive } = usePanelOpeners(store, onClose, onNavigate);\n\n // A function rather than an element so the live count can reach it: with a\n // live section it is called by `LiveSection` — the only place the host's hook\n // may be called — and without one it is called here with zero.\n const renderInbox = (liveCount: number): JSX.Element => (\n <PanelInbox\n state={state}\n messages={messages}\n store={store}\n onOpen={openNotification}\n hasLive={liveCount > 0}\n />\n );\n\n return (\n <Drawer\n open={open}\n onClose={onClose}\n anchor=\"right\"\n variant=\"right\"\n width={isMobile ? '100vw' : 400}\n dataTestId=\"notifications-panel\"\n >\n <DrawerHeader onClose={onClose}>{messages.panelTitle}</DrawerHeader>\n <DrawerContent>\n {/*\n Above the mark-all control as well as above the list, deliberately:\n \"marcar todas como lidas\" belongs to the INBOX, and a live entry has\n nothing to mark. A control between the two blocks would read as\n applying to both.\n */}\n {live ? (\n <LiveSection\n config={live}\n messages={messages}\n active={open}\n {...(onNavigate ? { onOpen: openLive } : {})}\n {...(liveSeen ? { seen: liveSeen } : {})}\n >\n {renderInbox}\n </LiveSection>\n ) : (\n renderInbox(0)\n )}\n </DrawerContent>\n </Drawer>\n );\n}\n","/** One inbox row: unread accent, content (opens/marks read), timestamp, delete. */\nimport type { JSX } from 'react';\n\nimport { Button } from '@12-apps/ui/form/Button';\nimport { Box } from '@12-apps/ui/mui/Box';\nimport { alpha, type Theme } from '@12-apps/ui/mui/styles';\nimport { Text } from '@12-apps/ui/typography/Text';\n\nimport type { NotificationMessages } from '../messages';\nimport type { InboxNotification } from '../wire';\n\nimport { relativeTime } from './relative-time';\n\nconst contentButtonSx = {\n flex: 1,\n minWidth: 0,\n display: 'flex',\n flexDirection: 'column',\n gap: 0.25,\n textAlign: 'left',\n border: 'none',\n background: 'none',\n p: 0,\n cursor: 'pointer',\n color: 'text.primary',\n fontFamily: 'inherit',\n} as const;\n\nconst unreadDotSx = {\n width: 8,\n height: 8,\n borderRadius: '50%',\n bgcolor: 'primary.main',\n flex: '0 0 auto',\n} as const;\n\nexport function NotificationRow({\n notification,\n messages,\n onOpen,\n onDelete,\n}: {\n notification: InboxNotification;\n messages: NotificationMessages;\n onOpen: (notification: InboxNotification) => void;\n onDelete: (id: string) => void;\n}): JSX.Element {\n const unread = notification.readAt === null;\n return (\n <Box\n data-testid={`notification-${notification.id}`}\n sx={{\n display: 'flex',\n alignItems: 'flex-start',\n gap: 1,\n py: 1.5,\n px: 1,\n borderBottom: '1px solid',\n borderColor: 'divider',\n bgcolor: unread ? (t: Theme) => alpha(t.palette.primary.main, 0.06) : 'transparent',\n }}\n >\n <Box\n component=\"button\"\n type=\"button\"\n onClick={() => onOpen(notification)}\n aria-label={\n unread ? `${notification.title} (${messages.unreadSuffix})` : notification.title\n }\n sx={contentButtonSx}\n >\n <Box sx={{ display: 'flex', alignItems: 'center', gap: 0.75 }}>\n {unread ? <Box aria-hidden sx={unreadDotSx} /> : null}\n <Text variant=\"body\" size=\"sm\" weight={unread ? 'bold' : 'medium'} as=\"span\">\n {notification.title}\n </Text>\n </Box>\n <Text variant=\"caption\" size=\"xs\" color=\"secondary\" as=\"span\">\n {notification.body}\n </Text>\n <Text variant=\"caption\" size=\"xs\" color=\"secondary\" as=\"span\" italic>\n {relativeTime(notification.createdAt, messages)}\n </Text>\n </Box>\n\n <Button\n variant=\"ghost\"\n color=\"neutral\"\n size=\"xs\"\n aria-label={messages.deleteOne(notification.title)}\n onClick={() => onDelete(notification.id)}\n dataTestId={`notification-delete-${notification.id}`}\n >\n ✕\n </Button>\n </Box>\n );\n}\n"],"mappings":"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","names":["Button","Box","jsx","jsxs","Box","Button"]}
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
} from "./chunk-CQZMTFPY.js";
|
|
5
5
|
import {
|
|
6
6
|
NOTIFICATION_CHANNELS
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-6W7INOYQ.js";
|
|
8
8
|
import {
|
|
9
9
|
__name
|
|
10
10
|
} from "./chunk-7QVYU63E.js";
|
|
@@ -291,4 +291,4 @@ __name(PreferencesScreen, "PreferencesScreen");
|
|
|
291
291
|
export {
|
|
292
292
|
PreferencesScreen
|
|
293
293
|
};
|
|
294
|
-
//# sourceMappingURL=preferences-screen-
|
|
294
|
+
//# sourceMappingURL=preferences-screen-SXUIGECY.js.map
|
package/dist/react/index.d.ts
CHANGED
|
@@ -1,8 +1,50 @@
|
|
|
1
|
-
import { N as NotificationsApiClient } from '../create-web-notifications-
|
|
2
|
-
export { B as BADGE_POLL_MS, a as BADGE_RECONCILE_MS, b as BellButtonProps, I as InboxListStatus, d as InboxState, e as InboxStore, f as
|
|
3
|
-
import { JSX } from 'react';
|
|
4
|
-
import {
|
|
5
|
-
|
|
1
|
+
import { L as LiveActivitiesConfig, N as NotificationsApiClient } from '../create-web-notifications-DV3Y8k7e.js';
|
|
2
|
+
export { B as BADGE_POLL_MS, a as BADGE_RECONCILE_MS, b as BellButtonProps, I as InboxListStatus, d as InboxState, e as InboxStore, f as LiveActivitiesHook, g as LiveActivityMessages, h as NotificationsHttpError, i as NotificationsPanelProps, j as NotificationsResult, k as NotificationsSignalHook, l as NotificationsSubscribe, m as NotificationsTransport, n as NotificationsWebConfig, P as PAGE_SIZE, o as PreferencesPayload, p as PreferencesScreenProps, q as PushRegistrationPayload, W as WebNotifications, r as WebPushPlatformHint, s as WebPushSetupConfig, t as createInboxStore, u as createNotificationsApiClient, c as createWebNotifications, v as httpNotificationsTransport, w as useInboxList, x as useInboxState, y as useUnreadCount } from '../create-web-notifications-DV3Y8k7e.js';
|
|
3
|
+
import { JSX, ReactNode } from 'react';
|
|
4
|
+
import { b as LiveActivity } from '../live-DYxEFO49.js';
|
|
5
|
+
export { c as LiveActivityLane, d as LiveActivityStep, l as liveActivityLane } from '../live-DYxEFO49.js';
|
|
6
|
+
import { N as NotificationMessages } from '../wire-BG1kuoXX.js';
|
|
7
|
+
import '../types-BlqZkCWZ.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* What the reader has already been shown, so the bell can say NEW rather than
|
|
11
|
+
* merely PRESENT.
|
|
12
|
+
*
|
|
13
|
+
* A live activity is unlike an inbox row in the one way that matters here: it
|
|
14
|
+
* stays on the panel for as long as the thing is happening, so its presence
|
|
15
|
+
* cannot mean "you have not seen this". A pedido that has been `Preparo` for
|
|
16
|
+
* ten minutes is still live and still worth counting, but nothing has happened
|
|
17
|
+
* — and a badge that shouts for a subject the reader has already looked at is a
|
|
18
|
+
* badge people stop reading.
|
|
19
|
+
*
|
|
20
|
+
* So presence and novelty are answered separately: the COUNT comes from how
|
|
21
|
+
* many are live, and the TONE comes from this. The panel writes it — being on
|
|
22
|
+
* screen is what seen means — and the bell reads it.
|
|
23
|
+
*
|
|
24
|
+
* ## Per subject, not one watermark
|
|
25
|
+
*
|
|
26
|
+
* A single "newest instant already seen" is smaller and was the first cut, and
|
|
27
|
+
* it is wrong in a way that shows up in normal use: a pedido placed ten minutes
|
|
28
|
+
* ago but only now reaching the client arrives with an `updatedAt` BEHIND the
|
|
29
|
+
* watermark, and would be silently marked as already seen. The reader has never
|
|
30
|
+
* laid eyes on it. Keyed by subject, an id that has not been recorded is new
|
|
31
|
+
* whatever its clock says.
|
|
32
|
+
*
|
|
33
|
+
* Bounded by pruning rather than by expiry: every write keeps only the subjects
|
|
34
|
+
* that are live at that moment, so the record can never outgrow the number of
|
|
35
|
+
* things happening at once. A subject that finishes and later comes back is
|
|
36
|
+
* news again, which is correct — it is a different occurrence.
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
/** id -> the `updatedAt` that was on screen. */
|
|
40
|
+
type SeenMap = Readonly<Record<string, string>>;
|
|
41
|
+
interface LiveSeenStore {
|
|
42
|
+
/** What has been shown, keyed by subject id. */
|
|
43
|
+
read: () => SeenMap;
|
|
44
|
+
/** Record that exactly these are on screen now, forgetting subjects that are not. */
|
|
45
|
+
mark: (activities: readonly LiveActivity[]) => void;
|
|
46
|
+
subscribe: (listener: () => void) => () => void;
|
|
47
|
+
}
|
|
6
48
|
|
|
7
49
|
/** Inline SVG bell (no icon-library dependency in this package). */
|
|
8
50
|
|
|
@@ -11,12 +53,82 @@ declare function BellIcon({ size, dim, }: {
|
|
|
11
53
|
dim?: boolean;
|
|
12
54
|
}): JSX.Element;
|
|
13
55
|
|
|
56
|
+
/**
|
|
57
|
+
* The pinned block at the top of the panel: everything that is happening NOW,
|
|
58
|
+
* above everything that has already happened.
|
|
59
|
+
*
|
|
60
|
+
* ## Why it is here and not a second surface
|
|
61
|
+
*
|
|
62
|
+
* The notification centre is where a person goes to find out what they missed.
|
|
63
|
+
* Splitting "happening" into its own bell would make them check two places to
|
|
64
|
+
* answer one question, and the half they would stop checking is the one that
|
|
65
|
+
* only has something in it occasionally — which is this one. Above the list,
|
|
66
|
+
* inside the same drawer, it is on the path they already walk.
|
|
67
|
+
*
|
|
68
|
+
* ## What it deliberately does NOT do
|
|
69
|
+
*
|
|
70
|
+
* - It does not touch `unread`. A live entry is not news; counting it would put
|
|
71
|
+
* a number on the bell that no amount of reading can clear.
|
|
72
|
+
* - It renders nothing at all when there is nothing live — no heading, no empty
|
|
73
|
+
* state, no reserved space. A panel with one permanent empty section in it is
|
|
74
|
+
* a panel that has taught its reader to skip the top.
|
|
75
|
+
* - It does not fetch. `useActivities` is the host's, and `active` tells it
|
|
76
|
+
* whether anyone is looking.
|
|
77
|
+
*/
|
|
78
|
+
|
|
79
|
+
interface LiveSectionProps {
|
|
80
|
+
config: LiveActivitiesConfig;
|
|
81
|
+
messages: NotificationMessages;
|
|
82
|
+
/** Whether the panel is open — passed straight through to the host's hook. */
|
|
83
|
+
active: boolean;
|
|
84
|
+
/**
|
|
85
|
+
* Follow a card's link.
|
|
86
|
+
*
|
|
87
|
+
* Optional, and the panel omits it for a host with no router: a card that
|
|
88
|
+
* cannot go anywhere renders as text rather than as a named control that
|
|
89
|
+
* does nothing.
|
|
90
|
+
*/
|
|
91
|
+
onOpen?: (activity: LiveActivity) => void;
|
|
92
|
+
/**
|
|
93
|
+
* The rest of the panel, given how many entries are live.
|
|
94
|
+
*
|
|
95
|
+
* A render prop rather than a sibling, because the count is knowable only
|
|
96
|
+
* where the host's hook is CALLED, and it cannot be called anywhere else:
|
|
97
|
+
* `live` is optional on the panel, so reading it there would mean calling a
|
|
98
|
+
* hook conditionally — the failure React reports as a crash in some unrelated
|
|
99
|
+
* component.
|
|
100
|
+
*
|
|
101
|
+
* The inbox needs the number for exactly one decision, and it is the decision
|
|
102
|
+
* this section exists to inform: whether "no notifications" is true. A live
|
|
103
|
+
* entry IS a notification, so a panel showing one under that sentence is
|
|
104
|
+
* contradicting itself.
|
|
105
|
+
*/
|
|
106
|
+
children?: (liveCount: number) => ReactNode;
|
|
107
|
+
/**
|
|
108
|
+
* Where "the reader has seen these" is recorded, for the bell to read.
|
|
109
|
+
*
|
|
110
|
+
* Written HERE because this is the component that puts them on screen, and
|
|
111
|
+
* being on screen is what seen means. Optional so the section stays usable by
|
|
112
|
+
* a host that mounts it outside the panel.
|
|
113
|
+
*/
|
|
114
|
+
seen?: LiveSeenStore;
|
|
115
|
+
}
|
|
116
|
+
declare function LiveSection({ config, messages, active, onOpen, children, seen, }: LiveSectionProps): JSX.Element;
|
|
117
|
+
|
|
14
118
|
/**
|
|
15
119
|
* "há 5 min"-style relative timestamp, falling back to an absolute date for
|
|
16
120
|
* anything older than a week. Every word comes from the messages table, so a
|
|
17
121
|
* host in another locale changes the copy and the locale together.
|
|
122
|
+
*
|
|
123
|
+
* `now` is a parameter rather than a read, and the live section is why. A
|
|
124
|
+
* relative phrase is only true for the instant it was computed, so something
|
|
125
|
+
* has to CAUSE the render that recomputes it — and the live entries' own data
|
|
126
|
+
* cannot: a host backed by react-query gets the previous object back whenever a
|
|
127
|
+
* poll is deep-equal (`structuralSharing`, on by default), which within one
|
|
128
|
+
* stage it always is. The section therefore ticks a clock and hands it down.
|
|
129
|
+
* Defaulted, so every existing caller reads the wall clock exactly as before.
|
|
18
130
|
*/
|
|
19
|
-
declare function relativeTime(iso: string, messages: NotificationMessages): string;
|
|
131
|
+
declare function relativeTime(iso: string, messages: NotificationMessages, now?: number): string;
|
|
20
132
|
|
|
21
133
|
/**
|
|
22
134
|
* The browser half of Web Push: register the service worker, ask permission,
|
|
@@ -44,4 +156,4 @@ declare function enableWebPush(api: NotificationsApiClient, swPath?: string): Pr
|
|
|
44
156
|
/** Disable flow: unsubscribe the browser and drop the server-side row. */
|
|
45
157
|
declare function disableWebPush(api: NotificationsApiClient): Promise<void>;
|
|
46
158
|
|
|
47
|
-
export { BellIcon, NotificationsApiClient, type PushSetupResult, disableWebPush, enableWebPush, getExistingPushSubscription, pushSupported, relativeTime };
|
|
159
|
+
export { BellIcon, LiveActivitiesConfig, LiveActivity, LiveSection, type LiveSectionProps, NotificationsApiClient, type PushSetupResult, disableWebPush, enableWebPush, getExistingPushSubscription, pushSupported, relativeTime };
|
package/dist/react/index.js
CHANGED
|
@@ -3,10 +3,11 @@ import {
|
|
|
3
3
|
createNotificationsApiClient,
|
|
4
4
|
createWebNotifications,
|
|
5
5
|
httpNotificationsTransport
|
|
6
|
-
} from "../chunk-
|
|
6
|
+
} from "../chunk-I5QUMTCN.js";
|
|
7
7
|
import {
|
|
8
|
+
LiveSection,
|
|
8
9
|
relativeTime
|
|
9
|
-
} from "../chunk-
|
|
10
|
+
} from "../chunk-ZY32PC34.js";
|
|
10
11
|
import {
|
|
11
12
|
BADGE_POLL_MS,
|
|
12
13
|
BADGE_RECONCILE_MS,
|
|
@@ -24,11 +25,15 @@ import {
|
|
|
24
25
|
pushSupported
|
|
25
26
|
} from "../chunk-CQZMTFPY.js";
|
|
26
27
|
import "../chunk-M2TVBVH2.js";
|
|
28
|
+
import {
|
|
29
|
+
liveActivityLane
|
|
30
|
+
} from "../chunk-RTURLH5U.js";
|
|
27
31
|
import "../chunk-7QVYU63E.js";
|
|
28
32
|
export {
|
|
29
33
|
BADGE_POLL_MS,
|
|
30
34
|
BADGE_RECONCILE_MS,
|
|
31
35
|
BellIcon,
|
|
36
|
+
LiveSection,
|
|
32
37
|
NotificationsHttpError,
|
|
33
38
|
PAGE_SIZE,
|
|
34
39
|
createInboxStore,
|
|
@@ -38,6 +43,7 @@ export {
|
|
|
38
43
|
enableWebPush,
|
|
39
44
|
getExistingPushSubscription,
|
|
40
45
|
httpNotificationsTransport,
|
|
46
|
+
liveActivityLane,
|
|
41
47
|
pushSupported,
|
|
42
48
|
relativeTime,
|
|
43
49
|
useInboxList,
|
package/dist/server/index.d.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { A as ApiNotifications } from '../create-api-notifications-
|
|
2
|
-
export { C as CommittedNotification, E as EMAIL_DRIVERS, c as EmailDriver, d as EmailDriverDeclaration, e as EmailMessage, f as ExtraDrivers, L as ListNotificationsInput, g as NotificationAudienceDirectory, h as NotificationChannelPolicy, i as NotificationCommittedListener, j as NotificationContactDirectory, k as NotificationCreateData, l as NotificationDelegate, m as NotificationDeliveryDelegate, n as NotificationDeliveryRow, o as NotificationDeliveryWhere, p as NotificationDispatchScheduler, q as NotificationInboxStore, r as NotificationPageAfter, s as NotificationPreferenceDelegate, t as NotificationPreferenceRow, u as NotificationPreferenceStore, v as NotificationRouter, w as NotificationWhere, a as NotificationsActor, x as NotificationsApiError, y as NotificationsDb, z as NotificationsDbClient, B as NotificationsDbProvider, D as NotificationsRequest, F as NotificationsResponse, b as NotificationsRoute, N as NotificationsServerConfig, G as NotifyByPermission, H as NotifyOptions, I as NotifyResult, P as PermissionNotificationResult, J as PermissionNotificationSkip, K as PushSubscriptionDelegate, M as PushSubscriptionInput, O as PushSubscriptionRow, Q as PushSubscriptionStore, S as SMS_DRIVERS, R as SmsDriver, T as SmsDriverDeclaration, U as SmsMessage, V as TransportDeclaration, W as TransportRegistry, X as WHATSAPP_DRIVERS, Y as WhatsAppDriver, Z as WhatsAppDriverDeclaration, _ as WhatsAppMessage, $ as createApiNotifications, a0 as createTransportRegistry, a1 as emailTransport, a2 as foldApiError, a3 as formatEmail, a4 as formatSms, a5 as formatWhatsApp, a6 as ok, a7 as smsTransport, a8 as whatsAppTransport } from '../create-api-notifications-
|
|
3
|
-
export { D as DriverDeclarationBase, F as FetchImpl, N as NotificationProviderError, W as WEB_PUSH_DRIVERS, a as WebPushDriverDeclaration, b as WebPushMessage, c as WebPushSender, d as WebPushSubscription, e as WebPushSubscriptionSource, f as absoluteLink, g as formatWebPush, w as webPushTransport } from '../web-push-
|
|
1
|
+
import { A as ApiNotifications } from '../create-api-notifications-CcPYrM3p.js';
|
|
2
|
+
export { C as CommittedNotification, E as EMAIL_DRIVERS, c as EmailDriver, d as EmailDriverDeclaration, e as EmailMessage, f as ExtraDrivers, L as ListNotificationsInput, g as NotificationAudienceDirectory, h as NotificationChannelPolicy, i as NotificationCommittedListener, j as NotificationContactDirectory, k as NotificationCreateData, l as NotificationDelegate, m as NotificationDeliveryDelegate, n as NotificationDeliveryRow, o as NotificationDeliveryWhere, p as NotificationDispatchScheduler, q as NotificationInboxStore, r as NotificationPageAfter, s as NotificationPreferenceDelegate, t as NotificationPreferenceRow, u as NotificationPreferenceStore, v as NotificationRouter, w as NotificationWhere, a as NotificationsActor, x as NotificationsApiError, y as NotificationsDb, z as NotificationsDbClient, B as NotificationsDbProvider, D as NotificationsRequest, F as NotificationsResponse, b as NotificationsRoute, N as NotificationsServerConfig, G as NotifyByPermission, H as NotifyOptions, I as NotifyResult, P as PermissionNotificationResult, J as PermissionNotificationSkip, K as PushSubscriptionDelegate, M as PushSubscriptionInput, O as PushSubscriptionRow, Q as PushSubscriptionStore, S as SMS_DRIVERS, R as SmsDriver, T as SmsDriverDeclaration, U as SmsMessage, V as TransportDeclaration, W as TransportRegistry, X as WHATSAPP_DRIVERS, Y as WhatsAppDriver, Z as WhatsAppDriverDeclaration, _ as WhatsAppMessage, $ as createApiNotifications, a0 as createTransportRegistry, a1 as emailTransport, a2 as foldApiError, a3 as formatEmail, a4 as formatSms, a5 as formatWhatsApp, a6 as ok, a7 as smsTransport, a8 as whatsAppTransport } from '../create-api-notifications-CcPYrM3p.js';
|
|
3
|
+
export { D as DriverDeclarationBase, F as FetchImpl, N as NotificationProviderError, W as WEB_PUSH_DRIVERS, a as WebPushDriverDeclaration, b as WebPushMessage, c as WebPushSender, d as WebPushSubscription, e as WebPushSubscriptionSource, f as absoluteLink, g as formatWebPush, w as webPushTransport } from '../web-push-Dnyaha2z.js';
|
|
4
4
|
import { NotifyPort } from '@12-apps/wiring/ports';
|
|
5
|
-
export { a as NOTIFICATIONS_DRAIN_CRON, b as NOTIFICATIONS_DRAIN_LEASE_MS, c as NOTIFICATIONS_JOBS, d as NOTIFICATIONS_SWEEP_QUEUE, N as NotificationsJobDeps } from '../jobs-
|
|
6
|
-
import '../generators-
|
|
7
|
-
import '../types-
|
|
8
|
-
import '../wire-
|
|
5
|
+
export { a as NOTIFICATIONS_DRAIN_CRON, b as NOTIFICATIONS_DRAIN_LEASE_MS, c as NOTIFICATIONS_JOBS, d as NOTIFICATIONS_SWEEP_QUEUE, N as NotificationsJobDeps } from '../jobs--fex87-q.js';
|
|
6
|
+
import '../generators-qAD4fNPq.js';
|
|
7
|
+
import '../types-BlqZkCWZ.js';
|
|
8
|
+
import '../wire-BG1kuoXX.js';
|
|
9
9
|
import '../template-Dlt5S54z.js';
|
|
10
10
|
import '@12-apps/wiring';
|
|
11
11
|
|
package/dist/server/index.js
CHANGED
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
NOTIFICATIONS_JOBS,
|
|
5
5
|
NOTIFICATIONS_SWEEP_QUEUE,
|
|
6
6
|
wireNotifyPort
|
|
7
|
-
} from "../chunk-
|
|
7
|
+
} from "../chunk-ZIR3ILFH.js";
|
|
8
8
|
import {
|
|
9
9
|
EMAIL_DRIVERS,
|
|
10
10
|
NotificationProviderError,
|
|
@@ -25,10 +25,11 @@ import {
|
|
|
25
25
|
smsTransport,
|
|
26
26
|
webPushTransport,
|
|
27
27
|
whatsAppTransport
|
|
28
|
-
} from "../chunk-
|
|
29
|
-
import "../chunk-
|
|
28
|
+
} from "../chunk-WVRODNXQ.js";
|
|
29
|
+
import "../chunk-SWOWHIFE.js";
|
|
30
30
|
import "../chunk-M2TVBVH2.js";
|
|
31
|
-
import "../chunk-
|
|
31
|
+
import "../chunk-RTURLH5U.js";
|
|
32
|
+
import "../chunk-6W7INOYQ.js";
|
|
32
33
|
import "../chunk-EKUSNUBT.js";
|
|
33
34
|
import "../chunk-7QVYU63E.js";
|
|
34
35
|
export {
|
|
@@ -60,7 +60,16 @@ interface NotificationContent {
|
|
|
60
60
|
body: string;
|
|
61
61
|
/** In-app deep link (a same-origin path such as `/orders/123`). */
|
|
62
62
|
link?: string;
|
|
63
|
-
/**
|
|
63
|
+
/**
|
|
64
|
+
* Structured extras for consumers that want more than text.
|
|
65
|
+
*
|
|
66
|
+
* ONE KEY IS RESERVED: `liveSubject` (`LIVE_SUBJECT_KEY` in `./live`) ties
|
|
67
|
+
* this notification to a live activity, and the WEB_PUSH transport turns it
|
|
68
|
+
* into the tray `tag` that collapses every push about one subject onto a
|
|
69
|
+
* single entry. A host already using that name for something else acquires
|
|
70
|
+
* that behaviour without asking for it — so it is named here, on the field a
|
|
71
|
+
* generator actually writes, and not only where the feature is documented.
|
|
72
|
+
*/
|
|
64
73
|
data?: Record<string, unknown>;
|
|
65
74
|
}
|
|
66
75
|
/** Who receives a notification. `clientId` scopes it to a tenant when set. */
|
package/dist/web-push/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { c as WebPushSender } from '../web-push-
|
|
2
|
-
import '../types-
|
|
1
|
+
import { c as WebPushSender } from '../web-push-Dnyaha2z.js';
|
|
2
|
+
import '../types-BlqZkCWZ.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* `@12-apps/notifications/web-push` — the VAPID sender, behind its own subpath.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { g as NotificationLogger, d as NotificationContent, j as NotificationTransport } from './types-
|
|
1
|
+
import { g as NotificationLogger, d as NotificationContent, j as NotificationTransport } from './types-BlqZkCWZ.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* The DRIVER port — the reason a host adds a vendor with a config entry and no
|
|
@@ -98,6 +98,26 @@ interface WebPushMessage {
|
|
|
98
98
|
body: string;
|
|
99
99
|
link: string | null;
|
|
100
100
|
data: Record<string, unknown>;
|
|
101
|
+
/**
|
|
102
|
+
* The OS tray tag, or `null` for an ordinary event.
|
|
103
|
+
*
|
|
104
|
+
* Present for a notification about a LIVE SUBJECT (see `../../live`), and
|
|
105
|
+
* this is the whole of the live feature's reach onto a locked phone: a tag
|
|
106
|
+
* makes the next push about the same subject REPLACE the one already in the
|
|
107
|
+
* tray — silently — instead of stacking under it. Four stages then cost one
|
|
108
|
+
* entry and one buzz, and the entry that remains is the current one.
|
|
109
|
+
*
|
|
110
|
+
* {@link formatWebPush} always sets it, `null` included, so on the WIRE the
|
|
111
|
+
* key is always there and a service worker reads one shape. A worker that
|
|
112
|
+
* ignores it keeps today's behaviour exactly, which is what makes this safe
|
|
113
|
+
* to ship ahead of the workers.
|
|
114
|
+
*
|
|
115
|
+
* OPTIONAL in the type, and only in the type: a host that hand-writes its own
|
|
116
|
+
* `NotificationTransport<WebPushMessage>` — a fixture, a second driver —
|
|
117
|
+
* would otherwise stop compiling on a field it has no way to know about and
|
|
118
|
+
* no reason to set.
|
|
119
|
+
*/
|
|
120
|
+
tag?: string | null;
|
|
101
121
|
}
|
|
102
122
|
interface WebPushDriverDeclaration extends DriverDeclarationBase {
|
|
103
123
|
channel: 'WEB_PUSH';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@12-apps/notifications",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.10.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"description": "Plug-and-play notification system (12-15): an always-on in-app inbox, per-user × per-category channel preferences, and email / SMS / WhatsApp / web-push transports behind vendor DRIVERS so a second provider is a config entry. Framework-free core (.), host-mounted backend surface (./server: inbox / preferences / push-subscription endpoints, the channel router with delivery records + retry sweep, the permission fan-out, duck-typed Prisma seam), Hono adapter (./hono), React surface (./react: bell + badge, inbox drawer, preferences screen), VAPID sender (./web-push) and the package-owned Prisma partial + migrations. Standardized adoption contract in ADOPTING.md.",
|
|
@@ -71,7 +71,7 @@
|
|
|
71
71
|
"prisma:sync:check": "node scripts/sync-notifications-schema.mjs --check"
|
|
72
72
|
},
|
|
73
73
|
"dependencies": {
|
|
74
|
-
"@12-apps/ui": "^6.
|
|
74
|
+
"@12-apps/ui": "^6.18.0"
|
|
75
75
|
},
|
|
76
76
|
"peerDependencies": {
|
|
77
77
|
"@12-apps/wiring": ">=1.3.0",
|
package/src/index.ts
CHANGED
|
@@ -65,6 +65,16 @@ export {
|
|
|
65
65
|
type NotificationMessages,
|
|
66
66
|
} from './messages';
|
|
67
67
|
|
|
68
|
+
export {
|
|
69
|
+
LIVE_PUSH_TAG_PREFIX,
|
|
70
|
+
LIVE_SUBJECT_KEY,
|
|
71
|
+
liveActivityLane,
|
|
72
|
+
livePushTag,
|
|
73
|
+
type LiveActivity,
|
|
74
|
+
type LiveActivityLane,
|
|
75
|
+
type LiveActivityStep,
|
|
76
|
+
} from './live';
|
|
77
|
+
|
|
68
78
|
export {
|
|
69
79
|
inboxWire,
|
|
70
80
|
type InboxNotification,
|