@things-factory/announcement 10.1.258 → 10.1.260

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.
Files changed (31) hide show
  1. package/client/index.ts +1 -0
  2. package/client/pages/announcement-list.ts +28 -43
  3. package/client/utils/announcement-list-query.ts +8 -0
  4. package/dist-client/index.d.ts +1 -0
  5. package/dist-client/index.js +1 -0
  6. package/dist-client/index.js.map +1 -1
  7. package/dist-client/pages/announcement-list.d.ts +0 -4
  8. package/dist-client/pages/announcement-list.js +28 -48
  9. package/dist-client/pages/announcement-list.js.map +1 -1
  10. package/dist-client/tsconfig.tsbuildinfo +1 -1
  11. package/dist-client/utils/announcement-list-query.d.ts +5 -0
  12. package/dist-client/utils/announcement-list-query.js +8 -0
  13. package/dist-client/utils/announcement-list-query.js.map +1 -0
  14. package/dist-server/service/announcement/announcement-rules.d.ts +0 -29
  15. package/dist-server/service/announcement/announcement-rules.js +1 -19
  16. package/dist-server/service/announcement/announcement-rules.js.map +1 -1
  17. package/dist-server/service/announcement/announcement-service.js +2 -14
  18. package/dist-server/service/announcement/announcement-service.js.map +1 -1
  19. package/dist-server/tsconfig.tsbuildinfo +1 -1
  20. package/package.json +9 -9
  21. package/server/service/announcement/announcement-rules.ts +0 -20
  22. package/server/service/announcement/announcement-service.ts +4 -15
  23. package/test/announcement-persistence.test.ts +3 -1
  24. package/test/announcement-rules.test.ts +0 -15
  25. package/test/list-query-state.test.ts +25 -0
  26. package/translations/en.json +3 -3
  27. package/translations/es.json +3 -3
  28. package/translations/ja.json +3 -3
  29. package/translations/ko.json +3 -3
  30. package/translations/ms.json +3 -3
  31. package/translations/zh.json +3 -3
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@things-factory/announcement",
3
- "version": "10.1.258",
3
+ "version": "10.1.260",
4
4
  "main": "dist-server/index.js",
5
5
  "browser": "dist-client/index.js",
6
6
  "things-factory": true,
@@ -36,18 +36,18 @@
36
36
  "@operato/shell": "^10.29.69",
37
37
  "@operato/styles": "^10.29.1",
38
38
  "@operato/utils": "^10.0.0",
39
- "@things-factory/ai-assistant": "^10.1.258",
40
- "@things-factory/attachment-base": "^10.1.258",
41
- "@things-factory/auth-base": "^10.1.258",
39
+ "@things-factory/ai-assistant": "^10.1.260",
40
+ "@things-factory/attachment-base": "^10.1.260",
41
+ "@things-factory/auth-base": "^10.1.260",
42
42
  "@things-factory/env": "^10.1.149",
43
- "@things-factory/i18n-base": "^10.1.258",
44
- "@things-factory/notification": "^10.1.258",
45
- "@things-factory/organization": "^10.1.258",
46
- "@things-factory/shell": "^10.1.258",
43
+ "@things-factory/i18n-base": "^10.1.260",
44
+ "@things-factory/notification": "^10.1.260",
45
+ "@things-factory/organization": "^10.1.260",
46
+ "@things-factory/shell": "^10.1.260",
47
47
  "graphql-scalars": "^1.22.0",
48
48
  "graphql-tag": "^2.12.6",
49
49
  "graphql-upload": "^15.0.2",
50
50
  "lit": "^3.1.2"
51
51
  },
52
- "gitHead": "c7b7e3b4e9aa1b36a246ccee312a4124a50924dd"
52
+ "gitHead": "72de583ba0394dcf8195ef723b0ee4217507e0e5"
53
53
  }
