@things-factory/announcement 10.1.248 → 10.1.249

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 +2 -2
@@ -0,0 +1,224 @@
1
+ import { __decorate, __metadata } from "tslib";
2
+ /*
3
+ * One announcement as its reader sees it - what kind it is and from whom, its body, its files, the screens it points to,
4
+ * and the acknowledgement it asks for (ADR-0163 decisions 3, 5). The screen and the popup both draw it.
5
+ */
6
+ import '@material/web/icon/icon.js';
7
+ import '@material/web/button/filled-button.js';
8
+ import { css, html, LitElement, nothing } from 'lit';
9
+ import { customElement, property, state } from 'lit/decorators.js';
10
+ import { unsafeHTML } from 'lit/directives/unsafe-html.js';
11
+ import { i18next, localize } from '@operato/i18n';
12
+ import { notify } from '@operato/layout';
13
+ import { navigate } from '@operato/shell';
14
+ import { renderMarkdown } from '@things-factory/ai-assistant/dist-client/index.js';
15
+ import { announcementApi } from '../utils/announcement-api.js';
16
+ import { categoryWord, importanceWord, reasonOf, t, when } from '../utils/announcement-words.js';
17
+ /** A screen of the application opens in place; an https page opens apart. */
18
+ export function followLink(url) {
19
+ if (/^https:\/\//i.test(url))
20
+ window.open(url, '_blank', 'noopener');
21
+ else
22
+ navigate(url);
23
+ }
24
+ let AnnouncementSheet = class AnnouncementSheet extends localize(i18next)(LitElement) {
25
+ constructor() {
26
+ super(...arguments);
27
+ this.busy = false;
28
+ }
29
+ static { this.styles = css `
30
+ :host {
31
+ display: flex;
32
+ flex-direction: column;
33
+ gap: var(--spacing-medium, 8px);
34
+ color: var(--md-sys-color-on-surface);
35
+ }
36
+ .meta {
37
+ display: flex;
38
+ flex-wrap: wrap;
39
+ align-items: center;
40
+ gap: var(--spacing-small, 4px) var(--spacing-medium, 8px);
41
+ font-size: var(--ui-t-small, 12px);
42
+ color: var(--ui-muted, var(--md-sys-color-on-surface-variant));
43
+ }
44
+ .chip {
45
+ display: inline-flex;
46
+ align-items: center;
47
+ gap: 2px;
48
+ padding: 0 var(--spacing-small, 4px);
49
+ border-radius: var(--border-radius, 4px);
50
+ border: 1px solid var(--ui-line-soft, var(--md-sys-color-outline-variant));
51
+ line-height: 1.6;
52
+ }
53
+ .chip.URGENT {
54
+ color: var(--md-sys-color-error);
55
+ border-color: currentColor;
56
+ }
57
+ .chip.IMPORTANT {
58
+ color: var(--md-sys-color-primary);
59
+ border-color: currentColor;
60
+ }
61
+ .chip md-icon {
62
+ --md-icon-size: 14px;
63
+ }
64
+ .markdown {
65
+ font-size: var(--ui-t-body, 14px);
66
+ line-height: 1.6;
67
+ overflow-wrap: anywhere;
68
+ }
69
+ .markdown :is(h1, h2, h3, h4) {
70
+ margin: var(--spacing-medium, 8px) 0 var(--spacing-small, 4px);
71
+ font-size: var(--ui-t-title, 16px);
72
+ }
73
+ .markdown :is(p, ul, ol, blockquote, pre, table) {
74
+ margin: 0 0 var(--spacing-medium, 8px);
75
+ }
76
+ .markdown :is(ul, ol) {
77
+ padding-inline-start: 1.4em;
78
+ list-style: revert;
79
+ display: block;
80
+ }
81
+ .markdown a {
82
+ color: var(--md-sys-color-primary);
83
+ }
84
+ .markdown table {
85
+ border-collapse: collapse;
86
+ }
87
+ .markdown :is(th, td) {
88
+ border: 1px solid var(--ui-line-soft, var(--md-sys-color-outline-variant));
89
+ padding: 2px var(--spacing-small, 4px);
90
+ }
91
+ h4 {
92
+ margin: var(--spacing-medium, 8px) 0 var(--spacing-small, 4px);
93
+ font-size: var(--ui-t-small, 12px);
94
+ color: var(--ui-muted, var(--md-sys-color-on-surface-variant));
95
+ font-weight: 600;
96
+ }
97
+ ul.files {
98
+ margin: 0;
99
+ padding: 0;
100
+ list-style: none;
101
+ display: flex;
102
+ flex-direction: column;
103
+ gap: 2px;
104
+ }
105
+ a,
106
+ .link {
107
+ display: inline-flex;
108
+ align-items: center;
109
+ gap: var(--spacing-small, 4px);
110
+ color: var(--md-sys-color-primary);
111
+ text-decoration: none;
112
+ cursor: pointer;
113
+ background: none;
114
+ border: 0;
115
+ padding: 0;
116
+ font: inherit;
117
+ }
118
+ a:hover,
119
+ .link:hover {
120
+ text-decoration: underline;
121
+ }
122
+ a md-icon,
123
+ .link md-icon {
124
+ --md-icon-size: 16px;
125
+ }
126
+ .size {
127
+ color: var(--ui-muted, var(--md-sys-color-on-surface-variant));
128
+ font-size: var(--ui-t-small, 12px);
129
+ }
130
+ .acknowledge {
131
+ display: flex;
132
+ flex-wrap: wrap;
133
+ align-items: center;
134
+ justify-content: space-between;
135
+ gap: var(--spacing-medium, 8px);
136
+ padding: var(--spacing-medium, 8px) var(--spacing-large, 12px);
137
+ border-radius: var(--border-radius, 4px);
138
+ background: var(--md-sys-color-surface-container, rgba(0, 0, 0, 0.04));
139
+ font-size: var(--ui-t-body, 14px);
140
+ }
141
+ .acknowledge.done {
142
+ color: var(--ui-muted, var(--md-sys-color-on-surface-variant));
143
+ }
144
+ `; }
145
+ render() {
146
+ const one = this.announcement;
147
+ if (!one)
148
+ return nothing;
149
+ const files = one.attachments ?? [];
150
+ const links = one.links ?? [];
151
+ return html `<div class="meta">
152
+ ${one.pinned ? html `<span class="chip"><md-icon>push_pin</md-icon>${t('announcement.field.pinned')}</span>` : nothing}
153
+ ${one.importance !== 'NORMAL' ? html `<span class="chip ${one.importance}">${importanceWord(one.importance)}</span>` : nothing}
154
+ <span class="chip">${categoryWord(one.category)}</span>
155
+ <span>${one.publisher?.name ?? one.creator?.name ?? ''}</span>
156
+ <span>${when(one.publishedAt ?? one.startsAt)}</span>
157
+ ${one.endsAt ? html `<span>${t('announcement.text.until', { when: when(one.endsAt) })}</span>` : nothing}
158
+ ${one.version > 1 ? html `<span>${t('announcement.text.version', { version: one.version })}</span>` : nothing}
159
+ </div>
160
+ ${one.body ? html `<div class="markdown">${unsafeHTML(renderMarkdown(one.body))}</div>` : nothing}
161
+ ${files.length
162
+ ? html `<h4>${t('announcement.field.attachments')}</h4>
163
+ <ul class="files">
164
+ ${files.map(file => html `<li>
165
+ <a href=${file.fullpath} target="_blank" rel="noopener" download=${file.name}><md-icon>attach_file</md-icon>${file.name}</a>
166
+ ${file.size ? html `<span class="size">${Math.max(1, Math.round(file.size / 1024))} KB</span>` : nothing}
167
+ </li>`)}
168
+ </ul>`
169
+ : nothing}
170
+ ${links.length
171
+ ? html `<h4>${t('announcement.field.links')}</h4>
172
+ <ul class="files">
173
+ ${links.map(link => html `<li><button class="link" @click=${() => this.follow(link.url)}><md-icon>open_in_new</md-icon>${link.label}</button></li>`)}
174
+ </ul>`
175
+ : nothing}
176
+ ${this.renderAcknowledge()}`;
177
+ }
178
+ renderAcknowledge() {
179
+ const one = this.announcement;
180
+ if (!one.requiresAcknowledgement || one.status !== 'PUBLISHED')
181
+ return nothing;
182
+ const by = one.acknowledgeBy ? t('announcement.text.acknowledge-by', { when: when(one.acknowledgeBy, false) }) : '';
183
+ if (one.mine?.acknowledgedAt)
184
+ return html `<div class="acknowledge done">${t('announcement.text.acknowledged-at', { when: when(one.mine.acknowledgedAt) })}</div>`;
185
+ /* Only its audience acknowledges; a writer looking at one addressed to others sees what is asked. */
186
+ if (!one.addressed)
187
+ return html `<div class="acknowledge">${t('announcement.text.acknowledge-asked')} ${by}</div>`;
188
+ return html `<div class="acknowledge">
189
+ <span>${t('announcement.text.acknowledge-asked')} ${by}</span>
190
+ <md-filled-button ?disabled=${this.busy} @click=${() => this.acknowledge()}>${t('announcement.button.acknowledge')}</md-filled-button>
191
+ </div>`;
192
+ }
193
+ follow(url) {
194
+ this.dispatchEvent(new CustomEvent('announcement-follow', { bubbles: true, composed: true }));
195
+ followLink(url);
196
+ }
197
+ async acknowledge() {
198
+ this.busy = true;
199
+ try {
200
+ const done = await announcementApi.acknowledge(this.announcement.id);
201
+ this.announcement = { ...this.announcement, ...done };
202
+ this.dispatchEvent(new CustomEvent('announcement-acknowledged', { bubbles: true, composed: true, detail: this.announcement }));
203
+ }
204
+ catch (error) {
205
+ notify({ level: 'error', message: reasonOf(error) });
206
+ }
207
+ finally {
208
+ this.busy = false;
209
+ }
210
+ }
211
+ };
212
+ __decorate([
213
+ property({ attribute: false }),
214
+ __metadata("design:type", Object)
215
+ ], AnnouncementSheet.prototype, "announcement", void 0);
216
+ __decorate([
217
+ state(),
218
+ __metadata("design:type", Object)
219
+ ], AnnouncementSheet.prototype, "busy", void 0);
220
+ AnnouncementSheet = __decorate([
221
+ customElement('announcement-sheet')
222
+ ], AnnouncementSheet);
223
+ export { AnnouncementSheet };
224
+ //# sourceMappingURL=announcement-sheet.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"announcement-sheet.js","sourceRoot":"","sources":["../../client/components/announcement-sheet.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * One announcement as its reader sees it - what kind it is and from whom, its body, its files, the screens it points to,\n * and the acknowledgement it asks for (ADR-0163 decisions 3, 5). The screen and the popup both draw it.\n */\nimport '@material/web/icon/icon.js'\nimport '@material/web/button/filled-button.js'\n\nimport { css, html, LitElement, nothing } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\nimport { unsafeHTML } from 'lit/directives/unsafe-html.js'\n\nimport { i18next, localize } from '@operato/i18n'\nimport { notify } from '@operato/layout'\nimport { navigate } from '@operato/shell'\nimport { renderMarkdown } from '@things-factory/ai-assistant/dist-client/index.js'\n\nimport { announcementApi, type AnnouncementItem } from '../utils/announcement-api.js'\nimport { categoryWord, importanceWord, reasonOf, t, when } from '../utils/announcement-words.js'\n\n/** A screen of the application opens in place; an https page opens apart. */\nexport function followLink(url: string) {\n if (/^https:\\/\\//i.test(url)) window.open(url, '_blank', 'noopener')\n else navigate(url)\n}\n\n@customElement('announcement-sheet')\nexport class AnnouncementSheet extends localize(i18next)(LitElement) {\n static styles = css`\n :host {\n display: flex;\n flex-direction: column;\n gap: var(--spacing-medium, 8px);\n color: var(--md-sys-color-on-surface);\n }\n .meta {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--spacing-small, 4px) var(--spacing-medium, 8px);\n font-size: var(--ui-t-small, 12px);\n color: var(--ui-muted, var(--md-sys-color-on-surface-variant));\n }\n .chip {\n display: inline-flex;\n align-items: center;\n gap: 2px;\n padding: 0 var(--spacing-small, 4px);\n border-radius: var(--border-radius, 4px);\n border: 1px solid var(--ui-line-soft, var(--md-sys-color-outline-variant));\n line-height: 1.6;\n }\n .chip.URGENT {\n color: var(--md-sys-color-error);\n border-color: currentColor;\n }\n .chip.IMPORTANT {\n color: var(--md-sys-color-primary);\n border-color: currentColor;\n }\n .chip md-icon {\n --md-icon-size: 14px;\n }\n .markdown {\n font-size: var(--ui-t-body, 14px);\n line-height: 1.6;\n overflow-wrap: anywhere;\n }\n .markdown :is(h1, h2, h3, h4) {\n margin: var(--spacing-medium, 8px) 0 var(--spacing-small, 4px);\n font-size: var(--ui-t-title, 16px);\n }\n .markdown :is(p, ul, ol, blockquote, pre, table) {\n margin: 0 0 var(--spacing-medium, 8px);\n }\n .markdown :is(ul, ol) {\n padding-inline-start: 1.4em;\n list-style: revert;\n display: block;\n }\n .markdown a {\n color: var(--md-sys-color-primary);\n }\n .markdown table {\n border-collapse: collapse;\n }\n .markdown :is(th, td) {\n border: 1px solid var(--ui-line-soft, var(--md-sys-color-outline-variant));\n padding: 2px var(--spacing-small, 4px);\n }\n h4 {\n margin: var(--spacing-medium, 8px) 0 var(--spacing-small, 4px);\n font-size: var(--ui-t-small, 12px);\n color: var(--ui-muted, var(--md-sys-color-on-surface-variant));\n font-weight: 600;\n }\n ul.files {\n margin: 0;\n padding: 0;\n list-style: none;\n display: flex;\n flex-direction: column;\n gap: 2px;\n }\n a,\n .link {\n display: inline-flex;\n align-items: center;\n gap: var(--spacing-small, 4px);\n color: var(--md-sys-color-primary);\n text-decoration: none;\n cursor: pointer;\n background: none;\n border: 0;\n padding: 0;\n font: inherit;\n }\n a:hover,\n .link:hover {\n text-decoration: underline;\n }\n a md-icon,\n .link md-icon {\n --md-icon-size: 16px;\n }\n .size {\n color: var(--ui-muted, var(--md-sys-color-on-surface-variant));\n font-size: var(--ui-t-small, 12px);\n }\n .acknowledge {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n justify-content: space-between;\n gap: var(--spacing-medium, 8px);\n padding: var(--spacing-medium, 8px) var(--spacing-large, 12px);\n border-radius: var(--border-radius, 4px);\n background: var(--md-sys-color-surface-container, rgba(0, 0, 0, 0.04));\n font-size: var(--ui-t-body, 14px);\n }\n .acknowledge.done {\n color: var(--ui-muted, var(--md-sys-color-on-surface-variant));\n }\n `\n\n @property({ attribute: false }) announcement!: AnnouncementItem\n @state() private busy = false\n\n render() {\n const one = this.announcement\n if (!one) return nothing\n const files = one.attachments ?? []\n const links = one.links ?? []\n return html`<div class=\"meta\">\n ${one.pinned ? html`<span class=\"chip\"><md-icon>push_pin</md-icon>${t('announcement.field.pinned')}</span>` : nothing}\n ${one.importance !== 'NORMAL' ? html`<span class=\"chip ${one.importance}\">${importanceWord(one.importance)}</span>` : nothing}\n <span class=\"chip\">${categoryWord(one.category)}</span>\n <span>${one.publisher?.name ?? one.creator?.name ?? ''}</span>\n <span>${when(one.publishedAt ?? one.startsAt)}</span>\n ${one.endsAt ? html`<span>${t('announcement.text.until', { when: when(one.endsAt) })}</span>` : nothing}\n ${one.version > 1 ? html`<span>${t('announcement.text.version', { version: one.version })}</span>` : nothing}\n </div>\n ${one.body ? html`<div class=\"markdown\">${unsafeHTML(renderMarkdown(one.body))}</div>` : nothing}\n ${files.length\n ? html`<h4>${t('announcement.field.attachments')}</h4>\n <ul class=\"files\">\n ${files.map(\n file =>\n html`<li>\n <a href=${file.fullpath} target=\"_blank\" rel=\"noopener\" download=${file.name}><md-icon>attach_file</md-icon>${file.name}</a>\n ${file.size ? html`<span class=\"size\">${Math.max(1, Math.round(file.size / 1024))} KB</span>` : nothing}\n </li>`\n )}\n </ul>`\n : nothing}\n ${links.length\n ? html`<h4>${t('announcement.field.links')}</h4>\n <ul class=\"files\">\n ${links.map(link => html`<li><button class=\"link\" @click=${() => this.follow(link.url)}><md-icon>open_in_new</md-icon>${link.label}</button></li>`)}\n </ul>`\n : nothing}\n ${this.renderAcknowledge()}`\n }\n\n private renderAcknowledge() {\n const one = this.announcement\n if (!one.requiresAcknowledgement || one.status !== 'PUBLISHED') return nothing\n const by = one.acknowledgeBy ? t('announcement.text.acknowledge-by', { when: when(one.acknowledgeBy, false) }) : ''\n if (one.mine?.acknowledgedAt) return html`<div class=\"acknowledge done\">${t('announcement.text.acknowledged-at', { when: when(one.mine.acknowledgedAt) })}</div>`\n /* Only its audience acknowledges; a writer looking at one addressed to others sees what is asked. */\n if (!one.addressed) return html`<div class=\"acknowledge\">${t('announcement.text.acknowledge-asked')} ${by}</div>`\n return html`<div class=\"acknowledge\">\n <span>${t('announcement.text.acknowledge-asked')} ${by}</span>\n <md-filled-button ?disabled=${this.busy} @click=${() => this.acknowledge()}>${t('announcement.button.acknowledge')}</md-filled-button>\n </div>`\n }\n\n private follow(url: string) {\n this.dispatchEvent(new CustomEvent('announcement-follow', { bubbles: true, composed: true }))\n followLink(url)\n }\n\n private async acknowledge() {\n this.busy = true\n try {\n const done = await announcementApi.acknowledge(this.announcement.id)\n this.announcement = { ...this.announcement, ...done }\n this.dispatchEvent(new CustomEvent('announcement-acknowledged', { bubbles: true, composed: true, detail: this.announcement }))\n } catch (error) {\n notify({ level: 'error', message: reasonOf(error) })\n } finally {\n this.busy = false\n }\n }\n}\n"]}
@@ -0,0 +1,32 @@
1
+ import '@material/web/icon/icon.js';
2
+ import '@operato/input/ox-select.js';
3
+ import '@operato/popup/ox-popup-list.js';
4
+ import { LitElement } from 'lit';
5
+ import { type Audience } from '../utils/announcement-api.js';
6
+ /** The framework chooser - a popup list with its search box on top. `change` hears only a value other than the one shown. */
7
+ export declare function chooser(options: {
8
+ value: string;
9
+ display: string;
10
+ }[], value: string, change: (value: string) => void, placeholder: string, label: string): import("lit-html").TemplateResult<1>;
11
+ declare const AudiencePicker_base: (new (...args: any[]) => LitElement) & typeof LitElement;
12
+ export declare class AudiencePicker extends AudiencePicker_base {
13
+ static styles: import("lit").CSSResult;
14
+ value: Audience[];
15
+ disabled: boolean;
16
+ private choices?;
17
+ private text;
18
+ private found;
19
+ private size?;
20
+ private searching?;
21
+ private counting?;
22
+ connectedCallback(): Promise<void>;
23
+ updated(changes: Map<string, unknown>): void;
24
+ render(): import("lit-html").TemplateResult<1>;
25
+ private give;
26
+ private take;
27
+ private tell;
28
+ private search;
29
+ /** How many the audience names now - asked a moment after it stops changing. */
30
+ private count;
31
+ }
32
+ export {};
@@ -0,0 +1,229 @@
1
+ import { __decorate, __metadata } from "tslib";
2
+ /*
3
+ * Whom an announcement goes to - the whole domain, departments (each with those under it), roles and people
4
+ * (ADR-0163 decision 2), with how many people that names now. Changes go up as `change` with the whole list.
5
+ */
6
+ import '@material/web/icon/icon.js';
7
+ import '@operato/input/ox-select.js';
8
+ import '@operato/popup/ox-popup-list.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 { announcementApi } from '../utils/announcement-api.js';
13
+ import { t } from '../utils/announcement-words.js';
14
+ const ICON = { DOMAIN: 'domain', DEPARTMENT: 'account_tree', ROLE: 'badge', USER: 'person' };
15
+ /** The framework chooser - a popup list with its search box on top. `change` hears only a value other than the one shown. */
16
+ export function chooser(options, value, change, placeholder, label) {
17
+ const shown = options.find(option => option.value === value)?.display ?? placeholder;
18
+ return html `<ox-select
19
+ .value=${value}
20
+ aria-label=${label}
21
+ @change=${(e) => {
22
+ /*
23
+ * Answered by the callback. ox-select's change is composed: left to bubble, it reaches the picker's host with an
24
+ * id for detail, after the picker's own change with the list, and the editor took the id as its audiences
25
+ * (「e.audiences.map is not a function」 on saving, 2026-10-30).
26
+ */
27
+ e.stopPropagation();
28
+ const next = String(e.detail ?? '');
29
+ if (next !== value)
30
+ change(next);
31
+ }}
32
+ >
33
+ <span slot="label">${shown}</span>
34
+ <ox-popup-list align-left nowrap with-search>${options.map(option => html `<div option value=${option.value}>${option.display}</div>`)}</ox-popup-list>
35
+ </ox-select>`;
36
+ }
37
+ let AudiencePicker = class AudiencePicker extends localize(i18next)(LitElement) {
38
+ constructor() {
39
+ super(...arguments);
40
+ this.value = [];
41
+ this.disabled = false;
42
+ this.text = '';
43
+ this.found = [];
44
+ }
45
+ static { this.styles = css `
46
+ :host {
47
+ display: flex;
48
+ flex-direction: column;
49
+ gap: var(--spacing-medium, 8px);
50
+ }
51
+ .chosen {
52
+ display: flex;
53
+ flex-wrap: wrap;
54
+ gap: var(--spacing-small, 4px);
55
+ min-height: 28px;
56
+ align-items: center;
57
+ }
58
+ .chip {
59
+ display: inline-flex;
60
+ align-items: center;
61
+ gap: 4px;
62
+ padding: 2px 4px 2px 8px;
63
+ border-radius: 999px;
64
+ background: var(--md-sys-color-secondary-container);
65
+ color: var(--md-sys-color-on-secondary-container);
66
+ font-size: var(--ui-t-body, 14px);
67
+ }
68
+ .chip md-icon {
69
+ --md-icon-size: 16px;
70
+ }
71
+ .chip button {
72
+ border: 0;
73
+ background: none;
74
+ cursor: pointer;
75
+ color: inherit;
76
+ display: inline-flex;
77
+ padding: 0;
78
+ }
79
+ .add {
80
+ display: flex;
81
+ flex-wrap: wrap;
82
+ gap: var(--spacing-small, 4px) var(--spacing-medium, 8px);
83
+ align-items: center;
84
+ }
85
+ .add ox-select {
86
+ min-width: 180px;
87
+ }
88
+ .add > button,
89
+ .found button {
90
+ border: 1px solid var(--ui-line-soft, var(--md-sys-color-outline-variant));
91
+ background: none;
92
+ border-radius: var(--border-radius, 4px);
93
+ padding: 2px var(--spacing-medium, 8px);
94
+ cursor: pointer;
95
+ font: inherit;
96
+ color: inherit;
97
+ }
98
+ .people {
99
+ position: relative;
100
+ }
101
+ .found {
102
+ display: flex;
103
+ flex-wrap: wrap;
104
+ gap: var(--spacing-small, 4px);
105
+ margin-top: var(--spacing-small, 4px);
106
+ }
107
+ .size,
108
+ .none {
109
+ font-size: var(--ui-t-small, 12px);
110
+ color: var(--ui-muted, var(--md-sys-color-on-surface-variant));
111
+ }
112
+ `; }
113
+ async connectedCallback() {
114
+ super.connectedCallback();
115
+ try {
116
+ this.choices = await announcementApi.choices();
117
+ }
118
+ catch {
119
+ this.choices = { domainId: '', domainName: '', departments: [], roles: [] };
120
+ }
121
+ }
122
+ updated(changes) {
123
+ if (changes.has('value'))
124
+ this.count();
125
+ }
126
+ render() {
127
+ const choices = this.choices;
128
+ const has = (scope, id) => this.value.some(one => one.scope === scope && one.targetId === id);
129
+ return html `<div class="chosen">
130
+ ${this.value.length
131
+ ? this.value.map(one => html `<span class="chip"
132
+ ><md-icon>${ICON[one.scope] ?? 'label'}</md-icon>${one.targetName ?? one.targetId}${one.scope === 'DEPARTMENT' ? html `<small>${t('announcement.text.with-sub-departments')}</small>` : nothing}
133
+ ${this.disabled ? nothing : html `<button aria-label=${t('announcement.button.remove')} @click=${() => this.take(one)}><md-icon>close</md-icon></button>`}</span
134
+ >`)
135
+ : html `<span class="none">${t('announcement.text.no-audience')}</span>`}
136
+ </div>
137
+ ${this.disabled || !choices
138
+ ? nothing
139
+ : html `<div class="add">
140
+ ${has('DOMAIN', choices.domainId) ? nothing : html `<button @click=${() => this.give({ scope: 'DOMAIN', targetId: choices.domainId, targetName: t('announcement.text.everyone-in', { name: choices.domainName }) })}>${t('announcement.text.everyone')}</button>`}
141
+ ${choices.departments.length
142
+ ? chooser(choices.departments.filter(one => !has('DEPARTMENT', one.id)).map(one => ({ value: one.id, display: one.note ? `${one.name} (${one.note})` : one.name })), '', id => this.give({ scope: 'DEPARTMENT', targetId: id, targetName: choices.departments.find(one => one.id === id)?.name }), t('announcement.text.add-department'), t('announcement.text.add-department'))
143
+ : nothing}
144
+ ${choices.roles.length
145
+ ? chooser(choices.roles.filter(one => !has('ROLE', one.id)).map(one => ({ value: one.id, display: one.name })), '', id => this.give({ scope: 'ROLE', targetId: id, targetName: choices.roles.find(one => one.id === id)?.name }), t('announcement.text.add-role'), t('announcement.text.add-role'))
146
+ : nothing}
147
+ <span class="people">
148
+ <input type="search" .value=${this.text} placeholder=${t('announcement.text.add-person')} @input=${(e) => this.search(e.target.value)} />
149
+ </span>
150
+ </div>
151
+ ${this.found.length
152
+ ? html `<div class="found">
153
+ ${this.found
154
+ .filter(one => !has('USER', one.id))
155
+ .map(one => html `<button @click=${() => this.give({ scope: 'USER', targetId: one.id, targetName: one.name })}>${one.name}${one.note && one.note !== one.name ? ` · ${one.note}` : ''}</button>`)}
156
+ </div>`
157
+ : nothing}`}
158
+ ${this.size === undefined ? nothing : html `<span class="size">${t('announcement.text.audience-size', { count: this.size })}</span>`}`;
159
+ }
160
+ give(target) {
161
+ this.text = '';
162
+ this.found = [];
163
+ this.tell([...this.value, target]);
164
+ }
165
+ take(target) {
166
+ this.tell(this.value.filter(one => !(one.scope === target.scope && one.targetId === target.targetId)));
167
+ }
168
+ tell(value) {
169
+ this.value = value;
170
+ this.dispatchEvent(new CustomEvent('change', { detail: value }));
171
+ }
172
+ search(text) {
173
+ this.text = text;
174
+ clearTimeout(this.searching);
175
+ this.searching = setTimeout(async () => {
176
+ try {
177
+ this.found = text.trim() ? await announcementApi.people(text.trim()) : [];
178
+ }
179
+ catch {
180
+ this.found = [];
181
+ }
182
+ }, 250);
183
+ }
184
+ /** How many the audience names now - asked a moment after it stops changing. */
185
+ count() {
186
+ clearTimeout(this.counting);
187
+ if (!this.value.length) {
188
+ this.size = 0;
189
+ return;
190
+ }
191
+ this.counting = setTimeout(async () => {
192
+ try {
193
+ this.size = await announcementApi.audienceSize(this.value.map(({ scope, targetId }) => ({ scope, targetId })));
194
+ }
195
+ catch {
196
+ this.size = undefined;
197
+ }
198
+ }, 300);
199
+ }
200
+ };
201
+ __decorate([
202
+ property({ attribute: false }),
203
+ __metadata("design:type", Array)
204
+ ], AudiencePicker.prototype, "value", void 0);
205
+ __decorate([
206
+ property({ type: Boolean }),
207
+ __metadata("design:type", Object)
208
+ ], AudiencePicker.prototype, "disabled", void 0);
209
+ __decorate([
210
+ state(),
211
+ __metadata("design:type", Object)
212
+ ], AudiencePicker.prototype, "choices", void 0);
213
+ __decorate([
214
+ state(),
215
+ __metadata("design:type", Object)
216
+ ], AudiencePicker.prototype, "text", void 0);
217
+ __decorate([
218
+ state(),
219
+ __metadata("design:type", Array)
220
+ ], AudiencePicker.prototype, "found", void 0);
221
+ __decorate([
222
+ state(),
223
+ __metadata("design:type", Number)
224
+ ], AudiencePicker.prototype, "size", void 0);
225
+ AudiencePicker = __decorate([
226
+ customElement('announcement-audience-picker')
227
+ ], AudiencePicker);
228
+ export { AudiencePicker };
229
+ //# sourceMappingURL=audience-picker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audience-picker.js","sourceRoot":"","sources":["../../client/components/audience-picker.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Whom an announcement goes to - the whole domain, departments (each with those under it), roles and people\n * (ADR-0163 decision 2), with how many people that names now. Changes go up as `change` with the whole list.\n */\nimport '@material/web/icon/icon.js'\nimport '@operato/input/ox-select.js'\nimport '@operato/popup/ox-popup-list.js'\n\nimport { css, html, LitElement, nothing } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\n\nimport { i18next, localize } from '@operato/i18n'\n\nimport { announcementApi, type Audience } from '../utils/announcement-api.js'\nimport { t } from '../utils/announcement-words.js'\n\ntype Choices = Awaited<ReturnType<typeof announcementApi.choices>>\n\nconst ICON: Record<string, string> = { DOMAIN: 'domain', DEPARTMENT: 'account_tree', ROLE: 'badge', USER: 'person' }\n\n/** The framework chooser - a popup list with its search box on top. `change` hears only a value other than the one shown. */\nexport function chooser(options: { value: string; display: string }[], value: string, change: (value: string) => void, placeholder: string, label: string) {\n const shown = options.find(option => option.value === value)?.display ?? placeholder\n return html`<ox-select\n .value=${value}\n aria-label=${label}\n @change=${(e: CustomEvent) => {\n /*\n * Answered by the callback. ox-select's change is composed: left to bubble, it reaches the picker's host with an\n * id for detail, after the picker's own change with the list, and the editor took the id as its audiences\n * (「e.audiences.map is not a function」 on saving, 2026-10-30).\n */\n e.stopPropagation()\n const next = String(e.detail ?? '')\n if (next !== value) change(next)\n }}\n >\n <span slot=\"label\">${shown}</span>\n <ox-popup-list align-left nowrap with-search>${options.map(option => html`<div option value=${option.value}>${option.display}</div>`)}</ox-popup-list>\n </ox-select>`\n}\n\n@customElement('announcement-audience-picker')\nexport class AudiencePicker extends localize(i18next)(LitElement) {\n static styles = css`\n :host {\n display: flex;\n flex-direction: column;\n gap: var(--spacing-medium, 8px);\n }\n .chosen {\n display: flex;\n flex-wrap: wrap;\n gap: var(--spacing-small, 4px);\n min-height: 28px;\n align-items: center;\n }\n .chip {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n padding: 2px 4px 2px 8px;\n border-radius: 999px;\n background: var(--md-sys-color-secondary-container);\n color: var(--md-sys-color-on-secondary-container);\n font-size: var(--ui-t-body, 14px);\n }\n .chip md-icon {\n --md-icon-size: 16px;\n }\n .chip button {\n border: 0;\n background: none;\n cursor: pointer;\n color: inherit;\n display: inline-flex;\n padding: 0;\n }\n .add {\n display: flex;\n flex-wrap: wrap;\n gap: var(--spacing-small, 4px) var(--spacing-medium, 8px);\n align-items: center;\n }\n .add ox-select {\n min-width: 180px;\n }\n .add > button,\n .found button {\n border: 1px solid var(--ui-line-soft, var(--md-sys-color-outline-variant));\n background: none;\n border-radius: var(--border-radius, 4px);\n padding: 2px var(--spacing-medium, 8px);\n cursor: pointer;\n font: inherit;\n color: inherit;\n }\n .people {\n position: relative;\n }\n .found {\n display: flex;\n flex-wrap: wrap;\n gap: var(--spacing-small, 4px);\n margin-top: var(--spacing-small, 4px);\n }\n .size,\n .none {\n font-size: var(--ui-t-small, 12px);\n color: var(--ui-muted, var(--md-sys-color-on-surface-variant));\n }\n `\n\n @property({ attribute: false }) value: Audience[] = []\n @property({ type: Boolean }) disabled = false\n @state() private choices?: Choices\n @state() private text = ''\n @state() private found: { id: string; name: string; note?: string }[] = []\n @state() private size?: number\n private searching?: ReturnType<typeof setTimeout>\n private counting?: ReturnType<typeof setTimeout>\n\n async connectedCallback() {\n super.connectedCallback()\n try {\n this.choices = await announcementApi.choices()\n } catch {\n this.choices = { domainId: '', domainName: '', departments: [], roles: [] }\n }\n }\n\n updated(changes: Map<string, unknown>) {\n if (changes.has('value')) this.count()\n }\n\n render() {\n const choices = this.choices\n const has = (scope: string, id: string) => this.value.some(one => one.scope === scope && one.targetId === id)\n return html`<div class=\"chosen\">\n ${this.value.length\n ? this.value.map(\n one => html`<span class=\"chip\"\n ><md-icon>${ICON[one.scope] ?? 'label'}</md-icon>${one.targetName ?? one.targetId}${one.scope === 'DEPARTMENT' ? html`<small>${t('announcement.text.with-sub-departments')}</small>` : nothing}\n ${this.disabled ? nothing : html`<button aria-label=${t('announcement.button.remove')} @click=${() => this.take(one)}><md-icon>close</md-icon></button>`}</span\n >`\n )\n : html`<span class=\"none\">${t('announcement.text.no-audience')}</span>`}\n </div>\n ${this.disabled || !choices\n ? nothing\n : html`<div class=\"add\">\n ${has('DOMAIN', choices.domainId) ? nothing : html`<button @click=${() => this.give({ scope: 'DOMAIN', targetId: choices.domainId, targetName: t('announcement.text.everyone-in', { name: choices.domainName }) })}>${t('announcement.text.everyone')}</button>`}\n ${choices.departments.length\n ? chooser(\n choices.departments.filter(one => !has('DEPARTMENT', one.id)).map(one => ({ value: one.id, display: one.note ? `${one.name} (${one.note})` : one.name })),\n '',\n id => this.give({ scope: 'DEPARTMENT', targetId: id, targetName: choices.departments.find(one => one.id === id)?.name }),\n t('announcement.text.add-department'),\n t('announcement.text.add-department')\n )\n : nothing}\n ${choices.roles.length\n ? chooser(\n choices.roles.filter(one => !has('ROLE', one.id)).map(one => ({ value: one.id, display: one.name })),\n '',\n id => this.give({ scope: 'ROLE', targetId: id, targetName: choices.roles.find(one => one.id === id)?.name }),\n t('announcement.text.add-role'),\n t('announcement.text.add-role')\n )\n : nothing}\n <span class=\"people\">\n <input type=\"search\" .value=${this.text} placeholder=${t('announcement.text.add-person')} @input=${(e: any) => this.search(e.target.value)} />\n </span>\n </div>\n ${this.found.length\n ? html`<div class=\"found\">\n ${this.found\n .filter(one => !has('USER', one.id))\n .map(one => html`<button @click=${() => this.give({ scope: 'USER', targetId: one.id, targetName: one.name })}>${one.name}${one.note && one.note !== one.name ? ` · ${one.note}` : ''}</button>`)}\n </div>`\n : nothing}`}\n ${this.size === undefined ? nothing : html`<span class=\"size\">${t('announcement.text.audience-size', { count: this.size })}</span>`}`\n }\n\n private give(target: Audience) {\n this.text = ''\n this.found = []\n this.tell([...this.value, target])\n }\n\n private take(target: Audience) {\n this.tell(this.value.filter(one => !(one.scope === target.scope && one.targetId === target.targetId)))\n }\n\n private tell(value: Audience[]) {\n this.value = value\n this.dispatchEvent(new CustomEvent('change', { detail: value }))\n }\n\n private search(text: string) {\n this.text = text\n clearTimeout(this.searching)\n this.searching = setTimeout(async () => {\n try {\n this.found = text.trim() ? await announcementApi.people(text.trim()) : []\n } catch {\n this.found = []\n }\n }, 250)\n }\n\n /** How many the audience names now - asked a moment after it stops changing. */\n private count() {\n clearTimeout(this.counting)\n if (!this.value.length) {\n this.size = 0\n return\n }\n this.counting = setTimeout(async () => {\n try {\n this.size = await announcementApi.audienceSize(this.value.map(({ scope, targetId }) => ({ scope, targetId })))\n } catch {\n this.size = undefined\n }\n }, 300)\n }\n}\n"]}
@@ -0,0 +1 @@
1
+ export { generateAnnouncementBadge, refreshAnnouncementBadge } from './utils/announcement-badge.js';
@@ -0,0 +1,2 @@
1
+ export { generateAnnouncementBadge, refreshAnnouncementBadge } from './utils/announcement-badge.js';
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../client/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,yBAAyB,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAA","sourcesContent":["export { generateAnnouncementBadge, refreshAnnouncementBadge } from './utils/announcement-badge.js'\n"]}
@@ -0,0 +1,59 @@
1
+ import '@material/web/icon/icon.js';
2
+ import '@operato/input/ox-input-file.js';
3
+ import '@operato/list-state/ox-page-state.js';
4
+ import '../components/audience-picker.js';
5
+ import { PageView } from '@operato/shell';
6
+ import { type AnnouncementItem, type AnnouncementLink, type Audience, type FileRow } from '../utils/announcement-api.js';
7
+ type Form = {
8
+ title: string;
9
+ body: string;
10
+ category: string;
11
+ importance: string;
12
+ startsAt: string;
13
+ endsAt: string;
14
+ pinned: boolean;
15
+ requiresAcknowledgement: boolean;
16
+ acknowledgeBy: string;
17
+ popup: boolean;
18
+ sendEmail: boolean;
19
+ links: AnnouncementLink[];
20
+ audiences: Audience[];
21
+ };
22
+ declare const AnnouncementEditor_base: (new (...args: any[]) => import("lit").LitElement) & typeof PageView;
23
+ /**
24
+ * 공지 작성 — what it says, to whom, when, and what it asks of its readers (ADR-0163 decisions 2-5). A new one is kept as
25
+ * a draft until it is published; one already out is changed into a new version, and its writer may tell everyone again.
26
+ * `?from=<id>` starts a new one as a copy.
27
+ */
28
+ export declare class AnnouncementEditor extends AnnouncementEditor_base {
29
+ static styles: import("lit").CSSResult[];
30
+ announcement: AnnouncementItem | null;
31
+ form: Form;
32
+ files: FileRow[];
33
+ added: File[];
34
+ removed: string[];
35
+ notifyAgain: boolean;
36
+ preview: boolean;
37
+ progress?: number;
38
+ loading: boolean;
39
+ error: unknown;
40
+ busy: boolean;
41
+ get out(): boolean;
42
+ get context(): {
43
+ frame: string;
44
+ menu: string;
45
+ actions: any[];
46
+ description?: string | undefined;
47
+ title: string;
48
+ };
49
+ pageUpdated(changes: any, lifecycle: any): Promise<void>;
50
+ private reset;
51
+ private load;
52
+ render(): import("lit-html").TemplateResult<1>;
53
+ private pick;
54
+ private input;
55
+ /** Keeps it, then sends it out when asked - a draft that cannot go out stays kept with the reason told. */
56
+ private save;
57
+ private deleteDraft;
58
+ }
59
+ export {};