@apptegy/nuxt-navigation 0.1.120 → 0.1.122
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/dist/module.json +1 -1
- package/dist/module.mjs +2 -2
- package/dist/runtime/components/Nav/NotificationItem.vue +151 -81
- package/dist/runtime/components/Nav/SidebarSection.vue +1 -0
- package/dist/runtime/composables/useSidebar.d.ts +1 -1
- package/dist/runtime/types/index.d.ts +2 -1
- package/dist/runtime/types/notifications.d.ts +19 -1
- package/dist/runtime/utils/notification-api.d.ts +5 -0
- package/dist/runtime/utils/notification-api.js +30 -1
- package/locale/en.json +1 -0
- package/package.json +1 -1
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { defineNuxtModule, createResolver, addComponentsDir, addImportsDir } from '@nuxt/kit';
|
|
2
2
|
|
|
3
|
-
const module = defineNuxtModule({
|
|
3
|
+
const module$1 = defineNuxtModule({
|
|
4
4
|
meta: {
|
|
5
5
|
name: "@apptegy/nuxt-navigation",
|
|
6
6
|
compatibility: {
|
|
@@ -22,4 +22,4 @@ const module = defineNuxtModule({
|
|
|
22
22
|
}
|
|
23
23
|
});
|
|
24
24
|
|
|
25
|
-
export { module as default };
|
|
25
|
+
export { module$1 as default };
|
|
@@ -3,95 +3,124 @@
|
|
|
3
3
|
class="notification-item"
|
|
4
4
|
:class="{ 'notification-item--in-badge': showBadgeActions }"
|
|
5
5
|
>
|
|
6
|
-
<div
|
|
7
|
-
class="notification-item__main"
|
|
8
|
-
role="button"
|
|
9
|
-
tabindex="0"
|
|
10
|
-
@click="handleClick"
|
|
11
|
-
@keydown.enter.prevent="handleClick"
|
|
12
|
-
>
|
|
6
|
+
<div class="notification-item__header">
|
|
13
7
|
<div
|
|
14
|
-
class="notification-
|
|
15
|
-
|
|
8
|
+
class="notification-item__main"
|
|
9
|
+
role="button"
|
|
10
|
+
tabindex="0"
|
|
11
|
+
@click="handleClick"
|
|
12
|
+
@keydown.enter.prevent="handleClick"
|
|
16
13
|
>
|
|
17
|
-
<div
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
14
|
+
<div
|
|
15
|
+
class="notification-item__avatar"
|
|
16
|
+
:style="{ '--type-accent': typeAccent }"
|
|
17
|
+
>
|
|
18
|
+
<div class="notification-item__avatar-ring">
|
|
19
|
+
<NavAvatar
|
|
20
|
+
:name="primaryActor.name"
|
|
21
|
+
:src="primaryActor.avatarUrl"
|
|
22
|
+
class="notification-item__avatar-image"
|
|
23
|
+
/>
|
|
24
|
+
</div>
|
|
25
|
+
<span
|
|
26
|
+
v-if="groupBadgeLabel"
|
|
27
|
+
class="notification-item__group-badge"
|
|
28
|
+
aria-hidden="true"
|
|
29
|
+
>
|
|
30
|
+
{{ groupBadgeLabel }}
|
|
31
|
+
</span>
|
|
32
|
+
<span
|
|
33
|
+
v-else
|
|
34
|
+
class="notification-item__type-badge"
|
|
35
|
+
aria-hidden="true"
|
|
36
|
+
>
|
|
37
|
+
<component :is="typeIcon" />
|
|
38
|
+
</span>
|
|
39
|
+
</div>
|
|
40
|
+
|
|
41
|
+
<div class="notification-item__content">
|
|
42
|
+
<p class="notification-item__message">
|
|
43
|
+
{{ item.actionText }}
|
|
44
|
+
</p>
|
|
45
|
+
<p class="notification-item__meta">
|
|
46
|
+
<span>{{ item.timestampLabel }}</span>
|
|
47
|
+
<template v-if="item.contextLabel">
|
|
48
|
+
<span
|
|
49
|
+
class="notification-item__meta-separator"
|
|
50
|
+
aria-hidden="true"
|
|
51
|
+
>·</span
|
|
52
|
+
>
|
|
53
|
+
<span>{{ item.contextLabel }}</span>
|
|
54
|
+
</template>
|
|
55
|
+
</p>
|
|
56
|
+
<Tag
|
|
57
|
+
v-if="item.stateLabel"
|
|
58
|
+
:value="item.stateLabel"
|
|
59
|
+
severity="secondary"
|
|
60
|
+
class="notification-item__state-chip"
|
|
22
61
|
/>
|
|
23
62
|
</div>
|
|
24
|
-
<span
|
|
25
|
-
v-if="groupBadgeLabel"
|
|
26
|
-
class="notification-item__group-badge"
|
|
27
|
-
aria-hidden="true"
|
|
28
|
-
>
|
|
29
|
-
{{ groupBadgeLabel }}
|
|
30
|
-
</span>
|
|
31
|
-
<span
|
|
32
|
-
class="notification-item__type-badge"
|
|
33
|
-
aria-hidden="true"
|
|
34
|
-
>
|
|
35
|
-
<component :is="typeIcon" />
|
|
36
|
-
</span>
|
|
37
63
|
</div>
|
|
38
64
|
|
|
39
|
-
<div class="notification-
|
|
40
|
-
<
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
<span>{{ item.timestampLabel }}</span>
|
|
45
|
-
<template v-if="item.contextLabel">
|
|
46
|
-
<span
|
|
47
|
-
class="notification-item__meta-separator"
|
|
48
|
-
aria-hidden="true"
|
|
49
|
-
>·</span
|
|
50
|
-
>
|
|
51
|
-
<span>{{ item.contextLabel }}</span>
|
|
52
|
-
</template>
|
|
53
|
-
</p>
|
|
54
|
-
<Tag
|
|
55
|
-
v-if="item.stateLabel"
|
|
56
|
-
:value="item.stateLabel"
|
|
57
|
-
severity="secondary"
|
|
58
|
-
class="notification-item__state-chip"
|
|
65
|
+
<div class="notification-item__actions">
|
|
66
|
+
<span
|
|
67
|
+
v-if="showBadgeActions"
|
|
68
|
+
class="notification-item__unread-dot"
|
|
69
|
+
aria-hidden="true"
|
|
59
70
|
/>
|
|
71
|
+
<Button
|
|
72
|
+
v-if="showBadgeActions"
|
|
73
|
+
type="button"
|
|
74
|
+
severity="secondary"
|
|
75
|
+
text
|
|
76
|
+
rounded
|
|
77
|
+
class="notification-item__dismiss"
|
|
78
|
+
:aria-label="$t('navigation.notificationsPanel.dismiss')"
|
|
79
|
+
@click="handleDismiss"
|
|
80
|
+
>
|
|
81
|
+
<svg
|
|
82
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
83
|
+
width="16"
|
|
84
|
+
height="16"
|
|
85
|
+
viewBox="0 0 24 24"
|
|
86
|
+
fill="none"
|
|
87
|
+
stroke="currentColor"
|
|
88
|
+
stroke-width="2"
|
|
89
|
+
stroke-linecap="round"
|
|
90
|
+
stroke-linejoin="round"
|
|
91
|
+
aria-hidden="true"
|
|
92
|
+
>
|
|
93
|
+
<path d="M18 6 6 18" />
|
|
94
|
+
<path d="m6 6 12 12" />
|
|
95
|
+
</svg>
|
|
96
|
+
</Button>
|
|
60
97
|
</div>
|
|
61
98
|
</div>
|
|
62
99
|
|
|
63
|
-
<div
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
rounded
|
|
75
|
-
class="notification-item__dismiss"
|
|
76
|
-
:aria-label="$t('navigation.notificationsPanel.dismiss')"
|
|
77
|
-
@click="handleDismiss"
|
|
78
|
-
>
|
|
79
|
-
<svg
|
|
80
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
81
|
-
width="16"
|
|
82
|
-
height="16"
|
|
83
|
-
viewBox="0 0 24 24"
|
|
84
|
-
fill="none"
|
|
85
|
-
stroke="currentColor"
|
|
86
|
-
stroke-width="2"
|
|
87
|
-
stroke-linecap="round"
|
|
88
|
-
stroke-linejoin="round"
|
|
100
|
+
<div
|
|
101
|
+
v-if="item.summary"
|
|
102
|
+
class="notification-item__summary"
|
|
103
|
+
role="button"
|
|
104
|
+
tabindex="0"
|
|
105
|
+
@click="handleClick"
|
|
106
|
+
@keydown.enter.prevent="handleClick"
|
|
107
|
+
>
|
|
108
|
+
<template v-if="reactionEmojis.length">
|
|
109
|
+
<span
|
|
110
|
+
class="notification-item__reactions"
|
|
89
111
|
aria-hidden="true"
|
|
90
112
|
>
|
|
91
|
-
<
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
113
|
+
<span
|
|
114
|
+
v-for="(emoji, index) in reactionEmojis"
|
|
115
|
+
:key="`${emoji}-${index}`"
|
|
116
|
+
class="notification-item__reaction"
|
|
117
|
+
>{{ emoji }}</span>
|
|
118
|
+
</span>
|
|
119
|
+
<span class="notification-item__summary-on">
|
|
120
|
+
{{ $t("navigation.notificationsPanel.summaryOn") }}
|
|
121
|
+
</span>
|
|
122
|
+
</template>
|
|
123
|
+
<span class="notification-item__summary-text">"{{ item.summary }}"</span>
|
|
95
124
|
</div>
|
|
96
125
|
</div>
|
|
97
126
|
</template>
|
|
@@ -108,6 +137,7 @@ const emit = defineEmits(["dismiss", "select"]);
|
|
|
108
137
|
const primaryActor = computed(() => props.item.actors[0] || { name: "" });
|
|
109
138
|
const typeAccent = computed(() => getTypeAccent(props.item.type));
|
|
110
139
|
const showBadgeActions = computed(() => isInBadge(props.item));
|
|
140
|
+
const reactionEmojis = computed(() => props.item.reactionEmojis ?? []);
|
|
111
141
|
const groupBadgeLabel = computed(() => {
|
|
112
142
|
const count = props.item.groupCount ?? Math.max(props.item.actors.length - 1, 0);
|
|
113
143
|
return count > 0 ? `+${count}` : "";
|
|
@@ -189,10 +219,8 @@ function handleDismiss(event) {
|
|
|
189
219
|
|
|
190
220
|
<style scoped>
|
|
191
221
|
.notification-item {
|
|
192
|
-
display:
|
|
193
|
-
|
|
194
|
-
gap: 12px;
|
|
195
|
-
align-items: start;
|
|
222
|
+
display: flex;
|
|
223
|
+
flex-direction: column;
|
|
196
224
|
width: 100%;
|
|
197
225
|
padding: 12px;
|
|
198
226
|
border: 1px solid rgb(232, 232, 236);
|
|
@@ -203,6 +231,12 @@ function handleDismiss(event) {
|
|
|
203
231
|
box-shadow: none;
|
|
204
232
|
transition: border-color 0.15s ease, box-shadow 0.15s ease;
|
|
205
233
|
}
|
|
234
|
+
.notification-item__header {
|
|
235
|
+
display: grid;
|
|
236
|
+
grid-template-columns: 1fr auto;
|
|
237
|
+
gap: 12px;
|
|
238
|
+
align-items: start;
|
|
239
|
+
}
|
|
206
240
|
.notification-item__main {
|
|
207
241
|
display: grid;
|
|
208
242
|
grid-template-columns: auto 1fr;
|
|
@@ -308,6 +342,42 @@ function handleDismiss(event) {
|
|
|
308
342
|
.notification-item__state-chip {
|
|
309
343
|
margin-top: 8px;
|
|
310
344
|
}
|
|
345
|
+
.notification-item__summary {
|
|
346
|
+
display: flex;
|
|
347
|
+
flex-wrap: wrap;
|
|
348
|
+
align-items: baseline;
|
|
349
|
+
gap: 6px;
|
|
350
|
+
margin-top: 12px;
|
|
351
|
+
padding-top: 12px;
|
|
352
|
+
border-top: 1px solid rgb(232, 232, 236);
|
|
353
|
+
cursor: pointer;
|
|
354
|
+
font-size: 13px;
|
|
355
|
+
line-height: 18px;
|
|
356
|
+
color: var(--grey-70, #4b5563);
|
|
357
|
+
}
|
|
358
|
+
.notification-item__summary:focus-visible {
|
|
359
|
+
outline: 2px solid var(--purple-60, #662e80);
|
|
360
|
+
outline-offset: 2px;
|
|
361
|
+
border-radius: 4px;
|
|
362
|
+
}
|
|
363
|
+
.notification-item__reactions {
|
|
364
|
+
display: inline-flex;
|
|
365
|
+
flex-wrap: wrap;
|
|
366
|
+
gap: 2px;
|
|
367
|
+
flex-shrink: 0;
|
|
368
|
+
}
|
|
369
|
+
.notification-item__reaction {
|
|
370
|
+
font-size: 14px;
|
|
371
|
+
line-height: 1;
|
|
372
|
+
}
|
|
373
|
+
.notification-item__summary-on {
|
|
374
|
+
color: var(--grey-50, #747474);
|
|
375
|
+
}
|
|
376
|
+
.notification-item__summary-text {
|
|
377
|
+
min-width: 0;
|
|
378
|
+
font-style: italic;
|
|
379
|
+
color: var(--grey-70, #4b5563);
|
|
380
|
+
}
|
|
311
381
|
.notification-item__actions {
|
|
312
382
|
display: flex;
|
|
313
383
|
align-items: center;
|
|
@@ -3,6 +3,7 @@ export interface IApplication {
|
|
|
3
3
|
text: string;
|
|
4
4
|
url: string;
|
|
5
5
|
img_src: string;
|
|
6
|
+
target?: string;
|
|
6
7
|
}
|
|
7
8
|
export interface IUser {
|
|
8
9
|
show: boolean;
|
|
@@ -11,4 +12,4 @@ export interface IUser {
|
|
|
11
12
|
client: string;
|
|
12
13
|
admin_email: string;
|
|
13
14
|
}
|
|
14
|
-
export type { NotificationType, NotificationApiState, INotificationApiActor, INotificationApiRecord, INotificationsPagination, INotificationsApiResponse, INotificationActor, INotificationItem, INotificationGroup, INotificationTab, INotificationFilter, INotificationsPanelData, INotificationsHeaderModel, } from './notifications.js';
|
|
15
|
+
export type { NotificationType, NotificationApiState, INotificationApiActor, INotificationApiRoom, INotificationApiRecord, INotificationsPagination, INotificationsApiResponse, INotificationActor, INotificationItem, INotificationGroup, INotificationTab, INotificationFilter, INotificationsPanelData, INotificationsHeaderModel, } from './notifications.js';
|
|
@@ -5,6 +5,11 @@ export interface INotificationApiActor {
|
|
|
5
5
|
first_name: string;
|
|
6
6
|
last_name: string;
|
|
7
7
|
}
|
|
8
|
+
export interface INotificationApiRoom {
|
|
9
|
+
id?: string;
|
|
10
|
+
sis_id?: string;
|
|
11
|
+
name: string;
|
|
12
|
+
}
|
|
8
13
|
export interface INotificationApiRecord {
|
|
9
14
|
id: number | string;
|
|
10
15
|
/**
|
|
@@ -14,7 +19,15 @@ export interface INotificationApiRecord {
|
|
|
14
19
|
kind?: string | null;
|
|
15
20
|
source_record_type: string;
|
|
16
21
|
title: string;
|
|
17
|
-
summary
|
|
22
|
+
/** Post/body preview shown in the card summary row. */
|
|
23
|
+
summary?: string | null;
|
|
24
|
+
/**
|
|
25
|
+
* Reaction tallies for aggregate reaction notifications (e.g. `{ CLAP: 2, LIKE: 1 }`).
|
|
26
|
+
* When present, corresponding emojis are shown beside the summary.
|
|
27
|
+
*/
|
|
28
|
+
reaction_counts?: Record<string, number> | null;
|
|
29
|
+
/** Room associated with the notification; `name` is shown in the meta line. */
|
|
30
|
+
room?: INotificationApiRoom | null;
|
|
18
31
|
actors?: INotificationApiActor[] | null;
|
|
19
32
|
detail_link?: string | null;
|
|
20
33
|
notification_state: NotificationApiState | string;
|
|
@@ -58,7 +71,12 @@ export interface INotificationItem {
|
|
|
58
71
|
* Derived from API `last_activity_at`, falling back to `created_at`.
|
|
59
72
|
*/
|
|
60
73
|
createdAt?: string;
|
|
74
|
+
/** Room name shown after the relative timestamp (e.g. "Staff Lounge"). */
|
|
61
75
|
contextLabel?: string;
|
|
76
|
+
/** Post/body preview shown in the card summary row. */
|
|
77
|
+
summary?: string;
|
|
78
|
+
/** Emoji list derived from API `reaction_counts`, shown before the summary when present. */
|
|
79
|
+
reactionEmojis?: string[];
|
|
62
80
|
/** When false, the notification counts toward the bell badge and appears in the New tab. */
|
|
63
81
|
dismissed?: boolean;
|
|
64
82
|
/** Read receipt — announcement and message types only. */
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import type { INotificationApiActor, INotificationApiRecord, INotificationGroup, INotificationItem, INotificationsApiResponse, INotificationsPanelData, INotificationTab, NotificationType } from '../types/index.js';
|
|
2
2
|
import { type INotificationDateLabels } from './notification-dates.js';
|
|
3
|
+
/**
|
|
4
|
+
* Expand `reaction_counts` into a list of emojis (one entry per count).
|
|
5
|
+
* Unknown reaction keys are skipped.
|
|
6
|
+
*/
|
|
7
|
+
export declare function mapReactionCountsToEmojis(reactionCounts: Record<string, number> | null | undefined): string[];
|
|
3
8
|
export declare const DEFAULT_NOTIFICATION_TABS: INotificationTab[];
|
|
4
9
|
/** Normalize kind values for filter matching and `filter[kind][]` query params. */
|
|
5
10
|
export declare function normalizeNotificationKind(value: string): string;
|
|
@@ -15,6 +15,31 @@ const SOURCE_RECORD_TYPE_MAP = {
|
|
|
15
15
|
"school-post": "school-post",
|
|
16
16
|
system: "system"
|
|
17
17
|
};
|
|
18
|
+
const REACTION_EMOJI_MAP = {
|
|
19
|
+
clap: "\u{1F44F}",
|
|
20
|
+
like: "\u{1F44D}",
|
|
21
|
+
heart: "\u2764\uFE0F",
|
|
22
|
+
dislike: "\u{1F44E}",
|
|
23
|
+
laugh: "\u{1F602}",
|
|
24
|
+
wow: "\u{1F62E}"
|
|
25
|
+
};
|
|
26
|
+
export function mapReactionCountsToEmojis(reactionCounts) {
|
|
27
|
+
if (!reactionCounts) {
|
|
28
|
+
return [];
|
|
29
|
+
}
|
|
30
|
+
const emojis = [];
|
|
31
|
+
for (const [key, count] of Object.entries(reactionCounts)) {
|
|
32
|
+
const emoji = REACTION_EMOJI_MAP[key.toLowerCase()];
|
|
33
|
+
const safeCount = Number(count);
|
|
34
|
+
if (!emoji || !Number.isFinite(safeCount) || safeCount < 1) {
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
for (let i = 0; i < Math.floor(safeCount); i += 1) {
|
|
38
|
+
emojis.push(emoji);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return emojis;
|
|
42
|
+
}
|
|
18
43
|
export const DEFAULT_NOTIFICATION_TABS = [
|
|
19
44
|
{ id: "new", label: "New", unreadOnly: true },
|
|
20
45
|
{ id: "all", label: "All" }
|
|
@@ -46,6 +71,8 @@ export function mapNotificationApiRecord(record, options) {
|
|
|
46
71
|
const state = record.notification_state;
|
|
47
72
|
const type = mapSourceRecordType(record.source_record_type);
|
|
48
73
|
const activityAt = resolveNotificationActivityAt(record);
|
|
74
|
+
const summary = record.summary?.trim() || void 0;
|
|
75
|
+
const reactionEmojis = mapReactionCountsToEmojis(record.reaction_counts);
|
|
49
76
|
return {
|
|
50
77
|
id: String(record.id),
|
|
51
78
|
type,
|
|
@@ -61,7 +88,9 @@ export function mapNotificationApiRecord(record, options) {
|
|
|
61
88
|
}
|
|
62
89
|
}),
|
|
63
90
|
createdAt: activityAt,
|
|
64
|
-
contextLabel: record.
|
|
91
|
+
contextLabel: record.room?.name?.trim() || void 0,
|
|
92
|
+
summary,
|
|
93
|
+
reactionEmojis: reactionEmojis.length > 0 ? reactionEmojis : void 0,
|
|
65
94
|
dismissed: state === "dismissed",
|
|
66
95
|
read: state === "read",
|
|
67
96
|
unread: state === "unread",
|
package/locale/en.json
CHANGED