@@ -217,23 +217,3 @@ export function noticeWordsOf(one: { title: string; body?: string | null }, kind
217
217
  note: say('announcement.notice.note', 'Open Announcements in the application to read it.')
218
218
  }
219
219
  }
220
-
221
- export type NoticeCount = { receivers: number; email: { sent: number; failed: number }; teams: { sent: number; failed: number } }
222
-
223
- /** One telling as the announcement keeps it - when, why, and what went out. A send that could not go at all counts its receivers, nothing sent. */
224
- export function deliveryOf(kind: NoticeKind, result: NoticeCount | undefined, receivers: number, at = new Date()) {
225
- return {
226
- at: at.toISOString(),
227
- kind,
228
- receivers: result?.receivers ?? receivers,
229
- email: result?.email ?? { sent: 0, failed: 0 },
230
- teams: result?.teams ?? { sent: 0, failed: 0 },
231
- ...(result ? {} : { failed: true })
232
- }
233
- }
234
-
235
- /** The record kept on an announcement - the newest tellings, at most `MAX_DELIVERIES`. */
236
- export const MAX_DELIVERIES = 50
237
- export function withDelivery<T>(deliveries: T[] | null | undefined, one: T): T[] {
238
- return [...(deliveries ?? []), one].slice(-MAX_DELIVERIES)
239
- }
@@ -7,12 +7,12 @@ import { EntityManager, In, IsNull, LessThanOrEqual } from 'typeorm'
7
7
  import { checkPermission, User, UserStatus } from '@things-factory/auth-base'
8
8
  import { logger } from '@things-factory/env'
9
9
  import { translatorFor } from '@things-factory/i18n-base/translator'
10
- import { noticeLinkOf, sendNotice, sendNoticeWhenCommitted, type Notice, type NoticeResult } from '@things-factory/notification'
10
+ import { keepNoticeDelivery, noticeDeliveryOf, noticeLinkOf, sendNotice, sendNoticeWhenCommitted, type Notice } from '@things-factory/notification'
11
11
  import { audienceMemberOf, audienceUserIds, type AudienceMember } from '@things-factory/organization'
12
12
  import { Domain, getDataSource, getRepository } from '@things-factory/shell'
13
13
 
14
14
  import { Announcement, AnnouncementAudience, AnnouncementReceipt, AnnouncementRevision, AnnouncementStatus } from './announcement'
15
- import { broadcastOf, deliveryOf, noticeWordsOf, viewAddressOf, withDelivery, type NoticeKind, type Say } from './announcement-rules'
15
+ import { broadcastOf, noticeWordsOf, viewAddressOf, type NoticeKind, type Say } from './announcement-rules'
16
16
 
17
17
  /** The attachments of an announcement hang on it under this `refType` (attachment-base). */
18
18
  export const ANNOUNCEMENT_REF_TYPE = 'Announcement'
@@ -173,17 +173,6 @@ function noticeOf(one: Announcement, kind: NoticeKind, receivers: Notice['receiv
173
173
  }
174
174
  }
175
175
 
176
- /** Keeps what one telling sent on the announcement - in its own transaction, the request's being over. */
177
- async function recordDelivery(id: string, kind: NoticeKind, result: NoticeResult | undefined, receivers: number): Promise<void> {
178
- try {
179
- const repository = getRepository(Announcement)
180
- const one = await repository.findOneBy({ id })
181
- if (one) await repository.update(id, { deliveries: withDelivery(one.deliveries, deliveryOf(kind, result, receivers)) } as any)
182
- } catch (error) {
183
- logger.error(`announcement ${id}: what was sent could not be kept`, error)
184
- }
185
- }
186
-
187
176
  /**
188
177
  * Tells `userIds` of it once the request's transaction has committed - a reader told before it would not find it yet -
189
178
  * and keeps what went out. A notice that does not arrive does not undo the announcement: its list, badge and popup
@@ -194,7 +183,7 @@ export async function tellWhenCommitted(context: any, one: Announcement, kind: N
194
183
  if (!receivers.length) return
195
184
  const broadcast = toEveryone && broadcastOf(await audiencesOf(one.id, context.state.tx), context.state.domain?.id)
196
185
  sendNoticeWhenCommitted(context, noticeOf(one, kind, receivers, broadcast, noticeLinkOf(context, viewAddressOf(one.id))), result =>
197
- recordDelivery(one.id, kind, result, receivers.length)
186
+ keepNoticeDelivery(Announcement, one.id, 'deliveries', noticeDeliveryOf(kind, result, receivers.length))
198
187
  )
199
188
  }
200
189
 
@@ -222,7 +211,7 @@ export async function publishDue(now = new Date()): Promise<number> {
222
211
  /* No request, so no address to put on a mail button - the mail says where to find it instead. */
