@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.
Files changed (81) hide show
  1. package/dist-client/announcement-watch.d.ts +6 -0
  2. package/dist-client/announcement-watch.js +105 -0
  3. package/dist-client/announcement-watch.js.map +1 -0
  4. package/dist-client/bootstrap.d.ts +1 -0
  5. package/dist-client/bootstrap.js +8 -0
  6. package/dist-client/bootstrap.js.map +1 -0
  7. package/dist-client/components/announcement-popup.d.ts +20 -0
  8. package/dist-client/components/announcement-popup.js +98 -0
  9. package/dist-client/components/announcement-popup.js.map +1 -0
  10. package/dist-client/components/announcement-receipts.d.ts +30 -0
  11. package/dist-client/components/announcement-receipts.js +220 -0
  12. package/dist-client/components/announcement-receipts.js.map +1 -0
  13. package/dist-client/components/announcement-sheet.d.ts +17 -0
  14. package/dist-client/components/announcement-sheet.js +224 -0
  15. package/dist-client/components/announcement-sheet.js.map +1 -0
  16. package/dist-client/components/audience-picker.d.ts +32 -0
  17. package/dist-client/components/audience-picker.js +229 -0
  18. package/dist-client/components/audience-picker.js.map +1 -0
  19. package/dist-client/index.d.ts +1 -0
  20. package/dist-client/index.js +2 -0
  21. package/dist-client/index.js.map +1 -0
  22. package/dist-client/pages/announcement-editor.d.ts +59 -0
  23. package/dist-client/pages/announcement-editor.js +523 -0
  24. package/dist-client/pages/announcement-editor.js.map +1 -0
  25. package/dist-client/pages/announcement-list.d.ts +54 -0
  26. package/dist-client/pages/announcement-list.js +209 -0
  27. package/dist-client/pages/announcement-list.js.map +1 -0
  28. package/dist-client/pages/announcement-manage-list.d.ts +46 -0
  29. package/dist-client/pages/announcement-manage-list.js +115 -0
  30. package/dist-client/pages/announcement-manage-list.js.map +1 -0
  31. package/dist-client/pages/announcement-view.d.ts +30 -0
  32. package/dist-client/pages/announcement-view.js +182 -0
  33. package/dist-client/pages/announcement-view.js.map +1 -0
  34. package/dist-client/route.d.ts +1 -0
  35. package/dist-client/route.js +18 -0
  36. package/dist-client/route.js.map +1 -0
  37. package/dist-client/tsconfig.tsbuildinfo +1 -0
  38. package/dist-client/utils/announcement-api.d.ts +149 -0
  39. package/dist-client/utils/announcement-api.js +52 -0
  40. package/dist-client/utils/announcement-api.js.map +1 -0
  41. package/dist-client/utils/announcement-badge.d.ts +4 -0
  42. package/dist-client/utils/announcement-badge.js +47 -0
  43. package/dist-client/utils/announcement-badge.js.map +1 -0
  44. package/dist-client/utils/announcement-words.d.ts +11 -0
  45. package/dist-client/utils/announcement-words.js +23 -0
  46. package/dist-client/utils/announcement-words.js.map +1 -0
  47. package/dist-server/announcement-read.d.ts +6 -0
  48. package/dist-server/announcement-read.js +45 -0
  49. package/dist-server/announcement-read.js.map +1 -0
  50. package/dist-server/attachment-reader.d.ts +1 -0
  51. package/dist-server/attachment-reader.js +13 -0
  52. package/dist-server/attachment-reader.js.map +1 -0
  53. package/dist-server/index.d.ts +2 -0
  54. package/dist-server/index.js +20 -0
  55. package/dist-server/index.js.map +1 -0
  56. package/dist-server/role-tasks.d.ts +2 -0
  57. package/dist-server/role-tasks.js +13 -0
  58. package/dist-server/role-tasks.js.map +1 -0
  59. package/dist-server/service/announcement/announcement-resolver.d.ts +38 -0
  60. package/dist-server/service/announcement/announcement-resolver.js +664 -0
  61. package/dist-server/service/announcement/announcement-resolver.js.map +1 -0
  62. package/dist-server/service/announcement/announcement-rules.d.ts +150 -0
  63. package/dist-server/service/announcement/announcement-rules.js +227 -0
  64. package/dist-server/service/announcement/announcement-rules.js.map +1 -0
  65. package/dist-server/service/announcement/announcement-service.d.ts +54 -0
  66. package/dist-server/service/announcement/announcement-service.js +248 -0
  67. package/dist-server/service/announcement/announcement-service.js.map +1 -0
  68. package/dist-server/service/announcement/announcement-types.d.ts +54 -0
  69. package/dist-server/service/announcement/announcement-types.js +203 -0
  70. package/dist-server/service/announcement/announcement-types.js.map +1 -0
  71. package/dist-server/service/announcement/announcement.d.ts +124 -0
  72. package/dist-server/service/announcement/announcement.js +462 -0
  73. package/dist-server/service/announcement/announcement.js.map +1 -0
  74. package/dist-server/service/announcement/index.d.ts +4 -0
  75. package/dist-server/service/announcement/index.js +8 -0
  76. package/dist-server/service/announcement/index.js.map +1 -0
  77. package/dist-server/service/index.d.ts +7 -0
  78. package/dist-server/service/index.js +23 -0
  79. package/dist-server/service/index.js.map +1 -0
  80. package/dist-server/tsconfig.tsbuildinfo +1 -0
  81. package/package.json +8 -8
