@things-factory/announcement 10.1.248 → 10.1.250
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-client/announcement-watch.d.ts +6 -0
- package/dist-client/announcement-watch.js +105 -0
- package/dist-client/announcement-watch.js.map +1 -0
- package/dist-client/bootstrap.d.ts +1 -0
- package/dist-client/bootstrap.js +8 -0
- package/dist-client/bootstrap.js.map +1 -0
- package/dist-client/components/announcement-popup.d.ts +20 -0
- package/dist-client/components/announcement-popup.js +98 -0
- package/dist-client/components/announcement-popup.js.map +1 -0
- package/dist-client/components/announcement-receipts.d.ts +30 -0
- package/dist-client/components/announcement-receipts.js +220 -0
- package/dist-client/components/announcement-receipts.js.map +1 -0
- package/dist-client/components/announcement-sheet.d.ts +17 -0
- package/dist-client/components/announcement-sheet.js +224 -0
- package/dist-client/components/announcement-sheet.js.map +1 -0
- package/dist-client/components/audience-picker.d.ts +32 -0
- package/dist-client/components/audience-picker.js +229 -0
- package/dist-client/components/audience-picker.js.map +1 -0
- package/dist-client/index.d.ts +1 -0
- package/dist-client/index.js +2 -0
- package/dist-client/index.js.map +1 -0
- package/dist-client/pages/announcement-editor.d.ts +59 -0
- package/dist-client/pages/announcement-editor.js +523 -0
- package/dist-client/pages/announcement-editor.js.map +1 -0
- package/dist-client/pages/announcement-list.d.ts +54 -0
- package/dist-client/pages/announcement-list.js +209 -0
- package/dist-client/pages/announcement-list.js.map +1 -0
- package/dist-client/pages/announcement-manage-list.d.ts +46 -0
- package/dist-client/pages/announcement-manage-list.js +115 -0
- package/dist-client/pages/announcement-manage-list.js.map +1 -0
- package/dist-client/pages/announcement-view.d.ts +30 -0
- package/dist-client/pages/announcement-view.js +182 -0
- package/dist-client/pages/announcement-view.js.map +1 -0
- package/dist-client/route.d.ts +1 -0
- package/dist-client/route.js +18 -0
- package/dist-client/route.js.map +1 -0
- package/dist-client/tsconfig.tsbuildinfo +1 -0
- package/dist-client/utils/announcement-api.d.ts +149 -0
- package/dist-client/utils/announcement-api.js +52 -0
- package/dist-client/utils/announcement-api.js.map +1 -0
- package/dist-client/utils/announcement-badge.d.ts +4 -0
- package/dist-client/utils/announcement-badge.js +47 -0
- package/dist-client/utils/announcement-badge.js.map +1 -0
- package/dist-client/utils/announcement-words.d.ts +11 -0
- package/dist-client/utils/announcement-words.js +23 -0
- package/dist-client/utils/announcement-words.js.map +1 -0
- package/dist-server/announcement-read.d.ts +6 -0
- package/dist-server/announcement-read.js +45 -0
- package/dist-server/announcement-read.js.map +1 -0
- package/dist-server/attachment-reader.d.ts +1 -0
- package/dist-server/attachment-reader.js +13 -0
- package/dist-server/attachment-reader.js.map +1 -0
- package/dist-server/index.d.ts +2 -0
- package/dist-server/index.js +20 -0
- package/dist-server/index.js.map +1 -0
- package/dist-server/role-tasks.d.ts +2 -0
- package/dist-server/role-tasks.js +13 -0
- package/dist-server/role-tasks.js.map +1 -0
- package/dist-server/service/announcement/announcement-resolver.d.ts +38 -0
- package/dist-server/service/announcement/announcement-resolver.js +664 -0
- package/dist-server/service/announcement/announcement-resolver.js.map +1 -0
- package/dist-server/service/announcement/announcement-rules.d.ts +150 -0
- package/dist-server/service/announcement/announcement-rules.js +227 -0
- package/dist-server/service/announcement/announcement-rules.js.map +1 -0
- package/dist-server/service/announcement/announcement-service.d.ts +54 -0
- package/dist-server/service/announcement/announcement-service.js +248 -0
- package/dist-server/service/announcement/announcement-service.js.map +1 -0
- package/dist-server/service/announcement/announcement-types.d.ts +54 -0
- package/dist-server/service/announcement/announcement-types.js +203 -0
- package/dist-server/service/announcement/announcement-types.js.map +1 -0
- package/dist-server/service/announcement/announcement.d.ts +124 -0
- package/dist-server/service/announcement/announcement.js +462 -0
- package/dist-server/service/announcement/announcement.js.map +1 -0
- package/dist-server/service/announcement/index.d.ts +4 -0
- package/dist-server/service/announcement/index.js +8 -0
- package/dist-server/service/announcement/index.js.map +1 -0
- package/dist-server/service/index.d.ts +7 -0
- package/dist-server/service/index.js +23 -0
- package/dist-server/service/index.js.map +1 -0
- package/dist-server/tsconfig.tsbuildinfo +1 -0
- package/package.json +8 -8
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
export type Audience = {
|
|
2
|
+
scope: 'DOMAIN' | 'DEPARTMENT' | 'ROLE' | 'USER' | string;
|
|
3
|
+
targetId: string;
|
|
4
|
+
targetName?: string | null;
|
|
5
|
+
};
|
|
6
|
+
export type AnnouncementLink = {
|
|
7
|
+
label: string;
|
|
8
|
+
url: string;
|
|
9
|
+
};
|
|
10
|
+
export type FileRow = {
|
|
11
|
+
id: string;
|
|
12
|
+
name: string;
|
|
13
|
+
fullpath: string;
|
|
14
|
+
size?: number;
|
|
15
|
+
mimetype?: string;
|
|
16
|
+
};
|
|
17
|
+
export type Receipt = {
|
|
18
|
+
readAt?: string | null;
|
|
19
|
+
acknowledgedAt?: string | null;
|
|
20
|
+
};
|
|
21
|
+
export type Delivery = {
|
|
22
|
+
at: string;
|
|
23
|
+
kind: string;
|
|
24
|
+
receivers: number;
|
|
25
|
+
email: {
|
|
26
|
+
sent: number;
|
|
27
|
+
failed: number;
|
|
28
|
+
};
|
|
29
|
+
teams: {
|
|
30
|
+
sent: number;
|
|
31
|
+
failed: number;
|
|
32
|
+
};
|
|
33
|
+
failed?: boolean;
|
|
34
|
+
};
|
|
35
|
+
export interface AnnouncementItem {
|
|
36
|
+
id: string;
|
|
37
|
+
title: string;
|
|
38
|
+
body?: string | null;
|
|
39
|
+
category: string;
|
|
40
|
+
importance: 'NORMAL' | 'IMPORTANT' | 'URGENT' | string;
|
|
41
|
+
status: 'DRAFT' | 'SCHEDULED' | 'PUBLISHED' | 'WITHDRAWN' | string;
|
|
42
|
+
startsAt?: string | null;
|
|
43
|
+
endsAt?: string | null;
|
|
44
|
+
pinned: boolean;
|
|
45
|
+
requiresAcknowledgement: boolean;
|
|
46
|
+
acknowledgeBy?: string | null;
|
|
47
|
+
popup: boolean;
|
|
48
|
+
sendEmail?: boolean;
|
|
49
|
+
deliveries?: Delivery[] | null;
|
|
50
|
+
links?: AnnouncementLink[] | null;
|
|
51
|
+
version: number;
|
|
52
|
+
publishedAt?: string | null;
|
|
53
|
+
audienceSize?: number | null;
|
|
54
|
+
withdrawnAt?: string | null;
|
|
55
|
+
withdrawReason?: string | null;
|
|
56
|
+
readerState?: 'unread' | 'read' | 'to-acknowledge' | 'acknowledged';
|
|
57
|
+
readCount?: number | null;
|
|
58
|
+
acknowledgedCount?: number | null;
|
|
59
|
+
editable?: boolean;
|
|
60
|
+
addressed?: boolean;
|
|
61
|
+
mine?: Receipt | null;
|
|
62
|
+
publisher?: {
|
|
63
|
+
id: string;
|
|
64
|
+
name?: string;
|
|
65
|
+
} | null;
|
|
66
|
+
creator?: {
|
|
67
|
+
id: string;
|
|
68
|
+
name?: string;
|
|
69
|
+
} | null;
|
|
70
|
+
audiences?: Audience[];
|
|
71
|
+
attachments?: FileRow[];
|
|
72
|
+
}
|
|
73
|
+
export interface AnnouncementInput {
|
|
74
|
+
title: string;
|
|
75
|
+
body?: string;
|
|
76
|
+
category?: string;
|
|
77
|
+
importance?: string;
|
|
78
|
+
startsAt?: string | null;
|
|
79
|
+
endsAt?: string | null;
|
|
80
|
+
pinned?: boolean;
|
|
81
|
+
requiresAcknowledgement?: boolean;
|
|
82
|
+
acknowledgeBy?: string | null;
|
|
83
|
+
popup?: boolean;
|
|
84
|
+
sendEmail?: boolean;
|
|
85
|
+
links?: AnnouncementLink[];
|
|
86
|
+
audiences?: Audience[];
|
|
87
|
+
}
|
|
88
|
+
/** The fields a list row needs. */
|
|
89
|
+
export declare const LIST_FIELDS = "id title category importance status startsAt endsAt pinned requiresAcknowledgement acknowledgeBy popup publishedAt version readerState publisher { id name }";
|
|
90
|
+
/** Everything one announcement shows. */
|
|
91
|
+
export declare const FULL_FIELDS = "id title category importance status startsAt endsAt pinned requiresAcknowledgement acknowledgeBy popup publishedAt version readerState publisher { id name } body links sendEmail deliveries audienceSize withdrawnAt withdrawReason editable addressed mine { readAt acknowledgedAt }\n creator { id name } audiences { scope targetId targetName } attachments { id name fullpath size mimetype }";
|
|
92
|
+
/** Tells the badge and an open list that a reading changed. */
|
|
93
|
+
export declare const announcementsChanged: () => boolean;
|
|
94
|
+
export declare const announcementApi: {
|
|
95
|
+
one: (id: string) => Promise<AnnouncementItem | null>;
|
|
96
|
+
summary: () => Promise<{
|
|
97
|
+
unread: number;
|
|
98
|
+
toAcknowledge: number;
|
|
99
|
+
pending: number;
|
|
100
|
+
}>;
|
|
101
|
+
popups: () => Promise<AnnouncementItem[]>;
|
|
102
|
+
read: (id: string) => Promise<AnnouncementItem>;
|
|
103
|
+
acknowledge: (id: string) => Promise<AnnouncementItem>;
|
|
104
|
+
receipts: (id: string) => Promise<{
|
|
105
|
+
userId: string;
|
|
106
|
+
name?: string;
|
|
107
|
+
email?: string;
|
|
108
|
+
listed: boolean;
|
|
109
|
+
notifiedAt?: string;
|
|
110
|
+
readAt?: string;
|
|
111
|
+
acknowledgedAt?: string;
|
|
112
|
+
}[]>;
|
|
113
|
+
revisions: (id: string) => Promise<{
|
|
114
|
+
version: number;
|
|
115
|
+
title: string;
|
|
116
|
+
body?: string;
|
|
117
|
+
rest?: any;
|
|
118
|
+
createdAt: string;
|
|
119
|
+
creator?: {
|
|
120
|
+
name?: string;
|
|
121
|
+
};
|
|
122
|
+
}[]>;
|
|
123
|
+
choices: () => Promise<{
|
|
124
|
+
domainId: string;
|
|
125
|
+
domainName: string;
|
|
126
|
+
departments: {
|
|
127
|
+
id: string;
|
|
128
|
+
name: string;
|
|
129
|
+
note?: string;
|
|
130
|
+
}[];
|
|
131
|
+
roles: {
|
|
132
|
+
id: string;
|
|
133
|
+
name: string;
|
|
134
|
+
note?: string;
|
|
135
|
+
}[];
|
|
136
|
+
}>;
|
|
137
|
+
people: (text: string) => Promise<{
|
|
138
|
+
id: string;
|
|
139
|
+
name: string;
|
|
140
|
+
note?: string;
|
|
141
|
+
}[]>;
|
|
142
|
+
audienceSize: (audiences: Audience[]) => Promise<number>;
|
|
143
|
+
create: (input: AnnouncementInput, attach: File[], uploading?: (fraction: number) => void) => Promise<AnnouncementItem>;
|
|
144
|
+
update: (id: string, input: AnnouncementInput, attach: File[], remove: string[], notifyAgain: boolean, uploading?: (fraction: number) => void) => Promise<AnnouncementItem>;
|
|
145
|
+
publish: (id: string) => Promise<AnnouncementItem>;
|
|
146
|
+
withdraw: (id: string, reason: string) => Promise<AnnouncementItem>;
|
|
147
|
+
remove: (id: string) => Promise<boolean>;
|
|
148
|
+
remind: (id: string) => Promise<number>;
|
|
149
|
+
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* The announcement doors as the screens call them (ADR-0163). Every call throws the server's words when it is refused.
|
|
3
|
+
*/
|
|
4
|
+
import gql from 'graphql-tag';
|
|
5
|
+
import { client, uploadContext } from '@operato/graphql';
|
|
6
|
+
/** The fields a list row needs. */
|
|
7
|
+
export const LIST_FIELDS = `id title category importance status startsAt endsAt pinned requiresAcknowledgement acknowledgeBy popup publishedAt version readerState publisher { id name }`;
|
|
8
|
+
/** Everything one announcement shows. */
|
|
9
|
+
export const FULL_FIELDS = `${LIST_FIELDS} body links sendEmail deliveries audienceSize withdrawnAt withdrawReason editable addressed mine { readAt acknowledgedAt }
|
|
10
|
+
creator { id name } audiences { scope targetId targetName } attachments { id name fullpath size mimetype }`;
|
|
11
|
+
function answer(response, field) {
|
|
12
|
+
if (response.errors?.length)
|
|
13
|
+
throw new Error(response.errors.map((one) => one.message).join(' · '));
|
|
14
|
+
return response.data?.[field];
|
|
15
|
+
}
|
|
16
|
+
async function ask(query, variables, field) {
|
|
17
|
+
return answer(await client.query({ query: gql(query), variables, fetchPolicy: 'network-only' }), field);
|
|
18
|
+
}
|
|
19
|
+
async function change(mutation, variables, field, uploading) {
|
|
20
|
+
return answer(await client.mutate({ mutation: gql(mutation), variables, ...(uploading ? { context: uploadContext(uploading) } : {}) }), field);
|
|
21
|
+
}
|
|
22
|
+
/** Tells the badge and an open list that a reading changed. */
|
|
23
|
+
export const announcementsChanged = () => window.dispatchEvent(new CustomEvent('announcements-changed'));
|
|
24
|
+
export const announcementApi = {
|
|
25
|
+
one: (id) => ask(`query ($id: String!) { announcement(id: $id) { ${FULL_FIELDS} readCount acknowledgedCount } }`, { id }, 'announcement'),
|
|
26
|
+
summary: () => ask(`query { announcementSummary { unread toAcknowledge pending } }`, {}, 'announcementSummary'),
|
|
27
|
+
popups: () => ask(`query { announcementPopups { ${FULL_FIELDS} } }`, {}, 'announcementPopups'),
|
|
28
|
+
read: async (id) => {
|
|
29
|
+
const one = await change(`mutation ($id: String!) { readAnnouncement(id: $id) { id readerState mine { readAt acknowledgedAt } } }`, { id }, 'readAnnouncement');
|
|
30
|
+
announcementsChanged();
|
|
31
|
+
return one;
|
|
32
|
+
},
|
|
33
|
+
acknowledge: async (id) => {
|
|
34
|
+
const one = await change(`mutation ($id: String!) { acknowledgeAnnouncement(id: $id) { id readerState mine { readAt acknowledgedAt } } }`, { id }, 'acknowledgeAnnouncement');
|
|
35
|
+
announcementsChanged();
|
|
36
|
+
return one;
|
|
37
|
+
},
|
|
38
|
+
receipts: (id) => ask(`query ($id: String!) { announcementReceipts(id: $id) { userId name email listed notifiedAt readAt acknowledgedAt } }`, { id }, 'announcementReceipts'),
|
|
39
|
+
revisions: (id) => ask(`query ($id: String!) { announcementRevisions(id: $id) { version title body rest createdAt creator { name } } }`, { id }, 'announcementRevisions'),
|
|
40
|
+
choices: () => ask(`query { announcementAudienceChoices { domainId domainName departments { id name note } roles { id name note } } }`, {}, 'announcementAudienceChoices'),
|
|
41
|
+
people: (text) => ask(`query ($text: String!) { announcementPeople(text: $text) { id name note } }`, { text }, 'announcementPeople'),
|
|
42
|
+
audienceSize: (audiences) => ask(`query ($audiences: [AnnouncementAudienceInput!]!) { announcementAudienceSize(audiences: $audiences) }`, { audiences }, 'announcementAudienceSize'),
|
|
43
|
+
create: (input, attach, uploading) => change(`mutation ($input: AnnouncementInput!, $attach: [Upload!]) { createAnnouncement(input: $input, attach: $attach) { id } }`, { input, attach }, 'createAnnouncement', attach.length ? uploading ?? (() => { }) : undefined),
|
|
44
|
+
update: (id, input, attach, remove, notifyAgain, uploading) => change(`mutation ($id: String!, $input: AnnouncementInput!, $attach: [Upload!], $remove: [String!], $notifyAgain: Boolean) {
|
|
45
|
+
updateAnnouncement(id: $id, input: $input, attach: $attach, remove: $remove, notifyAgain: $notifyAgain) { id status }
|
|
46
|
+
}`, { id, input, attach, remove, notifyAgain }, 'updateAnnouncement', attach.length ? uploading ?? (() => { }) : undefined),
|
|
47
|
+
publish: (id) => change(`mutation ($id: String!) { publishAnnouncement(id: $id) { id status startsAt } }`, { id }, 'publishAnnouncement'),
|
|
48
|
+
withdraw: (id, reason) => change(`mutation ($id: String!, $reason: String!) { withdrawAnnouncement(id: $id, reason: $reason) { id status } }`, { id, reason }, 'withdrawAnnouncement'),
|
|
49
|
+
remove: (id) => change(`mutation ($id: String!) { deleteAnnouncement(id: $id) }`, { id }, 'deleteAnnouncement'),
|
|
50
|
+
remind: (id) => change(`mutation ($id: String!) { remindAnnouncement(id: $id) }`, { id }, 'remindAnnouncement')
|
|
51
|
+
};
|
|
52
|
+
//# sourceMappingURL=announcement-api.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"announcement-api.js","sourceRoot":"","sources":["../../client/utils/announcement-api.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * The announcement doors as the screens call them (ADR-0163). Every call throws the server's words when it is refused.\n */\nimport gql from 'graphql-tag'\n\nimport { client, uploadContext } from '@operato/graphql'\n\nexport type Audience = { scope: 'DOMAIN' | 'DEPARTMENT' | 'ROLE' | 'USER' | string; targetId: string; targetName?: string | null }\nexport type AnnouncementLink = { label: string; url: string }\nexport type FileRow = { id: string; name: string; fullpath: string; size?: number; mimetype?: string }\nexport type Receipt = { readAt?: string | null; acknowledgedAt?: string | null }\nexport type Delivery = { at: string; kind: string; receivers: number; email: { sent: number; failed: number }; teams: { sent: number; failed: number }; failed?: boolean }\n\nexport interface AnnouncementItem {\n id: string\n title: string\n body?: string | null\n category: string\n importance: 'NORMAL' | 'IMPORTANT' | 'URGENT' | string\n status: 'DRAFT' | 'SCHEDULED' | 'PUBLISHED' | 'WITHDRAWN' | string\n startsAt?: string | null\n endsAt?: string | null\n pinned: boolean\n requiresAcknowledgement: boolean\n acknowledgeBy?: string | null\n popup: boolean\n sendEmail?: boolean\n deliveries?: Delivery[] | null\n links?: AnnouncementLink[] | null\n version: number\n publishedAt?: string | null\n audienceSize?: number | null\n withdrawnAt?: string | null\n withdrawReason?: string | null\n readerState?: 'unread' | 'read' | 'to-acknowledge' | 'acknowledged'\n readCount?: number | null\n acknowledgedCount?: number | null\n editable?: boolean\n addressed?: boolean\n mine?: Receipt | null\n publisher?: { id: string; name?: string } | null\n creator?: { id: string; name?: string } | null\n audiences?: Audience[]\n attachments?: FileRow[]\n}\n\nexport interface AnnouncementInput {\n title: string\n body?: string\n category?: string\n importance?: string\n startsAt?: string | null\n endsAt?: string | null\n pinned?: boolean\n requiresAcknowledgement?: boolean\n acknowledgeBy?: string | null\n popup?: boolean\n sendEmail?: boolean\n links?: AnnouncementLink[]\n audiences?: Audience[]\n}\n\n/** The fields a list row needs. */\nexport const LIST_FIELDS = `id title category importance status startsAt endsAt pinned requiresAcknowledgement acknowledgeBy popup publishedAt version readerState publisher { id name }`\n\n/** Everything one announcement shows. */\nexport const FULL_FIELDS = `${LIST_FIELDS} body links sendEmail deliveries audienceSize withdrawnAt withdrawReason editable addressed mine { readAt acknowledgedAt }\n creator { id name } audiences { scope targetId targetName } attachments { id name fullpath size mimetype }`\n\nfunction answer<T>(response: any, field: string): T {\n if (response.errors?.length) throw new Error(response.errors.map((one: any) => one.message).join(' · '))\n return response.data?.[field] as T\n}\n\nasync function ask<T>(query: string, variables: Record<string, unknown>, field: string): Promise<T> {\n return answer<T>(await client.query({ query: gql(query), variables, fetchPolicy: 'network-only' }), field)\n}\n\nasync function change<T>(mutation: string, variables: Record<string, unknown>, field: string, uploading?: (fraction: number) => void): Promise<T> {\n return answer<T>(await client.mutate({ mutation: gql(mutation), variables, ...(uploading ? { context: uploadContext(uploading) } : {}) }), field)\n}\n\n/** Tells the badge and an open list that a reading changed. */\nexport const announcementsChanged = () => window.dispatchEvent(new CustomEvent('announcements-changed'))\n\nexport const announcementApi = {\n one: (id: string) => ask<AnnouncementItem | null>(`query ($id: String!) { announcement(id: $id) { ${FULL_FIELDS} readCount acknowledgedCount } }`, { id }, 'announcement'),\n summary: () => ask<{ unread: number; toAcknowledge: number; pending: number }>(`query { announcementSummary { unread toAcknowledge pending } }`, {}, 'announcementSummary'),\n popups: () => ask<AnnouncementItem[]>(`query { announcementPopups { ${FULL_FIELDS} } }`, {}, 'announcementPopups'),\n read: async (id: string) => {\n const one = await change<AnnouncementItem>(`mutation ($id: String!) { readAnnouncement(id: $id) { id readerState mine { readAt acknowledgedAt } } }`, { id }, 'readAnnouncement')\n announcementsChanged()\n return one\n },\n acknowledge: async (id: string) => {\n const one = await change<AnnouncementItem>(`mutation ($id: String!) { acknowledgeAnnouncement(id: $id) { id readerState mine { readAt acknowledgedAt } } }`, { id }, 'acknowledgeAnnouncement')\n announcementsChanged()\n return one\n },\n receipts: (id: string) =>\n ask<{ userId: string; name?: string; email?: string; listed: boolean; notifiedAt?: string; readAt?: string; acknowledgedAt?: string }[]>(\n `query ($id: String!) { announcementReceipts(id: $id) { userId name email listed notifiedAt readAt acknowledgedAt } }`,\n { id },\n 'announcementReceipts'\n ),\n revisions: (id: string) =>\n ask<{ version: number; title: string; body?: string; rest?: any; createdAt: string; creator?: { name?: string } }[]>(\n `query ($id: String!) { announcementRevisions(id: $id) { version title body rest createdAt creator { name } } }`,\n { id },\n 'announcementRevisions'\n ),\n choices: () =>\n ask<{ domainId: string; domainName: string; departments: { id: string; name: string; note?: string }[]; roles: { id: string; name: string; note?: string }[] }>(\n `query { announcementAudienceChoices { domainId domainName departments { id name note } roles { id name note } } }`,\n {},\n 'announcementAudienceChoices'\n ),\n people: (text: string) => ask<{ id: string; name: string; note?: string }[]>(`query ($text: String!) { announcementPeople(text: $text) { id name note } }`, { text }, 'announcementPeople'),\n audienceSize: (audiences: Audience[]) =>\n ask<number>(`query ($audiences: [AnnouncementAudienceInput!]!) { announcementAudienceSize(audiences: $audiences) }`, { audiences }, 'announcementAudienceSize'),\n create: (input: AnnouncementInput, attach: File[], uploading?: (fraction: number) => void) =>\n change<AnnouncementItem>(\n `mutation ($input: AnnouncementInput!, $attach: [Upload!]) { createAnnouncement(input: $input, attach: $attach) { id } }`,\n { input, attach },\n 'createAnnouncement',\n attach.length ? uploading ?? (() => {}) : undefined\n ),\n update: (id: string, input: AnnouncementInput, attach: File[], remove: string[], notifyAgain: boolean, uploading?: (fraction: number) => void) =>\n change<AnnouncementItem>(\n `mutation ($id: String!, $input: AnnouncementInput!, $attach: [Upload!], $remove: [String!], $notifyAgain: Boolean) {\n updateAnnouncement(id: $id, input: $input, attach: $attach, remove: $remove, notifyAgain: $notifyAgain) { id status }\n }`,\n { id, input, attach, remove, notifyAgain },\n 'updateAnnouncement',\n attach.length ? uploading ?? (() => {}) : undefined\n ),\n publish: (id: string) => change<AnnouncementItem>(`mutation ($id: String!) { publishAnnouncement(id: $id) { id status startsAt } }`, { id }, 'publishAnnouncement'),\n withdraw: (id: string, reason: string) =>\n change<AnnouncementItem>(`mutation ($id: String!, $reason: String!) { withdrawAnnouncement(id: $id, reason: $reason) { id status } }`, { id, reason }, 'withdrawAnnouncement'),\n remove: (id: string) => change<boolean>(`mutation ($id: String!) { deleteAnnouncement(id: $id) }`, { id }, 'deleteAnnouncement'),\n remind: (id: string) => change<number>(`mutation ($id: String!) { remindAnnouncement(id: $id) }`, { id }, 'remindAnnouncement')\n}\n"]}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** Asks the server again and tells every badge. A count that could not be asked is not zero: it draws nothing. */
|
|
2
|
+
export declare function refreshAnnouncementBadge(): Promise<void>;
|
|
3
|
+
/** One badge's counts. The menu asks for a new one when it draws again; the one before it then ends, as worklist's do. */
|
|
4
|
+
export declare function generateAnnouncementBadge(): AsyncGenerator<number | undefined, void, unknown>;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* The count the menu shows on 공지 - current announcements still owed reading or acknowledging (ADR-0163 decision 5).
|
|
3
|
+
* An application puts `generateAnnouncementBadge` on its menu entry; the count is asked again whenever an announcement
|
|
4
|
+
* arrives or a reading changes.
|
|
5
|
+
*/
|
|
6
|
+
import { announcementApi } from './announcement-api.js';
|
|
7
|
+
let count;
|
|
8
|
+
/* One more each time the count is asked again - a badge that was busy drawing the last one still sees the next. */
|
|
9
|
+
let version = 0;
|
|
10
|
+
const waiting = new Set();
|
|
11
|
+
/** Asks the server again and tells every badge. A count that could not be asked is not zero: it draws nothing. */
|
|
12
|
+
export async function refreshAnnouncementBadge() {
|
|
13
|
+
try {
|
|
14
|
+
count = (await announcementApi.summary()).pending;
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
count = undefined;
|
|
18
|
+
}
|
|
19
|
+
version++;
|
|
20
|
+
for (const wake of [...waiting])
|
|
21
|
+
wake();
|
|
22
|
+
}
|
|
23
|
+
let generation = 0;
|
|
24
|
+
/** One badge's counts. The menu asks for a new one when it draws again; the one before it then ends, as worklist's do. */
|
|
25
|
+
export async function* generateAnnouncementBadge() {
|
|
26
|
+
const mine = ++generation;
|
|
27
|
+
for (const wake of [...waiting])
|
|
28
|
+
wake();
|
|
29
|
+
let seen = version;
|
|
30
|
+
yield count;
|
|
31
|
+
while (true) {
|
|
32
|
+
if (seen === version) {
|
|
33
|
+
await new Promise(resolve => {
|
|
34
|
+
const wake = () => {
|
|
35
|
+
waiting.delete(wake);
|
|
36
|
+
resolve();
|
|
37
|
+
};
|
|
38
|
+
waiting.add(wake);
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
if (mine !== generation)
|
|
42
|
+
return;
|
|
43
|
+
seen = version;
|
|
44
|
+
yield count;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=announcement-badge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"announcement-badge.js","sourceRoot":"","sources":["../../client/utils/announcement-badge.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAA;AAEvD,IAAI,KAAyB,CAAA;AAC7B,mHAAmH;AACnH,IAAI,OAAO,GAAG,CAAC,CAAA;AACf,MAAM,OAAO,GAAG,IAAI,GAAG,EAAc,CAAA;AAErC,kHAAkH;AAClH,MAAM,CAAC,KAAK,UAAU,wBAAwB;IAC5C,IAAI,CAAC;QACH,KAAK,GAAG,CAAC,MAAM,eAAe,CAAC,OAAO,EAAE,CAAC,CAAC,OAAO,CAAA;IACnD,CAAC;IAAC,MAAM,CAAC;QACP,KAAK,GAAG,SAAS,CAAA;IACnB,CAAC;IACD,OAAO,EAAE,CAAA;IACT,KAAK,MAAM,IAAI,IAAI,CAAC,GAAG,OAAO,CAAC;QAAE,IAAI,EAAE,CAAA;AACzC,CAAC;AAED,IAAI,UAAU,GAAG,CAAC,CAAA;AAElB,0HAA0H;AAC1H,MAAM,CAAC,KAAK,SAAS,CAAC,CAAC,yBAAyB;IAC9C,MAAM,IAAI,GAAG,EAAE,UAAU,CAAA;IACzB,KAAK,MAAM,IAAI,IAAI,CAAC,GAAG,OAAO,CAAC;QAAE,IAAI,EAAE,CAAA;IACvC,IAAI,IAAI,GAAG,OAAO,CAAA;IAClB,MAAM,KAAK,CAAA;IACX,OAAO,IAAI,EAAE,CAAC;QACZ,IAAI,IAAI,KAAK,OAAO,EAAE,CAAC;YACrB,MAAM,IAAI,OAAO,CAAO,OAAO,CAAC,EAAE;gBAChC,MAAM,IAAI,GAAG,GAAG,EAAE;oBAChB,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;oBACpB,OAAO,EAAE,CAAA;gBACX,CAAC,CAAA;gBACD,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;YACnB,CAAC,CAAC,CAAA;QACJ,CAAC;QACD,IAAI,IAAI,KAAK,UAAU;YAAE,OAAM;QAC/B,IAAI,GAAG,OAAO,CAAA;QACd,MAAM,KAAK,CAAA;IACb,CAAC;AACH,CAAC","sourcesContent":["/*\n * The count the menu shows on 공지 - current announcements still owed reading or acknowledging (ADR-0163 decision 5).\n * An application puts `generateAnnouncementBadge` on its menu entry; the count is asked again whenever an announcement\n * arrives or a reading changes.\n */\nimport { announcementApi } from './announcement-api.js'\n\nlet count: number | undefined\n/* One more each time the count is asked again - a badge that was busy drawing the last one still sees the next. */\nlet version = 0\nconst waiting = new Set<() => void>()\n\n/** Asks the server again and tells every badge. A count that could not be asked is not zero: it draws nothing. */\nexport async function refreshAnnouncementBadge(): Promise<void> {\n try {\n count = (await announcementApi.summary()).pending\n } catch {\n count = undefined\n }\n version++\n for (const wake of [...waiting]) wake()\n}\n\nlet generation = 0\n\n/** One badge's counts. The menu asks for a new one when it draws again; the one before it then ends, as worklist's do. */\nexport async function* generateAnnouncementBadge() {\n const mine = ++generation\n for (const wake of [...waiting]) wake()\n let seen = version\n yield count\n while (true) {\n if (seen === version) {\n await new Promise<void>(resolve => {\n const wake = () => {\n waiting.delete(wake)\n resolve()\n }\n waiting.add(wake)\n })\n }\n if (mine !== generation) return\n seen = version\n yield count\n }\n}\n"]}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare const t: (key: string, options?: Record<string, unknown>) => string;
|
|
2
|
+
export declare const CATEGORIES: string[];
|
|
3
|
+
export declare const IMPORTANCES: string[];
|
|
4
|
+
export declare const categoryWord: (code?: string | null) => string;
|
|
5
|
+
export declare const importanceWord: (code?: string | null) => string;
|
|
6
|
+
export declare const statusWord: (code?: string | null) => string;
|
|
7
|
+
export declare const readerStateWord: (state?: string | null) => string;
|
|
8
|
+
/** A day and time as the reader's locale writes it. */
|
|
9
|
+
export declare function when(value?: string | Date | null, withTime?: boolean): string;
|
|
10
|
+
/** The words of a refusal the server gave, or the fallback. */
|
|
11
|
+
export declare const reasonOf: (error: unknown) => string;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* The words and marks of announcements - one place for the list, the screen and the popup.
|
|
3
|
+
*/
|
|
4
|
+
import { i18next } from '@operato/i18n';
|
|
5
|
+
export const t = (key, options = {}) => String(i18next.t(key, options));
|
|
6
|
+
export const CATEGORIES = ['GENERAL', 'HR', 'WELFARE', 'EVENT', 'SYSTEM', 'SECURITY'];
|
|
7
|
+
export const IMPORTANCES = ['NORMAL', 'IMPORTANT', 'URGENT'];
|
|
8
|
+
export const categoryWord = (code) => (code ? t(`announcement.category.${code}`, { defaultValue: code }) : '');
|
|
9
|
+
export const importanceWord = (code) => (code ? t(`announcement.importance.${code}`, { defaultValue: code }) : '');
|
|
10
|
+
export const statusWord = (code) => (code ? t(`announcement.status.${code}`, { defaultValue: code }) : '');
|
|
11
|
+
export const readerStateWord = (state) => (state ? t(`announcement.reader-state.${state}`, { defaultValue: state }) : '');
|
|
12
|
+
/** A day and time as the reader's locale writes it. */
|
|
13
|
+
export function when(value, withTime = true) {
|
|
14
|
+
if (!value)
|
|
15
|
+
return '';
|
|
16
|
+
const date = new Date(value);
|
|
17
|
+
return withTime
|
|
18
|
+
? date.toLocaleString(i18next.language, { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' })
|
|
19
|
+
: date.toLocaleDateString(i18next.language, { year: 'numeric', month: '2-digit', day: '2-digit' });
|
|
20
|
+
}
|
|
21
|
+
/** The words of a refusal the server gave, or the fallback. */
|
|
22
|
+
export const reasonOf = (error) => error?.message || t('announcement.text.failed');
|
|
23
|
+
//# sourceMappingURL=announcement-words.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"announcement-words.js","sourceRoot":"","sources":["../../client/utils/announcement-words.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAA;AAEvC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,GAAW,EAAE,UAAmC,EAAE,EAAE,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC,CAAA;AAExG,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,SAAS,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,QAAQ,EAAE,UAAU,CAAC,CAAA;AACrF,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,QAAQ,EAAE,WAAW,EAAE,QAAQ,CAAC,CAAA;AAE5D,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,IAAoB,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,yBAAyB,IAAI,EAAE,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAA;AAC9H,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,IAAoB,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,2BAA2B,IAAI,EAAE,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAA;AAClI,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,IAAoB,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,uBAAuB,IAAI,EAAE,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAA;AAC1H,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,KAAqB,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,6BAA6B,KAAK,EAAE,EAAE,EAAE,YAAY,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAA;AAEzI,uDAAuD;AACvD,MAAM,UAAU,IAAI,CAAC,KAA4B,EAAE,QAAQ,GAAG,IAAI;IAChE,IAAI,CAAC,KAAK;QAAE,OAAO,EAAE,CAAA;IACrB,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,KAAK,CAAC,CAAA;IAC5B,OAAO,QAAQ;QACb,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,SAAS,EAAE,GAAG,EAAE,SAAS,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC;QAClI,CAAC,CAAC,IAAI,CAAC,kBAAkB,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,SAAS,EAAE,GAAG,EAAE,SAAS,EAAE,CAAC,CAAA;AACtG,CAAC;AAED,+DAA+D;AAC/D,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,KAAc,EAAE,EAAE,CAAE,KAAe,EAAE,OAAO,IAAI,CAAC,CAAC,0BAA0B,CAAC,CAAA","sourcesContent":["/*\n * The words and marks of announcements - one place for the list, the screen and the popup.\n */\nimport { i18next } from '@operato/i18n'\n\nexport const t = (key: string, options: Record<string, unknown> = {}) => String(i18next.t(key, options))\n\nexport const CATEGORIES = ['GENERAL', 'HR', 'WELFARE', 'EVENT', 'SYSTEM', 'SECURITY']\nexport const IMPORTANCES = ['NORMAL', 'IMPORTANT', 'URGENT']\n\nexport const categoryWord = (code?: string | null) => (code ? t(`announcement.category.${code}`, { defaultValue: code }) : '')\nexport const importanceWord = (code?: string | null) => (code ? t(`announcement.importance.${code}`, { defaultValue: code }) : '')\nexport const statusWord = (code?: string | null) => (code ? t(`announcement.status.${code}`, { defaultValue: code }) : '')\nexport const readerStateWord = (state?: string | null) => (state ? t(`announcement.reader-state.${state}`, { defaultValue: state }) : '')\n\n/** A day and time as the reader's locale writes it. */\nexport function when(value?: string | Date | null, withTime = true): string {\n if (!value) return ''\n const date = new Date(value)\n return withTime\n ? date.toLocaleString(i18next.language, { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' })\n : date.toLocaleDateString(i18next.language, { year: 'numeric', month: '2-digit', day: '2-digit' })\n}\n\n/** The words of a refusal the server gave, or the fallback. */\nexport const reasonOf = (error: unknown) => (error as Error)?.message || t('announcement.text.failed')\n"]}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type ListRead } from '@things-factory/ai-assistant';
|
|
2
|
+
export declare const ANNOUNCEMENT_LIST_READ: ListRead;
|
|
3
|
+
/** The keys an application's assistant mode names in its `readTargets` to read announcements. */
|
|
4
|
+
export declare const ANNOUNCEMENT_READ_TARGETS: string[];
|
|
5
|
+
/** At boot: the assistant may read the announcements, in a mode that names them. */
|
|
6
|
+
export declare function registerAnnouncementReads(): void;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ANNOUNCEMENT_READ_TARGETS = exports.ANNOUNCEMENT_LIST_READ = void 0;
|
|
4
|
+
exports.registerAnnouncementReads = registerAnnouncementReads;
|
|
5
|
+
const ai_assistant_1 = require("@things-factory/ai-assistant");
|
|
6
|
+
const announcement_rules_1 = require("./service/announcement/announcement-rules");
|
|
7
|
+
/*
|
|
8
|
+
* 공지사항 as the assistant reads it (ADR-0163 decision 8) - the readers' own list query, as the person: only what is
|
|
9
|
+
* addressed to them and out, with their own reading of each. So it is `mine` - a card made of it shows each viewer
|
|
10
|
+
* their own announcements - and it is a source for 나의 대시보드 cards (ADR-0160) as every read target is.
|
|
11
|
+
*
|
|
12
|
+
* `status` narrows by the reader's state (unread, to acknowledge …), which the server works out from the reader's own
|
|
13
|
+
* receipts; `period` (current | past) is a column, a card's match keeps the current ones.
|
|
14
|
+
*/
|
|
15
|
+
exports.ANNOUNCEMENT_LIST_READ = {
|
|
16
|
+
key: 'announcement.list',
|
|
17
|
+
title: 'announcement.title.list',
|
|
18
|
+
name: 'Announcements (공지사항) addressed to the person - current and past, each with their own reading state (unread, read, to-acknowledge, acknowledged)',
|
|
19
|
+
listQuery: 'announcements',
|
|
20
|
+
selection: 'id title body category importance pinned requiresAcknowledgement acknowledgeBy publishedAt endsAt period readerState publisher { name }',
|
|
21
|
+
columns: [
|
|
22
|
+
{ name: 'title', type: 'string', labelKey: 'announcement.field.title' },
|
|
23
|
+
{ name: 'category', type: 'string', labelKey: 'announcement.field.category', words: 'announcement.category.' },
|
|
24
|
+
{ name: 'importance', type: 'string', labelKey: 'announcement.field.importance', words: 'announcement.importance.' },
|
|
25
|
+
{ name: 'readerState', type: 'string', labelKey: 'announcement.field.reader-state', words: 'announcement.reader-state.' },
|
|
26
|
+
{ name: 'period', type: 'string', labelKey: 'announcement.text.period', words: 'announcement.period.' },
|
|
27
|
+
{ name: 'publishedAt', type: 'date', labelKey: 'announcement.field.published-at' },
|
|
28
|
+
{ name: 'endsAt', type: 'date', labelKey: 'announcement.field.ends-at' },
|
|
29
|
+
{ name: 'acknowledgeBy', type: 'date', labelKey: 'announcement.field.acknowledge-by' },
|
|
30
|
+
{ name: 'publisher', path: 'publisher.name', type: 'string', labelKey: 'announcement.field.publisher' },
|
|
31
|
+
{ name: 'body', type: 'string' }
|
|
32
|
+
],
|
|
33
|
+
dateField: 'publishedAt',
|
|
34
|
+
status: { field: 'readerState', values: [...announcement_rules_1.READER_STATES] },
|
|
35
|
+
search: 'fields title, body, category',
|
|
36
|
+
route: 'announcement-list',
|
|
37
|
+
mine: true
|
|
38
|
+
};
|
|
39
|
+
/** The keys an application's assistant mode names in its `readTargets` to read announcements. */
|
|
40
|
+
exports.ANNOUNCEMENT_READ_TARGETS = [exports.ANNOUNCEMENT_LIST_READ.key];
|
|
41
|
+
/** At boot: the assistant may read the announcements, in a mode that names them. */
|
|
42
|
+
function registerAnnouncementReads() {
|
|
43
|
+
(0, ai_assistant_1.registerReadTarget)((0, ai_assistant_1.listReadTarget)(exports.ANNOUNCEMENT_LIST_READ));
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=announcement-read.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"announcement-read.js","sourceRoot":"","sources":["../server/announcement-read.ts"],"names":[],"mappings":";;;AAyCA,8DAEC;AA3CD,+DAAgG;AAEhG,kFAAyE;AAEzE;;;;;;;GAOG;AACU,QAAA,sBAAsB,GAAa;IAC9C,GAAG,EAAE,mBAAmB;IACxB,KAAK,EAAE,yBAAyB;IAChC,IAAI,EAAE,iJAAiJ;IACvJ,SAAS,EAAE,eAAe;IAC1B,SAAS,EAAE,yIAAyI;IACpJ,OAAO,EAAE;QACP,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,0BAA0B,EAAE;QACvE,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,6BAA6B,EAAE,KAAK,EAAE,wBAAwB,EAAE;QAC9G,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,+BAA+B,EAAE,KAAK,EAAE,0BAA0B,EAAE;QACpH,EAAE,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,iCAAiC,EAAE,KAAK,EAAE,4BAA4B,EAAE;QACzH,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,0BAA0B,EAAE,KAAK,EAAE,sBAAsB,EAAE;QACvG,EAAE,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,iCAAiC,EAAE;QAClF,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,4BAA4B,EAAE;QACxE,EAAE,IAAI,EAAE,eAAe,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,mCAAmC,EAAE;QACtF,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,gBAAgB,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,8BAA8B,EAAE;QACvG,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE;KACjC;IACD,SAAS,EAAE,aAAa;IACxB,MAAM,EAAE,EAAE,KAAK,EAAE,aAAa,EAAE,MAAM,EAAE,CAAC,GAAG,kCAAa,CAAC,EAAE;IAC5D,MAAM,EAAE,8BAA8B;IACtC,KAAK,EAAE,mBAAmB;IAC1B,IAAI,EAAE,IAAI;CACX,CAAA;AAED,iGAAiG;AACpF,QAAA,yBAAyB,GAAG,CAAC,8BAAsB,CAAC,GAAG,CAAC,CAAA;AAErE,oFAAoF;AACpF,SAAgB,yBAAyB;IACvC,IAAA,iCAAkB,EAAC,IAAA,6BAAc,EAAC,8BAAsB,CAAC,CAAC,CAAA;AAC5D,CAAC","sourcesContent":["import { listReadTarget, registerReadTarget, type ListRead } from '@things-factory/ai-assistant'\n\nimport { READER_STATES } from './service/announcement/announcement-rules'\n\n/*\n * 공지사항 as the assistant reads it (ADR-0163 decision 8) - the readers' own list query, as the person: only what is\n * addressed to them and out, with their own reading of each. So it is `mine` - a card made of it shows each viewer\n * their own announcements - and it is a source for 나의 대시보드 cards (ADR-0160) as every read target is.\n *\n * `status` narrows by the reader's state (unread, to acknowledge …), which the server works out from the reader's own\n * receipts; `period` (current | past) is a column, a card's match keeps the current ones.\n */\nexport const ANNOUNCEMENT_LIST_READ: ListRead = {\n key: 'announcement.list',\n title: 'announcement.title.list',\n name: 'Announcements (공지사항) addressed to the person - current and past, each with their own reading state (unread, read, to-acknowledge, acknowledged)',\n listQuery: 'announcements',\n selection: 'id title body category importance pinned requiresAcknowledgement acknowledgeBy publishedAt endsAt period readerState publisher { name }',\n columns: [\n { name: 'title', type: 'string', labelKey: 'announcement.field.title' },\n { name: 'category', type: 'string', labelKey: 'announcement.field.category', words: 'announcement.category.' },\n { name: 'importance', type: 'string', labelKey: 'announcement.field.importance', words: 'announcement.importance.' },\n { name: 'readerState', type: 'string', labelKey: 'announcement.field.reader-state', words: 'announcement.reader-state.' },\n { name: 'period', type: 'string', labelKey: 'announcement.text.period', words: 'announcement.period.' },\n { name: 'publishedAt', type: 'date', labelKey: 'announcement.field.published-at' },\n { name: 'endsAt', type: 'date', labelKey: 'announcement.field.ends-at' },\n { name: 'acknowledgeBy', type: 'date', labelKey: 'announcement.field.acknowledge-by' },\n { name: 'publisher', path: 'publisher.name', type: 'string', labelKey: 'announcement.field.publisher' },\n { name: 'body', type: 'string' }\n ],\n dateField: 'publishedAt',\n status: { field: 'readerState', values: [...READER_STATES] },\n search: 'fields title, body, category',\n route: 'announcement-list',\n mine: true\n}\n\n/** The keys an application's assistant mode names in its `readTargets` to read announcements. */\nexport const ANNOUNCEMENT_READ_TARGETS = [ANNOUNCEMENT_LIST_READ.key]\n\n/** At boot: the assistant may read the announcements, in a mode that names them. */\nexport function registerAnnouncementReads(): void {\n registerReadTarget(listReadTarget(ANNOUNCEMENT_LIST_READ))\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function registerAnnouncementFiles(): void;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.registerAnnouncementFiles = registerAnnouncementFiles;
|
|
4
|
+
/*
|
|
5
|
+
* Who may open an announcement's file by its path - whoever may see the announcement (ADR-0163 decision 2): its audience
|
|
6
|
+
* once it is out, its writers. attachment-base has already checked the file is of the request's domain.
|
|
7
|
+
*/
|
|
8
|
+
const attachment_base_1 = require("@things-factory/attachment-base");
|
|
9
|
+
const announcement_service_1 = require("./service/announcement/announcement-service");
|
|
10
|
+
function registerAnnouncementFiles() {
|
|
11
|
+
(0, attachment_base_1.registerAttachmentReader)(announcement_service_1.ANNOUNCEMENT_REF_TYPE, async (attachment, context) => !!attachment.refBy && !!(await (0, announcement_service_1.visibleAnnouncement)(attachment.refBy, context)));
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=attachment-reader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attachment-reader.js","sourceRoot":"","sources":["../server/attachment-reader.ts"],"names":[],"mappings":";;AAQA,8DAEC;AAVD;;;GAGG;AACH,qEAA0E;AAE1E,sFAAwG;AAExG,SAAgB,yBAAyB;IACvC,IAAA,0CAAwB,EAAC,4CAAqB,EAAE,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC,MAAM,IAAA,0CAAmB,EAAC,UAAU,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,CAAA;AAChK,CAAC","sourcesContent":["/*\n * Who may open an announcement's file by its path - whoever may see the announcement (ADR-0163 decision 2): its audience\n * once it is out, its writers. attachment-base has already checked the file is of the request's domain.\n */\nimport { registerAttachmentReader } from '@things-factory/attachment-base'\n\nimport { ANNOUNCEMENT_REF_TYPE, visibleAnnouncement } from './service/announcement/announcement-service'\n\nexport function registerAnnouncementFiles(): void {\n registerAttachmentReader(ANNOUNCEMENT_REF_TYPE, async (attachment, context) => !!attachment.refBy && !!(await visibleAnnouncement(attachment.refBy, context)))\n}\n"]}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ANNOUNCEMENT_READ_TARGETS = exports.ANNOUNCEMENT_LIST_READ = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
tslib_1.__exportStar(require("./service"), exports);
|
|
6
|
+
var announcement_read_1 = require("./announcement-read");
|
|
7
|
+
Object.defineProperty(exports, "ANNOUNCEMENT_LIST_READ", { enumerable: true, get: function () { return announcement_read_1.ANNOUNCEMENT_LIST_READ; } });
|
|
8
|
+
Object.defineProperty(exports, "ANNOUNCEMENT_READ_TARGETS", { enumerable: true, get: function () { return announcement_read_1.ANNOUNCEMENT_READ_TARGETS; } });
|
|
9
|
+
const auth_base_1 = require("@things-factory/auth-base");
|
|
10
|
+
const announcement_read_2 = require("./announcement-read");
|
|
11
|
+
const attachment_reader_1 = require("./attachment-reader");
|
|
12
|
+
const role_tasks_1 = require("./role-tasks");
|
|
13
|
+
const announcement_service_1 = require("./service/announcement/announcement-service");
|
|
14
|
+
for (const task of role_tasks_1.ROLE_TASKS)
|
|
15
|
+
(0, auth_base_1.registerRoleTask)(task);
|
|
16
|
+
(0, attachment_reader_1.registerAnnouncementFiles)();
|
|
17
|
+
(0, announcement_read_2.registerAnnouncementReads)();
|
|
18
|
+
/* Scheduled announcements go out at their start (ADR-0163 decision 4). */
|
|
19
|
+
process.on('bootstrap-module-start', () => (0, announcement_service_1.schedulePublishing)());
|
|
20
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../server/index.ts"],"names":[],"mappings":";;;;AAAA,oDAAyB;AACzB,yDAAuF;AAA9E,2HAAA,sBAAsB,OAAA;AAAE,8HAAA,yBAAyB,OAAA;AAE1D,yDAA4D;AAE5D,2DAA+D;AAC/D,2DAA+D;AAC/D,6CAAyC;AACzC,sFAAgF;AAEhF,KAAK,MAAM,IAAI,IAAI,uBAAU;IAAE,IAAA,4BAAgB,EAAC,IAAI,CAAC,CAAA;AACrD,IAAA,6CAAyB,GAAE,CAAA;AAC3B,IAAA,6CAAyB,GAAE,CAAA;AAE3B,0EAA0E;AAC1E,OAAO,CAAC,EAAE,CAAC,wBAA+B,EAAE,GAAG,EAAE,CAAC,IAAA,yCAAkB,GAAE,CAAC,CAAA","sourcesContent":["export * from './service'\nexport { ANNOUNCEMENT_LIST_READ, ANNOUNCEMENT_READ_TARGETS } from './announcement-read'\n\nimport { registerRoleTask } from '@things-factory/auth-base'\n\nimport { registerAnnouncementReads } from './announcement-read'\nimport { registerAnnouncementFiles } from './attachment-reader'\nimport { ROLE_TASKS } from './role-tasks'\nimport { schedulePublishing } from './service/announcement/announcement-service'\n\nfor (const task of ROLE_TASKS) registerRoleTask(task)\nregisterAnnouncementFiles()\nregisterAnnouncementReads()\n\n/* Scheduled announcements go out at their start (ADR-0163 decision 4). */\nprocess.on('bootstrap-module-start' as any, () => schedulePublishing())\n"]}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* The work this module names, for the seats applications build (ADR-0139 decision 11).
|
|
4
|
+
*
|
|
5
|
+
* Reading announcements is no task: an announcement is read by the people it names (ADR-0163 decision 2). Writing,
|
|
6
|
+
* publishing, withdrawing and seeing who read them is `announcement.writing`.
|
|
7
|
+
*
|
|
8
|
+
* Data only - `server/index.ts` registers it - so an application's tests read it without starting the framework.
|
|
9
|
+
*/
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.ROLE_TASKS = void 0;
|
|
12
|
+
exports.ROLE_TASKS = [{ id: 'announcement.writing', grants: [{ category: 'announcement', axes: ['mutation'] }] }];
|
|
13
|
+
//# sourceMappingURL=role-tasks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"role-tasks.js","sourceRoot":"","sources":["../server/role-tasks.ts"],"names":[],"mappings":";AAAA;;;;;;;GAOG;;;AAIU,QAAA,UAAU,GAAe,CAAC,EAAE,EAAE,EAAE,sBAAsB,EAAE,MAAM,EAAE,CAAC,EAAE,QAAQ,EAAE,cAAc,EAAE,IAAI,EAAE,CAAC,UAAU,CAAC,EAAE,CAAC,EAAE,CAAC,CAAA","sourcesContent":["/*\n * The work this module names, for the seats applications build (ADR-0139 decision 11).\n *\n * Reading announcements is no task: an announcement is read by the people it names (ADR-0163 decision 2). Writing,\n * publishing, withdrawing and seeing who read them is `announcement.writing`.\n *\n * Data only - `server/index.ts` registers it - so an application's tests read it without starting the framework.\n */\n\nimport type { RoleTask } from '@things-factory/auth-base'\n\nexport const ROLE_TASKS: RoleTask[] = [{ id: 'announcement.writing', grants: [{ category: 'announcement', axes: ['mutation'] }] }]\n"]}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { FileUpload } from 'graphql-upload/GraphQLUpload.js';
|
|
2
|
+
import { Attachment } from '@things-factory/attachment-base';
|
|
3
|
+
import { User } from '@things-factory/auth-base';
|
|
4
|
+
import { ListParam } from '@things-factory/shell';
|
|
5
|
+
import { Announcement, AnnouncementAudience, AnnouncementReceipt, AnnouncementRevision } from './announcement';
|
|
6
|
+
import { AnnouncementAudienceChoices, AnnouncementAudienceInput, AnnouncementChoice, AnnouncementInput, AnnouncementList, AnnouncementReceiptRow, AnnouncementSummary } from './announcement-types';
|
|
7
|
+
export declare class AnnouncementResolver {
|
|
8
|
+
announcements(params: ListParam, context: ResolverContext): Promise<AnnouncementList>;
|
|
9
|
+
announcement(id: string, context: ResolverContext): Promise<Announcement | undefined>;
|
|
10
|
+
announcementSummary(context: ResolverContext): Promise<AnnouncementSummary>;
|
|
11
|
+
announcementPopups(context: ResolverContext): Promise<Announcement[]>;
|
|
12
|
+
readAnnouncement(id: string, context: ResolverContext): Promise<Announcement>;
|
|
13
|
+
acknowledgeAnnouncement(id: string, context: ResolverContext): Promise<Announcement>;
|
|
14
|
+
private receive;
|
|
15
|
+
managedAnnouncements(params: ListParam, context: ResolverContext): Promise<AnnouncementList>;
|
|
16
|
+
announcementReceipts(id: string, context: ResolverContext): Promise<AnnouncementReceiptRow[]>;
|
|
17
|
+
announcementRevisions(id: string, context: ResolverContext): Promise<AnnouncementRevision[]>;
|
|
18
|
+
announcementAudienceChoices(context: ResolverContext): Promise<AnnouncementAudienceChoices>;
|
|
19
|
+
announcementPeople(text: string, context: ResolverContext): Promise<AnnouncementChoice[]>;
|
|
20
|
+
announcementAudienceSize(audiences: AnnouncementAudienceInput[], context: ResolverContext): Promise<number>;
|
|
21
|
+
createAnnouncement(input: AnnouncementInput, attach: FileUpload[], context: ResolverContext): Promise<Announcement>;
|
|
22
|
+
updateAnnouncement(id: string, input: AnnouncementInput, attach: FileUpload[], remove: string[], notifyAgain: boolean, context: ResolverContext): Promise<Announcement>;
|
|
23
|
+
publishAnnouncement(id: string, context: ResolverContext): Promise<Announcement>;
|
|
24
|
+
withdrawAnnouncement(id: string, reason: string, context: ResolverContext): Promise<Announcement>;
|
|
25
|
+
deleteAnnouncement(id: string, context: ResolverContext): Promise<boolean>;
|
|
26
|
+
remindAnnouncement(id: string, context: ResolverContext): Promise<number>;
|
|
27
|
+
audiences(one: Announcement): Promise<AnnouncementAudience[]>;
|
|
28
|
+
attachments(one: Announcement, context: ResolverContext): Promise<Attachment[]>;
|
|
29
|
+
mine(one: Announcement, context: ResolverContext): Promise<AnnouncementReceipt | null>;
|
|
30
|
+
addressed(one: Announcement, context: ResolverContext): Promise<boolean>;
|
|
31
|
+
period(one: Announcement): string | undefined;
|
|
32
|
+
readerState(one: Announcement, context: ResolverContext): Promise<string>;
|
|
33
|
+
readCount(one: Announcement, context: ResolverContext): Promise<number | null>;
|
|
34
|
+
acknowledgedCount(one: Announcement, context: ResolverContext): Promise<number | null>;
|
|
35
|
+
editable(one: Announcement, context: ResolverContext): Promise<boolean>;
|
|
36
|
+
publisher(one: Announcement): Promise<User | null>;
|
|
37
|
+
creator(one: Announcement): Promise<User | null>;
|
|
38
|
+
}
|