223
212
  const people = await receiversOf(receivers)
224
213
  const broadcast = broadcastOf(await audiencesOf(one.id), one.domain?.id)
225
- if (people.length) await recordDelivery(one.id, 'published', await sendNotice(one.domain, noticeOf(one, 'published', people, broadcast)), people.length)
214
+ if (people.length) await keepNoticeDelivery(Announcement, one.id, 'deliveries', noticeDeliveryOf('published', await sendNotice(one.domain, noticeOf(one, 'published', people, broadcast)), people.length))
226
215
  } catch (error) {
227
216
  logger.error(`announcement ${one.id}: publishing at its start failed`, error)
228
217
  }
@@ -11,9 +11,11 @@ const { existsSync } = require('fs')
11
11
  const { join } = require('path')
12
12
  const { DataSource } = require('typeorm')
13
13
 
14
- /* What notification was asked to send (ADR-0164) - the channels themselves are notification's tests. */
14
+ /* What notification was asked to send (ADR-0164) - the channels themselves are notification's tests. What went out is
15
+ * kept on the announcement by notification's own record (`keepNoticeDelivery`), so the real one is used. */
15
16
  const told: { domain: any; notice: any }[] = []
16
17
  jest.mock('@things-factory/notification', () => ({
18
+ ...jest.requireActual('@things-factory/notification/dist-server/controllers/notice/notice-delivery-record.js'),
17
19
  sendNotice: async (domain: any, notice: any) => {
18
20
  told.push({ domain, notice })
19
21
  return { receivers: notice.receivers.length, email: { sent: notice.channels?.email ? notice.receivers.length : 0, failed: 0 }, teams: { sent: 0, failed: 0 } }
@@ -7,9 +7,7 @@ import {
7
7
  inputRefusal,
8
8
  isCurrent,
9
9
  linkRefusal,
10
- deliveryOf,
11
10
  noticeWordsOf,
12
- withDelivery,
13
11
  owedReminder,
14
12
  periodOf,
15
13
  readerNarrowingOf,
@@ -194,19 +192,6 @@ describe('the notice', () => {
194
192
  expect(noticeWordsOf({ title: 'x' }, 'published', say).body).toBe('')
195
193
  })
196
194
 
197
- it('★ each telling is kept - what went out, or that nothing could go', () => {
198
- const at = new Date('2026-10-26T09:00:00Z')
199
- const sent = { receivers: 3, email: { sent: 2, failed: 1 }, teams: { sent: 0, failed: 0 } }
200
- expect(deliveryOf('published', sent, 3, at)).toEqual({ at: '2026-10-26T09:00:00.000Z', kind: 'published', ...sent })
201
- expect(deliveryOf('reminder', undefined, 4, at)).toEqual({ at: '2026-10-26T09:00:00.000Z', kind: 'reminder', receivers: 4, email: { sent: 0, failed: 0 }, teams: { sent: 0, failed: 0 }, failed: true })
202
- })
203
-
204
- it('the record keeps the newest fifty', () => {
205
- const many = Array.from({ length: 50 }, (_, i) => i)
206
- expect(withDelivery(many, 50)).toEqual(Array.from({ length: 50 }, (_, i) => i + 1))
207
- expect(withDelivery(null, 1)).toEqual([1])
208
- })
209
-
210
195
  it('★ only the marks that wrap words go - a lone ~, _ or | is part of the text', () => {
211
196
  expect(plainSummary('**11월 2일(월) 22시~24시** 점검 · ~~취소~~ `a_b` *기울임* | 표')).toBe('11월 2일(월) 22시~24시 점검 · 취소 a_b 기울임 | 표')
212
197
  })
@@ -0,0 +1,25 @@
1
+ /*
2
+ * The announcement list's query is its grid's - the period a filter of the grid, kept in the address with search and
3
+ * page (FOOTER-BUTTON-REVIEW G09), so a shared link, coming back and reloading read the same list.
4
+ */
5
+ import { readFileSync } from 'fs'
6
+ import { join } from 'path'
7
+
8
+ import { announcementPeriodOf } from '../client/utils/announcement-list-query'
9
+
10
+ const page = readFileSync(join(__dirname, '..', 'client', 'pages', 'announcement-list.ts'), 'utf8')
11
+
12
+ test('★ the period is past only when the grid says so - none, or a cleared choice, is the current ones', () => {
13
+ expect(announcementPeriodOf([{ name: 'period', value: 'past' }])).toBe('past')
14
+ expect(announcementPeriodOf([{ name: 'period', value: 'current' }])).toBe('current')
15
+ expect(announcementPeriodOf([{ name: 'period', value: '' }])).toBe('current')
16
+ expect(announcementPeriodOf([{ name: 'category', value: 'past' }])).toBe('current')
17
+ expect(announcementPeriodOf(undefined)).toBe('current')
18
+ })
19
+
20
+ test('★ the list keeps its query in the address and narrows by the grid, never by a page choice that re-reads by hand', () => {
21
+ expect(page).toMatch(/\?url-params-sensitive=\$\{this\.active\}/)
22
+ expect(page).toMatch(/name: 'period',[\s\S]*?hidden: true,[\s\S]*?filter: \{[\s\S]*?value: 'current'/)
23
+ expect(page).not.toMatch(/@state\(\) period|ox-select|this\.grist\.fetch\(\)\s*\n\s*\}\}/)
24
+ expect(page).toMatch(/value: announcementPeriodOf\(filters\)/)
25
+ })
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "announcement.title.list": "Announcements",
3
3
  "announcement.title.manage": "Manage announcements",
4
- "announcement.title.new": "New announcement",
4
+ "announcement.title.new": "Write announcement",
5
5
  "announcement.title.edit": "Edit announcement",
6
6
  "announcement.title.view": "Announcement",
7
7
  "announcement.title.popup": "Announcements",
@@ -12,7 +12,7 @@
12
12
  "announcement.button.copy": "Copy",
13
13
  "announcement.button.delete": "Delete",
14
14
  "announcement.button.edit": "Edit",
15
- "announcement.button.new": "New announcement",
15
+ "announcement.button.new": "Write announcement",
16
16
  "announcement.button.next": "Next",
17
17
  "announcement.button.open": "Open",
18
18
  "announcement.button.previous": "Previous",
@@ -93,7 +93,7 @@
93
93
  "announcement.text.link-label": "Name",
94
94
  "announcement.text.link-url": "Address",
95
95
  "announcement.text.manage-empty": "No announcements.",
96
- "announcement.text.manage-empty-description": "Write one with \"New announcement\".",
96
+ "announcement.text.manage-empty-description": "Press Write announcement to write one.",
97
97
  "announcement.text.markdown-hint": "Markdown: **bold**, - list, [link](address)",
98
98
  "announcement.text.new-version": "Saving makes version {version}.",
99
99
  "announcement.text.no-audience": "No audience yet.",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "announcement.title.list": "Avisos",
3
3
  "announcement.title.manage": "Gestionar avisos",
4
- "announcement.title.new": "Nuevo aviso",
4
+ "announcement.title.new": "Redactar aviso",
5
5
  "announcement.title.edit": "Editar aviso",
6
6
  "announcement.title.view": "Aviso",
7
7
  "announcement.title.popup": "Avisos",
@@ -12,7 +12,7 @@
12
12
  "announcement.button.copy": "Copiar",
13
13
  "announcement.button.delete": "Eliminar",
14
14
  "announcement.button.edit": "Editar",
15
- "announcement.button.new": "Nuevo aviso",
15
+ "announcement.button.new": "Redactar aviso",
16
16
  "announcement.button.next": "Siguiente",
17
17
  "announcement.button.open": "Abrir",
18
18
  "announcement.button.previous": "Anterior",
@@ -93,7 +93,7 @@
93
93
  "announcement.text.link-label": "Nombre",
94
94
  "announcement.text.link-url": "Dirección",
95
95
  "announcement.text.manage-empty": "No hay avisos.",
96
- "announcement.text.manage-empty-description": "Escribe uno con «Nuevo aviso».",
96
+ "announcement.text.manage-empty-description": "Pulse Redactar aviso para redactar uno.",
97
97
  "announcement.text.markdown-hint": "Markdown: **negrita**, - lista, [enlace](dirección)",
98
98
  "announcement.text.new-version": "Al guardar será la versión {version}.",
99
99
  "announcement.text.no-audience": "Aún sin destinatarios.",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "announcement.title.list": "お知らせ",
3
3
  "announcement.title.manage": "お知らせ管理",
4
- "announcement.title.new": "新規お知らせ",
4
+ "announcement.title.new": "お知らせを作成",
5
5
  "announcement.title.edit": "お知らせ編集",
6
6
  "announcement.title.view": "お知らせ",
7
7
  "announcement.title.popup": "お知らせ",
@@ -12,7 +12,7 @@
12
12
  "announcement.button.copy": "複製",
13
13
  "announcement.button.delete": "削除",
14
14
  "announcement.button.edit": "編集",
15
- "announcement.button.new": "新規お知らせ",
15
+ "announcement.button.new": "お知らせを作成",
16
16
  "announcement.button.next": "次へ",
17
17
  "announcement.button.open": "詳細",
18
18
  "announcement.button.previous": "前へ",
@@ -93,7 +93,7 @@
93
93
  "announcement.text.link-label": "名前",
94
94
  "announcement.text.link-url": "アドレス",
95
95
  "announcement.text.manage-empty": "お知らせはありません。",
96
- "announcement.text.manage-empty-description": "「新規お知らせ」で作成します。",
96
+ "announcement.text.manage-empty-description": "「お知らせを作成」を押して作成してください。",
97
97
  "announcement.text.markdown-hint": "Markdown: **太字**, - リスト, [リンク](アドレス)",
98
98
  "announcement.text.new-version": "保存すると第{version}版になります。",
99
99
  "announcement.text.no-audience": "宛先が指定されていません。",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "announcement.title.list": "공지사항",
3
3
  "announcement.title.manage": "공지 관리",
4
- "announcement.title.new": "새 공지",
4
+ "announcement.title.new": "공지 작성",
5
5
  "announcement.title.edit": "공지 수정",
6
6
  "announcement.title.view": "공지사항",
7
7
  "announcement.title.popup": "공지사항",
@@ -12,7 +12,7 @@
12
12
  "announcement.button.copy": "복제",
13
13
  "announcement.button.delete": "삭제",
14
14
  "announcement.button.edit": "수정",
15
- "announcement.button.new": "새 공지",
15
+ "announcement.button.new": "공지 작성",
16
16
  "announcement.button.next": "다음",
17
17
  "announcement.button.open": "자세히 보기",
18
18
  "announcement.button.previous": "이전",
@@ -93,7 +93,7 @@
93
93
  "announcement.text.link-label": "이름",
94
94
  "announcement.text.link-url": "주소",
95
95
  "announcement.text.manage-empty": "공지가 없습니다.",
96
- "announcement.text.manage-empty-description": "「새 공지」로 작성합니다.",
96
+ "announcement.text.manage-empty-description": "「공지 작성」을 눌러 공지를 작성하세요.",
97
97
  "announcement.text.markdown-hint": "마크다운: **굵게**, - 목록, [링크](주소)",
98
98
  "announcement.text.new-version": "저장하면 {version} 버전이 됩니다.",
99
99
  "announcement.text.no-audience": "받는 사람을 지정하지 않았습니다.",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "announcement.title.list": "Pengumuman",
3
3
  "announcement.title.manage": "Urus pengumuman",
4
- "announcement.title.new": "Pengumuman baharu",
4
+ "announcement.title.new": "Tulis pengumuman",
5
5
  "announcement.title.edit": "Sunting pengumuman",
6
6
  "announcement.title.view": "Pengumuman",
7
7
  "announcement.title.popup": "Pengumuman",
@@ -12,7 +12,7 @@
12
12
  "announcement.button.copy": "Salin",
13
13
  "announcement.button.delete": "Padam",
14
14
  "announcement.button.edit": "Sunting",
15
- "announcement.button.new": "Pengumuman baharu",
15
+ "announcement.button.new": "Tulis pengumuman",
16
16
  "announcement.button.next": "Seterusnya",
17
17
  "announcement.button.open": "Buka",
18
18
  "announcement.button.previous": "Sebelumnya",
@@ -93,7 +93,7 @@
93
93
  "announcement.text.link-label": "Nama",
94
94
  "announcement.text.link-url": "Alamat",
95
95
  "announcement.text.manage-empty": "Tiada pengumuman.",
96
- "announcement.text.manage-empty-description": "Tulis dengan \"Pengumuman baharu\".",
96
+ "announcement.text.manage-empty-description": "Tekan Tulis pengumuman untuk menulisnya.",
97
97
  "announcement.text.markdown-hint": "Markdown: **tebal**, - senarai, [pautan](alamat)",
98
98
  "announcement.text.new-version": "Simpan menjadikannya versi {version}.",
99
99
  "announcement.text.no-audience": "Belum ada penerima.",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "announcement.title.list": "公告",
3
3
  "announcement.title.manage": "公告管理",
4
- "announcement.title.new": "新公告",
4
+ "announcement.title.new": "撰写公告",
5
5
  "announcement.title.edit": "编辑公告",
6
6
  "announcement.title.view": "公告",
7
7
  "announcement.title.popup": "公告",
@@ -12,7 +12,7 @@
12
12
  "announcement.button.copy": "复制",
13
13
  "announcement.button.delete": "删除",
14
14
  "announcement.button.edit": "编辑",
15
- "announcement.button.new": "新公告",
15
+ "announcement.button.new": "撰写公告",
16
16
  "announcement.button.next": "下一条",
17
17
  "announcement.button.open": "查看详情",
18
18
  "announcement.button.previous": "上一条",
@@ -93,7 +93,7 @@
93
93
  "announcement.text.link-label": "名称",
94
94
  "announcement.text.link-url": "地址",
95
95
  "announcement.text.manage-empty": "没有公告。",
96
- "announcement.text.manage-empty-description": "通过“新公告”撰写。",
96
+ "announcement.text.manage-empty-description": "请点击“撰写公告”撰写公告。",
97
97
  "announcement.text.markdown-hint": "Markdown:**加粗**、- 列表、[链接](地址)",
98
98
  "announcement.text.new-version": "保存后成为第 {version} 版。",
99
99
  "announcement.text.no-audience": "尚未指定接收人。",