@@ -0,0 +1,6 @@
1
+ import './components/announcement-popup.js';
2
+ /** Opens the popups due now that this session has not closed - one window at a time. */
3
+ export declare function showDuePopups(): Promise<void>;
4
+ /** Starts once the person is known. */
5
+ export declare function startAnnouncementWatch(): Promise<void>;
6
+ export declare function stopAnnouncementWatch(): void;
@@ -0,0 +1,105 @@
1
+ /*
2
+ * Keeps the person up with announcements while the application is open (ADR-0163 decision 5): the popups due at sign-in,
3
+ * a new one popping up the moment it is published to them, and the menu badge.
4
+ *
5
+ * The notice of an announcement reaches the screen through notification's subscription; the announcements themselves
6
+ * are asked of the server - a screen that was closed when one went out still finds it here.
7
+ */
8
+ import './components/announcement-popup.js';
9
+ import gql from 'graphql-tag';
10
+ import { html } from 'lit';
11
+ import { subscribe } from '@operato/graphql';
12
+ import { openPopup } from '@operato/layout';
13
+ import { announcementApi } from './utils/announcement-api.js';
14
+ import { refreshAnnouncementBadge } from './utils/announcement-badge.js';
15
+ import { t } from './utils/announcement-words.js';
16
+ /* Closed in this browser session - asked again at the next sign-in (one still to be acknowledged comes back then). */
17
+ const CLOSED_KEY = 'announcement.popups-closed';
18
+ function closedInSession() {
19
+ try {
20
+ return new Set(JSON.parse(sessionStorage.getItem(CLOSED_KEY) || '[]'));
21
+ }
22
+ catch {
23
+ return new Set();
24
+ }
25
+ }
26
+ function rememberClosed(ids) {
27
+ try {
28
+ sessionStorage.setItem(CLOSED_KEY, JSON.stringify([...new Set([...closedInSession(), ...ids])]));
29
+ }
30
+ catch {
31
+ /* a browser that keeps nothing shows them again - no harm */
32
+ }
33
+ }
34
+ let popup;
35
+ /* A reading changed on a screen or in the popup - the badge counts again. */
36
+ window.addEventListener('announcements-changed', () => refreshAnnouncementBadge());
37
+ let subscription;
38
+ let watching = false;
39
+ /** Opens the popups due now that this session has not closed - one window at a time. */
40
+ export async function showDuePopups() {
41
+ if (popup && !popup.closed)
42
+ return;
43
+ let due;
44
+ try {
45
+ due = await announcementApi.popups();
46
+ }
47
+ catch {
48
+ return;
49
+ }
50
+ const closed = closedInSession();
51
+ const items = due.filter(one => !closed.has(one.id));
52
+ if (!items.length)
53
+ return;
54
+ const handle = openPopup(html `<announcement-popup .items=${items} @done=${() => handle.close()}></announcement-popup>`, {
55
+ backdrop: true,
56
+ size: 'medium',
57
+ title: t('announcement.title.popup')
58
+ });
59
+ popup = handle;
60
+ handle.result?.then?.(() => {
61
+ rememberClosed(items.map(one => one.id));
62
+ refreshAnnouncementBadge();
63
+ });
64
+ }
65
+ /** Starts once the person is known. */
66
+ export async function startAnnouncementWatch() {
67
+ if (watching)
68
+ return;
69
+ watching = true;
70
+ refreshAnnouncementBadge();
71
+ showDuePopups();
72
+ try {
73
+ subscription = await subscribe({
74
+ query: gql `
75
+ subscription {
76
+ notification(subjects: ["info"]) {
77
+ type
78
+ }
79
+ }
80
+ `
81
+ }, {
82
+ next({ data }) {
83
+ if (data?.notification?.type !== 'announcement')
84
+ return;
85
+ refreshAnnouncementBadge();
86
+ showDuePopups();
87
+ }
88
+ });
89
+ }
90
+ catch {
91
+ /* without the live line the badge and popups still come at the next sign-in */
92
+ }
93
+ }
94
+ export function stopAnnouncementWatch() {
95
+ subscription?.unsubscribe();
96
+ subscription = undefined;
97
+ popup?.close();
98
+ popup = undefined;
99
+ watching = false;
100
+ try {
101
+ sessionStorage.removeItem(CLOSED_KEY);
102
+ }
103
+ catch { }
104
+ }
105
+ //# sourceMappingURL=announcement-watch.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"announcement-watch.js","sourceRoot":"","sources":["../client/announcement-watch.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Keeps the person up with announcements while the application is open (ADR-0163 decision 5): the popups due at sign-in,\n * a new one popping up the moment it is published to them, and the menu badge.\n *\n * The notice of an announcement reaches the screen through notification's subscription; the announcements themselves\n * are asked of the server - a screen that was closed when one went out still finds it here.\n */\nimport './components/announcement-popup.js'\n\nimport gql from 'graphql-tag'\nimport { html } from 'lit'\n\nimport { subscribe } from '@operato/graphql'\nimport { openPopup } from '@operato/layout'\n\nimport { announcementApi, type AnnouncementItem } from './utils/announcement-api.js'\nimport { refreshAnnouncementBadge } from './utils/announcement-badge.js'\nimport { t } from './utils/announcement-words.js'\n\n/* Closed in this browser session - asked again at the next sign-in (one still to be acknowledged comes back then). */\nconst CLOSED_KEY = 'announcement.popups-closed'\n\nfunction closedInSession(): Set<string> {\n try {\n return new Set(JSON.parse(sessionStorage.getItem(CLOSED_KEY) || '[]'))\n } catch {\n return new Set()\n }\n}\n\nfunction rememberClosed(ids: string[]) {\n try {\n sessionStorage.setItem(CLOSED_KEY, JSON.stringify([...new Set([...closedInSession(), ...ids])]))\n } catch {\n /* a browser that keeps nothing shows them again - no harm */\n }\n}\n\nlet popup: { close: () => void; closed: boolean } | undefined\n\n/* A reading changed on a screen or in the popup - the badge counts again. */\nwindow.addEventListener('announcements-changed', () => refreshAnnouncementBadge())\nlet subscription: { unsubscribe(): void } | undefined\nlet watching = false\n\n/** Opens the popups due now that this session has not closed - one window at a time. */\nexport async function showDuePopups(): Promise<void> {\n if (popup && !popup.closed) return\n let due: AnnouncementItem[]\n try {\n due = await announcementApi.popups()\n } catch {\n return\n }\n const closed = closedInSession()\n const items = due.filter(one => !closed.has(one.id))\n if (!items.length) return\n const handle: any = openPopup(html`<announcement-popup .items=${items} @done=${() => handle.close()}></announcement-popup>`, {\n backdrop: true,\n size: 'medium',\n title: t('announcement.title.popup')\n })\n popup = handle\n handle.result?.then?.(() => {\n rememberClosed(items.map(one => one.id))\n refreshAnnouncementBadge()\n })\n}\n\n/** Starts once the person is known. */\nexport async function startAnnouncementWatch(): Promise<void> {\n if (watching) return\n watching = true\n refreshAnnouncementBadge()\n showDuePopups()\n try {\n subscription = await subscribe(\n {\n query: gql`\n subscription {\n notification(subjects: [\"info\"]) {\n type\n }\n }\n `\n },\n {\n next({ data }: any) {\n if (data?.notification?.type !== 'announcement') return\n refreshAnnouncementBadge()\n showDuePopups()\n }\n }\n )\n } catch {\n /* without the live line the badge and popups still come at the next sign-in */\n }\n}\n\nexport function stopAnnouncementWatch(): void {\n subscription?.unsubscribe()\n subscription = undefined\n popup?.close()\n popup = undefined\n watching = false\n try {\n sessionStorage.removeItem(CLOSED_KEY)\n } catch {}\n}\n"]}
@@ -0,0 +1 @@
1
+ export default function bootstrap(): void;
@@ -0,0 +1,8 @@
1
+ import { auth } from '@things-factory/auth-base/dist-client/auth.js';
2
+ import { startAnnouncementWatch, stopAnnouncementWatch } from './announcement-watch.js';
3
+ /* Announcements follow the person - the popups due and the badge come when they are known (ADR-0163 decision 5). */
4
+ export default function bootstrap() {
5
+ auth.on('profile', () => startAnnouncementWatch());
6
+ auth.on('presignout', () => stopAnnouncementWatch());
7
+ }
8
+ //# sourceMappingURL=bootstrap.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bootstrap.js","sourceRoot":"","sources":["../client/bootstrap.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,+CAA+C,CAAA;AAEpE,OAAO,EAAE,sBAAsB,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAA;AAEvF,oHAAoH;AACpH,MAAM,CAAC,OAAO,UAAU,SAAS;IAC/B,IAAI,CAAC,EAAE,CAAC,SAAS,EAAE,GAAG,EAAE,CAAC,sBAAsB,EAAE,CAAC,CAAA;IAClD,IAAI,CAAC,EAAE,CAAC,YAAY,EAAE,GAAG,EAAE,CAAC,qBAAqB,EAAE,CAAC,CAAA;AACtD,CAAC","sourcesContent":["import { auth } from '@things-factory/auth-base/dist-client/auth.js'\n\nimport { startAnnouncementWatch, stopAnnouncementWatch } from './announcement-watch.js'\n\n/* Announcements follow the person - the popups due and the badge come when they are known (ADR-0163 decision 5). */\nexport default function bootstrap() {\n auth.on('profile', () => startAnnouncementWatch())\n auth.on('presignout', () => stopAnnouncementWatch())\n}\n"]}
@@ -0,0 +1,20 @@
1
+ import '@material/web/button/filled-button.js';
2
+ import '@material/web/button/text-button.js';
3
+ import '@operato/popup/ox-popup-actions.js';
4
+ import './announcement-sheet.js';
5
+ import { LitElement, nothing } from 'lit';
6
+ import { type AnnouncementItem } from '../utils/announcement-api.js';
7
+ declare const AnnouncementPopup_base: (new (...args: any[]) => LitElement) & typeof LitElement;
8
+ export declare class AnnouncementPopup extends AnnouncementPopup_base {
9
+ static styles: import("lit").CSSResult[];
10
+ items: AnnouncementItem[];
11
+ private index;
12
+ render(): typeof nothing | import("lit-html").TemplateResult<1>;
13
+ firstUpdated(): void;
14
+ /** Shows one, and reads it - seeing it in the popup is reading it. */
15
+ private show;
16
+ private replace;
17
+ private open;
18
+ private close;
19
+ }
20
+ export {};
@@ -0,0 +1,98 @@
1
+ import { __decorate, __metadata } from "tslib";
2
+ /*
3
+ * The announcements that pop up (the user, 2026-10-26: 「공지 사항이 있을 때 자동 팝업」, ADR-0163 decision 5) - one
4
+ * window, one announcement at a time. Seeing one reads it; one that asks to be acknowledged is acknowledged here or
5
+ * shows again at the next sign-in.
6
+ */
7
+ import '@material/web/button/filled-button.js';
8
+ import '@material/web/button/text-button.js';
9
+ import '@operato/popup/ox-popup-actions.js';
10
+ import './announcement-sheet.js';
11
+ import { css, html, LitElement, nothing } from 'lit';
12
+ import { customElement, property, state } from 'lit/decorators.js';
13
+ import { i18next, localize } from '@operato/i18n';
14
+ import { navigate } from '@operato/shell';
15
+ import { CommonPopupStyles, PopupBodyStyles } from '@operato/styles';
16
+ import { announcementApi } from '../utils/announcement-api.js';
17
+ import { t } from '../utils/announcement-words.js';
18
+ let AnnouncementPopup = class AnnouncementPopup extends localize(i18next)(LitElement) {
19
+ constructor() {
20
+ super(...arguments);
21
+ this.items = [];
22
+ this.index = 0;
23
+ }
24
+ static { this.styles = [
25
+ CommonPopupStyles,
26
+ PopupBodyStyles,
27
+ css `
28
+ :host {
29
+ height: 100%;
30
+ }
31
+ h3 {
32
+ margin: 0 0 var(--spacing-small, 4px);
33
+ font-size: var(--ui-t-title, 18px);
34
+ }
35
+ .count {
36
+ margin-inline-end: auto;
37
+ align-self: center;
38
+ font-size: var(--ui-t-small, 12px);
39
+ color: var(--ui-muted, var(--md-sys-color-on-surface-variant));
40
+ }
41
+ `
42
+ ]; }
43
+ render() {
44
+ const one = this.items[this.index];
45
+ if (!one)
46
+ return nothing;
47
+ const last = this.index >= this.items.length - 1;
48
+ return html `<div class="body">
49
+ <h3>${one.title}</h3>
50
+ <announcement-sheet .announcement=${one} @announcement-follow=${() => this.close()} @announcement-acknowledged=${(e) => this.replace(e.detail)}></announcement-sheet>
51
+ </div>
52
+ <ox-popup-actions>
53
+ ${this.items.length > 1 ? html `<span class="count">${this.index + 1} / ${this.items.length}</span>` : nothing}
54
+ <md-text-button @click=${() => this.open(one)}>${t('announcement.button.open')}</md-text-button>
55
+ ${this.index > 0 ? html `<md-text-button @click=${() => this.show(this.index - 1)}>${t('announcement.button.previous')}</md-text-button>` : nothing}
56
+ ${last
57
+ ? html `<md-filled-button slot="confirm" @click=${() => this.close()}>${t('announcement.button.close')}</md-filled-button>`
58
+ : html `<md-filled-button slot="confirm" @click=${() => this.show(this.index + 1)}>${t('announcement.button.next')}</md-filled-button>`}
59
+ </ox-popup-actions>`;
60
+ }
61
+ firstUpdated() {
62
+ this.show(0);
63
+ }
64
+ /** Shows one, and reads it - seeing it in the popup is reading it. */
65
+ show(index) {
66
+ this.index = index;
67
+ const one = this.items[index];
68
+ if (one && !one.mine?.readAt) {
69
+ announcementApi
70
+ .read(one.id)
71
+ .then(done => this.replace({ ...one, ...done }))
72
+ .catch(() => { });
73
+ }
74
+ }
75
+ replace(one) {
76
+ this.items = this.items.map(item => (item.id === one.id ? { ...item, ...one } : item));
77
+ }
78
+ open(one) {
79
+ this.close();
80
+ navigate(`announcement-view?id=${one.id}`);
81
+ }
82
+ close() {
83
+ this.dispatchEvent(new CustomEvent('done', { bubbles: true, composed: true }));
84
+ }
85
+ };
86
+ __decorate([
87
+ property({ attribute: false }),
88
+ __metadata("design:type", Array)
89
+ ], AnnouncementPopup.prototype, "items", void 0);
90
+ __decorate([
91
+ state(),
92
+ __metadata("design:type", Object)
93
+ ], AnnouncementPopup.prototype, "index", void 0);
94
+ AnnouncementPopup = __decorate([
95
+ customElement('announcement-popup')
96
+ ], AnnouncementPopup);
97
+ export { AnnouncementPopup };
98
+ //# sourceMappingURL=announcement-popup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"announcement-popup.js","sourceRoot":"","sources":["../../client/components/announcement-popup.ts"],"names":[],"mappings":";AAAA;;;;GAIG;AACH,OAAO,uCAAuC,CAAA;AAC9C,OAAO,qCAAqC,CAAA;AAC5C,OAAO,oCAAoC,CAAA;AAC3C,OAAO,yBAAyB,CAAA;AAEhC,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,KAAK,CAAA;AACpD,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAA;AAElE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AACjD,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AACzC,OAAO,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AAEpE,OAAO,EAAE,eAAe,EAAyB,MAAM,8BAA8B,CAAA;AACrF,OAAO,EAAE,CAAC,EAAE,MAAM,gCAAgC,CAAA;AAG3C,IAAM,iBAAiB,GAAvB,MAAM,iBAAkB,SAAQ,QAAQ,CAAC,OAAO,CAAC,CAAC,UAAU,CAAC;IAA7D;;QAqB2B,UAAK,GAAuB,EAAE,CAAA;QAC7C,UAAK,GAAG,CAAC,CAAA;IAgD5B,CAAC;aArEQ,WAAM,GAAG;QACd,iBAAiB;QACjB,eAAe;QACf,GAAG,CAAA;;;;;;;;;;;;;;KAcF;KACF,AAlBY,CAkBZ;IAKD,MAAM;QACJ,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QAClC,IAAI,CAAC,GAAG;YAAE,OAAO,OAAO,CAAA;QACxB,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAA;QAChD,OAAO,IAAI,CAAA;cACD,GAAG,CAAC,KAAK;4CACqB,GAAG,yBAAyB,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,EAAE,+BAA+B,CAAC,CAAc,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC;;;UAGzJ,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA,uBAAuB,IAAI,CAAC,KAAK,GAAG,CAAC,MAAM,IAAI,CAAC,KAAK,CAAC,MAAM,SAAS,CAAC,CAAC,CAAC,OAAO;iCACpF,GAAG,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,0BAA0B,CAAC;UAC5E,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA,0BAA0B,GAAG,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,8BAA8B,CAAC,mBAAmB,CAAC,CAAC,CAAC,OAAO;UAChJ,IAAI;YACJ,CAAC,CAAC,IAAI,CAAA,2CAA2C,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC,2BAA2B,CAAC,qBAAqB;YAC1H,CAAC,CAAC,IAAI,CAAA,2CAA2C,GAAG,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,0BAA0B,CAAC,qBAAqB;0BACtH,CAAA;IACxB,CAAC;IAED,YAAY;QACV,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IACd,CAAC;IAED,sEAAsE;IAC9D,IAAI,CAAC,KAAa;QACxB,IAAI,CAAC,KAAK,GAAG,KAAK,CAAA;QAClB,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAA;QAC7B,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC;YAC7B,eAAe;iBACZ,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;iBACZ,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,GAAG,GAAG,EAAE,GAAG,IAAI,EAAE,CAAC,CAAC;iBAC/C,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAA;QACpB,CAAC;IACH,CAAC;IAEO,OAAO,CAAC,GAAqB;QACnC,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,GAAG,IAAI,EAAE,GAAG,GAAG,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAA;IACxF,CAAC;IAEO,IAAI,CAAC,GAAqB;QAChC,IAAI,CAAC,KAAK,EAAE,CAAA;QACZ,QAAQ,CAAC,wBAAwB,GAAG,CAAC,EAAE,EAAE,CAAC,CAAA;IAC5C,CAAC;IAEO,KAAK;QACX,IAAI,CAAC,aAAa,CAAC,IAAI,WAAW,CAAC,MAAM,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,CAAA;IAChF,CAAC;;AAhD+B;IAA/B,QAAQ,CAAC,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;;gDAA+B;AAC7C;IAAhB,KAAK,EAAE;;gDAAkB;AAtBf,iBAAiB;IAD7B,aAAa,CAAC,oBAAoB,CAAC;GACvB,iBAAiB,CAsE7B","sourcesContent":["/*\n * The announcements that pop up (the user, 2026-10-26: 「공지 사항이 있을 때 자동 팝업」, ADR-0163 decision 5) - one\n * window, one announcement at a time. Seeing one reads it; one that asks to be acknowledged is acknowledged here or\n * shows again at the next sign-in.\n */\nimport '@material/web/button/filled-button.js'\nimport '@material/web/button/text-button.js'\nimport '@operato/popup/ox-popup-actions.js'\nimport './announcement-sheet.js'\n\nimport { css, html, LitElement, nothing } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\n\nimport { i18next, localize } from '@operato/i18n'\nimport { navigate } from '@operato/shell'\nimport { CommonPopupStyles, PopupBodyStyles } from '@operato/styles'\n\nimport { announcementApi, type AnnouncementItem } from '../utils/announcement-api.js'\nimport { t } from '../utils/announcement-words.js'\n\n@customElement('announcement-popup')\nexport class AnnouncementPopup extends localize(i18next)(LitElement) {\n static styles = [\n CommonPopupStyles,\n PopupBodyStyles,\n css`\n :host {\n height: 100%;\n }\n h3 {\n margin: 0 0 var(--spacing-small, 4px);\n font-size: var(--ui-t-title, 18px);\n }\n .count {\n margin-inline-end: auto;\n align-self: center;\n font-size: var(--ui-t-small, 12px);\n color: var(--ui-muted, var(--md-sys-color-on-surface-variant));\n }\n `\n ]\n\n @property({ attribute: false }) items: AnnouncementItem[] = []\n @state() private index = 0\n\n render() {\n const one = this.items[this.index]\n if (!one) return nothing\n const last = this.index >= this.items.length - 1\n return html`<div class=\"body\">\n <h3>${one.title}</h3>\n <announcement-sheet .announcement=${one} @announcement-follow=${() => this.close()} @announcement-acknowledged=${(e: CustomEvent) => this.replace(e.detail)}></announcement-sheet>\n </div>\n <ox-popup-actions>\n ${this.items.length > 1 ? html`<span class=\"count\">${this.index + 1} / ${this.items.length}</span>` : nothing}\n <md-text-button @click=${() => this.open(one)}>${t('announcement.button.open')}</md-text-button>\n ${this.index > 0 ? html`<md-text-button @click=${() => this.show(this.index - 1)}>${t('announcement.button.previous')}</md-text-button>` : nothing}\n ${last\n ? html`<md-filled-button slot=\"confirm\" @click=${() => this.close()}>${t('announcement.button.close')}</md-filled-button>`\n : html`<md-filled-button slot=\"confirm\" @click=${() => this.show(this.index + 1)}>${t('announcement.button.next')}</md-filled-button>`}\n </ox-popup-actions>`\n }\n\n firstUpdated() {\n this.show(0)\n }\n\n /** Shows one, and reads it - seeing it in the popup is reading it. */\n private show(index: number) {\n this.index = index\n const one = this.items[index]\n if (one && !one.mine?.readAt) {\n announcementApi\n .read(one.id)\n .then(done => this.replace({ ...one, ...done }))\n .catch(() => {})\n }\n }\n\n private replace(one: AnnouncementItem) {\n this.items = this.items.map(item => (item.id === one.id ? { ...item, ...one } : item))\n }\n\n private open(one: AnnouncementItem) {\n this.close()\n navigate(`announcement-view?id=${one.id}`)\n }\n\n private close() {\n this.dispatchEvent(new CustomEvent('done', { bubbles: true, composed: true }))\n }\n}\n"]}
@@ -0,0 +1,30 @@
1
+ import '@material/web/button/filled-button.js';
2
+ import '@material/web/button/text-button.js';
3
+ import '@operato/popup/ox-popup-actions.js';
4
+ import { LitElement } from 'lit';
5
+ import { type AnnouncementItem } from '../utils/announcement-api.js';
6
+ declare const AnnouncementReceipts_base: (new (...args: any[]) => LitElement) & typeof LitElement;
7
+ export declare class AnnouncementReceipts extends AnnouncementReceipts_base {
8
+ static styles: import("lit").CSSResult[];
9
+ announcement: AnnouncementItem;
10
+ private rows;
11
+ private which;
12
+ private busy;
13
+ connectedCallback(): void;
14
+ private load;
15
+ render(): import("lit-html").TemplateResult<1>;
16
+ private remind;
17
+ private close;
18
+ }
19
+ declare const AnnouncementWithdrawForm_base: (new (...args: any[]) => LitElement) & typeof LitElement;
20
+ /** Why it is withdrawn - the readers' copy goes, the reason stays with its record. */
21
+ export declare class AnnouncementWithdrawForm extends AnnouncementWithdrawForm_base {
22
+ static styles: import("lit").CSSResult[];
23
+ announcement: AnnouncementItem;
24
+ private reason;
25
+ private busy;
26
+ render(): import("lit-html").TemplateResult<1>;
27
+ private withdraw;
28
+ private close;
29
+ }
30
+ export {};
@@ -0,0 +1,220 @@
1
+ import { __decorate, __metadata } from "tslib";
2
+ /*
3
+ * The small forms a writer opens from an announcement (ADR-0163 decision 4): who read it and who acknowledged it, with
4
+ * a reminder to those who have not; and the reason it is withdrawn.
5
+ */
6
+ import '@material/web/button/filled-button.js';
7
+ import '@material/web/button/text-button.js';
8
+ import '@operato/popup/ox-popup-actions.js';
9
+ import { css, html, LitElement, nothing } from 'lit';
10
+ import { customElement, property, state } from 'lit/decorators.js';
11
+ import { i18next, localize } from '@operato/i18n';
12
+ import { notify } from '@operato/layout';
13
+ import { CommonPopupStyles, PopupBodyStyles } from '@operato/styles';
14
+ import { announcementApi } from '../utils/announcement-api.js';
15
+ import { reasonOf, t, when } from '../utils/announcement-words.js';
16
+ const FormStyles = css `
17
+ :host {
18
+ height: 100%;
19
+ line-height: 1.5;
20
+ }
21
+ .tabs {
22
+ display: flex;
23
+ gap: var(--spacing-small, 4px);
24
+ margin-bottom: var(--spacing-medium, 8px);
25
+ }
26
+ .tabs button {
27
+ border: 1px solid var(--ui-line-soft, var(--md-sys-color-outline-variant));
28
+ background: none;
29
+ border-radius: 999px;
30
+ padding: 2px var(--spacing-medium, 8px);
31
+ font: inherit;
32
+ font-size: var(--ui-t-small, 12px);
33
+ cursor: pointer;
34
+ color: inherit;
35
+ }
36
+ .tabs button[aria-pressed='true'] {
37
+ background: var(--md-sys-color-secondary-container);
38
+ border-color: transparent;
39
+ }
40
+ table {
41
+ width: 100%;
42
+ border-collapse: collapse;
43
+ font-size: var(--ui-t-body, 14px);
44
+ font-variant-numeric: tabular-nums;
45
+ }
46
+ th,
47
+ td {
48
+ text-align: left;
49
+ padding: var(--spacing-small, 4px) var(--spacing-medium, 8px);
50
+ border-bottom: 1px solid var(--ui-line-soft, var(--md-sys-color-outline-variant));
51
+ }
52
+ th {
53
+ font-weight: 600;
54
+ font-size: var(--ui-t-small, 12px);
55
+ color: var(--ui-muted, var(--md-sys-color-on-surface-variant));
56
+ }
57
+ .muted {
58
+ color: var(--ui-muted, var(--md-sys-color-on-surface-variant));
59
+ }
60
+ textarea {
61
+ width: 100%;
62
+ min-height: 96px;
63
+ box-sizing: border-box;
64
+ font: inherit;
65
+ }
66
+ `;
67
+ let AnnouncementReceipts = class AnnouncementReceipts extends localize(i18next)(LitElement) {
68
+ constructor() {
69
+ super(...arguments);
70
+ this.rows = [];
71
+ this.which = 'all';
72
+ this.busy = false;
73
+ }
74
+ static { this.styles = [CommonPopupStyles, PopupBodyStyles, FormStyles]; }
75
+ connectedCallback() {
76
+ super.connectedCallback();
77
+ this.load();
78
+ }
79
+ async load() {
80
+ try {
81
+ this.rows = await announcementApi.receipts(this.announcement.id);
82
+ }
83
+ catch (error) {
84
+ notify({ level: 'error', message: reasonOf(error) });
85
+ }
86
+ }
87
+ render() {
88
+ const one = this.announcement;
89
+ const listed = this.rows.filter(row => row.listed);
90
+ const counts = {
91
+ all: listed.length,
92
+ unread: listed.filter(row => !row.readAt).length,
93
+ unacknowledged: listed.filter(row => !row.acknowledgedAt).length,
94
+ later: this.rows.filter(row => !row.listed).length
95
+ };
96
+ const tabs = ['all', 'unread', ...(one.requiresAcknowledgement ? ['unacknowledged'] : []), ...(counts.later ? ['later'] : [])].map(key => html `<button aria-pressed=${String(this.which === key)} @click=${() => (this.which = key)}>${t(`announcement.receipts.${key}`)} ${counts[key]}</button>`);
97
+ const shown = this.which === 'later'
98
+ ? this.rows.filter(row => !row.listed)
99
+ : listed.filter(row => (this.which === 'unread' ? !row.readAt : this.which === 'unacknowledged' ? !row.acknowledgedAt : true));
100
+ const owed = one.requiresAcknowledgement ? counts.unacknowledged : counts.unread;
101
+ return html `<div class="body">
102
+ <div class="tabs">${tabs}</div>
103
+ <table>
104
+ <thead>
105
+ <tr>
106
+ <th>${t('announcement.receipts.name')}</th>
107
+ <th>${t('announcement.receipts.read-at')}</th>
108
+ ${one.requiresAcknowledgement ? html `<th>${t('announcement.receipts.acknowledged-at')}</th>` : nothing}
109
+ <th>${t('announcement.receipts.notified-at')}</th>
110
+ </tr>
111
+ </thead>
112
+ <tbody>
113
+ ${shown.map(row => html `<tr>
114
+ <td>${row.name ?? row.email ?? row.userId}</td>
115
+ <td class=${row.readAt ? '' : 'muted'}>${row.readAt ? when(row.readAt) : t('announcement.reader-state.unread')}</td>
116
+ ${one.requiresAcknowledgement ? html `<td class=${row.acknowledgedAt ? '' : 'muted'}>${row.acknowledgedAt ? when(row.acknowledgedAt) : '—'}</td>` : nothing}
117
+ <td class="muted">${when(row.notifiedAt)}</td>
118
+ </tr>`)}
119
+ </tbody>
120
+ </table>
121
+ </div>
122
+ <ox-popup-actions>
123
+ <md-text-button slot="cancel" @click=${() => this.close()}>${t('announcement.button.close')}</md-text-button>
124
+ <md-filled-button slot="confirm" ?disabled=${this.busy || !owed || one.status !== 'PUBLISHED'} @click=${() => this.remind()}
125
+ >${t('announcement.button.remind-count', { count: owed })}</md-filled-button
126
+ >
127
+ </ox-popup-actions>`;
128
+ }
129
+ async remind() {
130
+ this.busy = true;
131
+ try {
132
+ const told = await announcementApi.remind(this.announcement.id);
133
+ notify({ message: t('announcement.text.reminded', { count: told }) });
134
+ await this.load();
135
+ }
136
+ catch (error) {
137
+ notify({ level: 'error', message: reasonOf(error) });
138
+ }
139
+ finally {
140
+ this.busy = false;
141
+ }
142
+ }
143
+ close() {
144
+ this.dispatchEvent(new CustomEvent('done', { bubbles: true, composed: true }));
145
+ }
146
+ };
147
+ __decorate([
148
+ property({ attribute: false }),
149
+ __metadata("design:type", Object)
150
+ ], AnnouncementReceipts.prototype, "announcement", void 0);
151
+ __decorate([
152
+ state(),
153
+ __metadata("design:type", Array)
154
+ ], AnnouncementReceipts.prototype, "rows", void 0);
155
+ __decorate([
156
+ state(),
157
+ __metadata("design:type", String)
158
+ ], AnnouncementReceipts.prototype, "which", void 0);
159
+ __decorate([
160
+ state(),
161
+ __metadata("design:type", Object)
162
+ ], AnnouncementReceipts.prototype, "busy", void 0);
163
+ AnnouncementReceipts = __decorate([
164
+ customElement('announcement-receipts')
165
+ ], AnnouncementReceipts);
166
+ export { AnnouncementReceipts };
167
+ /** Why it is withdrawn - the readers' copy goes, the reason stays with its record. */
168
+ let AnnouncementWithdrawForm = class AnnouncementWithdrawForm extends localize(i18next)(LitElement) {
169
+ constructor() {
170
+ super(...arguments);
171
+ this.reason = '';
172
+ this.busy = false;
173
+ }
174
+ static { this.styles = [CommonPopupStyles, PopupBodyStyles, FormStyles]; }
175
+ render() {
176
+ return html `<div class="body">
177
+ <p>${t('announcement.text.withdraw-lead')}</p>
178
+ <textarea .value=${this.reason} maxlength="500" placeholder=${t('announcement.text.withdraw-reason')} @input=${(e) => (this.reason = e.target.value)}></textarea>
179
+ </div>
180
+ <ox-popup-actions>
181
+ <md-text-button slot="cancel" @click=${() => this.close()}>${t('announcement.button.cancel')}</md-text-button>
182
+ <md-filled-button slot="confirm" ?disabled=${this.busy || !this.reason.trim()} @click=${() => this.withdraw()}>${t('announcement.button.withdraw')}</md-filled-button>
183
+ </ox-popup-actions>`;
184
+ }
185
+ async withdraw() {
186
+ this.busy = true;
187
+ try {
188
+ await announcementApi.withdraw(this.announcement.id, this.reason);
189
+ notify({ message: t('announcement.text.withdrawn-done') });
190
+ this.dispatchEvent(new CustomEvent('announcement-withdrawn', { bubbles: true, composed: true }));
191
+ this.close();
192
+ }
193
+ catch (error) {
194
+ notify({ level: 'error', message: reasonOf(error) });
195
+ }
196
+ finally {
197
+ this.busy = false;
198
+ }
199
+ }
200
+ close() {
201
+ this.dispatchEvent(new CustomEvent('done', { bubbles: true, composed: true }));
202
+ }
203
+ };
204
+ __decorate([
205
+ property({ attribute: false }),
206
+ __metadata("design:type", Object)
207
+ ], AnnouncementWithdrawForm.prototype, "announcement", void 0);
208
+ __decorate([
209
+ state(),
210
+ __metadata("design:type", Object)
211
+ ], AnnouncementWithdrawForm.prototype, "reason", void 0);
212
+ __decorate([
213
+ state(),
214
+ __metadata("design:type", Object)
215
+ ], AnnouncementWithdrawForm.prototype, "busy", void 0);
216
+ AnnouncementWithdrawForm = __decorate([
217
+ customElement('announcement-withdraw-form')
218
+ ], AnnouncementWithdrawForm);
219
+ export { AnnouncementWithdrawForm };
220
+ //# sourceMappingURL=announcement-receipts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"announcement-receipts.js","sourceRoot":"","sources":["../../client/components/announcement-receipts.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * The small forms a writer opens from an announcement (ADR-0163 decision 4): who read it and who acknowledged it, with\n * a reminder to those who have not; and the reason it is withdrawn.\n */\nimport '@material/web/button/filled-button.js'\nimport '@material/web/button/text-button.js'\nimport '@operato/popup/ox-popup-actions.js'\n\nimport { css, html, LitElement, nothing } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\n\nimport { i18next, localize } from '@operato/i18n'\nimport { notify } from '@operato/layout'\nimport { CommonPopupStyles, PopupBodyStyles } from '@operato/styles'\n\nimport { announcementApi, type AnnouncementItem } from '../utils/announcement-api.js'\nimport { reasonOf, t, when } from '../utils/announcement-words.js'\n\ntype Row = Awaited<ReturnType<typeof announcementApi.receipts>>[number]\n\nconst FormStyles = css`\n :host {\n height: 100%;\n line-height: 1.5;\n }\n .tabs {\n display: flex;\n gap: var(--spacing-small, 4px);\n margin-bottom: var(--spacing-medium, 8px);\n }\n .tabs button {\n border: 1px solid var(--ui-line-soft, var(--md-sys-color-outline-variant));\n background: none;\n border-radius: 999px;\n padding: 2px var(--spacing-medium, 8px);\n font: inherit;\n font-size: var(--ui-t-small, 12px);\n cursor: pointer;\n color: inherit;\n }\n .tabs button[aria-pressed='true'] {\n background: var(--md-sys-color-secondary-container);\n border-color: transparent;\n }\n table {\n width: 100%;\n border-collapse: collapse;\n font-size: var(--ui-t-body, 14px);\n font-variant-numeric: tabular-nums;\n }\n th,\n td {\n text-align: left;\n padding: var(--spacing-small, 4px) var(--spacing-medium, 8px);\n border-bottom: 1px solid var(--ui-line-soft, var(--md-sys-color-outline-variant));\n }\n th {\n font-weight: 600;\n font-size: var(--ui-t-small, 12px);\n color: var(--ui-muted, var(--md-sys-color-on-surface-variant));\n }\n .muted {\n color: var(--ui-muted, var(--md-sys-color-on-surface-variant));\n }\n textarea {\n width: 100%;\n min-height: 96px;\n box-sizing: border-box;\n font: inherit;\n }\n`\n\n@customElement('announcement-receipts')\nexport class AnnouncementReceipts extends localize(i18next)(LitElement) {\n static styles = [CommonPopupStyles, PopupBodyStyles, FormStyles]\n @property({ attribute: false }) announcement!: AnnouncementItem\n @state() private rows: Row[] = []\n @state() private which: 'all' | 'unread' | 'unacknowledged' | 'later' = 'all'\n @state() private busy = false\n\n connectedCallback() {\n super.connectedCallback()\n this.load()\n }\n\n private async load() {\n try {\n this.rows = await announcementApi.receipts(this.announcement.id)\n } catch (error) {\n notify({ level: 'error', message: reasonOf(error) })\n }\n }\n\n render() {\n const one = this.announcement\n const listed = this.rows.filter(row => row.listed)\n const counts = {\n all: listed.length,\n unread: listed.filter(row => !row.readAt).length,\n unacknowledged: listed.filter(row => !row.acknowledgedAt).length,\n later: this.rows.filter(row => !row.listed).length\n }\n const tabs = (['all', 'unread', ...(one.requiresAcknowledgement ? ['unacknowledged'] : []), ...(counts.later ? ['later'] : [])] as (keyof typeof counts)[]).map(\n key => html`<button aria-pressed=${String(this.which === key)} @click=${() => (this.which = key)}>${t(`announcement.receipts.${key}`)} ${counts[key]}</button>`\n )\n const shown =\n this.which === 'later'\n ? this.rows.filter(row => !row.listed)\n : listed.filter(row => (this.which === 'unread' ? !row.readAt : this.which === 'unacknowledged' ? !row.acknowledgedAt : true))\n const owed = one.requiresAcknowledgement ? counts.unacknowledged : counts.unread\n return html`<div class=\"body\">\n <div class=\"tabs\">${tabs}</div>\n <table>\n <thead>\n <tr>\n <th>${t('announcement.receipts.name')}</th>\n <th>${t('announcement.receipts.read-at')}</th>\n ${one.requiresAcknowledgement ? html`<th>${t('announcement.receipts.acknowledged-at')}</th>` : nothing}\n <th>${t('announcement.receipts.notified-at')}</th>\n </tr>\n </thead>\n <tbody>\n ${shown.map(\n row => html`<tr>\n <td>${row.name ?? row.email ?? row.userId}</td>\n <td class=${row.readAt ? '' : 'muted'}>${row.readAt ? when(row.readAt) : t('announcement.reader-state.unread')}</td>\n ${one.requiresAcknowledgement ? html`<td class=${row.acknowledgedAt ? '' : 'muted'}>${row.acknowledgedAt ? when(row.acknowledgedAt) : '—'}</td>` : nothing}\n <td class=\"muted\">${when(row.notifiedAt)}</td>\n </tr>`\n )}\n </tbody>\n </table>\n </div>\n <ox-popup-actions>\n <md-text-button slot=\"cancel\" @click=${() => this.close()}>${t('announcement.button.close')}</md-text-button>\n <md-filled-button slot=\"confirm\" ?disabled=${this.busy || !owed || one.status !== 'PUBLISHED'} @click=${() => this.remind()}\n >${t('announcement.button.remind-count', { count: owed })}</md-filled-button\n >\n </ox-popup-actions>`\n }\n\n private async remind() {\n this.busy = true\n try {\n const told = await announcementApi.remind(this.announcement.id)\n notify({ message: t('announcement.text.reminded', { count: told }) })\n await this.load()\n } catch (error) {\n notify({ level: 'error', message: reasonOf(error) })\n } finally {\n this.busy = false\n }\n }\n\n private close() {\n this.dispatchEvent(new CustomEvent('done', { bubbles: true, composed: true }))\n }\n}\n\n/** Why it is withdrawn - the readers' copy goes, the reason stays with its record. */\n@customElement('announcement-withdraw-form')\nexport class AnnouncementWithdrawForm extends localize(i18next)(LitElement) {\n static styles = [CommonPopupStyles, PopupBodyStyles, FormStyles]\n @property({ attribute: false }) announcement!: AnnouncementItem\n @state() private reason = ''\n @state() private busy = false\n\n render() {\n return html`<div class=\"body\">\n <p>${t('announcement.text.withdraw-lead')}</p>\n <textarea .value=${this.reason} maxlength=\"500\" placeholder=${t('announcement.text.withdraw-reason')} @input=${(e: any) => (this.reason = e.target.value)}></textarea>\n </div>\n <ox-popup-actions>\n <md-text-button slot=\"cancel\" @click=${() => this.close()}>${t('announcement.button.cancel')}</md-text-button>\n <md-filled-button slot=\"confirm\" ?disabled=${this.busy || !this.reason.trim()} @click=${() => this.withdraw()}>${t('announcement.button.withdraw')}</md-filled-button>\n </ox-popup-actions>`\n }\n\n private async withdraw() {\n this.busy = true\n try {\n await announcementApi.withdraw(this.announcement.id, this.reason)\n notify({ message: t('announcement.text.withdrawn-done') })\n this.dispatchEvent(new CustomEvent('announcement-withdrawn', { bubbles: true, composed: true }))\n this.close()\n } catch (error) {\n notify({ level: 'error', message: reasonOf(error) })\n } finally {\n this.busy = false\n }\n }\n\n private close() {\n this.dispatchEvent(new CustomEvent('done', { bubbles: true, composed: true }))\n }\n}\n"]}
@@ -0,0 +1,17 @@
1
+ import '@material/web/icon/icon.js';
2
+ import '@material/web/button/filled-button.js';
3
+ import { LitElement, nothing } from 'lit';
4
+ import { type AnnouncementItem } from '../utils/announcement-api.js';
5
+ /** A screen of the application opens in place; an https page opens apart. */
6
+ export declare function followLink(url: string): void;
7
+ declare const AnnouncementSheet_base: (new (...args: any[]) => LitElement) & typeof LitElement;
8
+ export declare class AnnouncementSheet extends AnnouncementSheet_base {
9
+ static styles: import("lit").CSSResult;
10
+ announcement: AnnouncementItem;
11
+ private busy;
12
+ render(): typeof nothing | import("lit-html").TemplateResult<1>;
13
+ private renderAcknowledge;
14
+ private follow;
15
+ private acknowledge;
16
+ }
17
+ export {};