@things-factory/announcement 10.1.256 → 10.1.258
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/client/components/announcement-receipts.ts +31 -36
- package/client/components/announcement-sheet.ts +18 -15
- package/client/components/audience-picker.ts +41 -28
- package/client/pages/announcement-editor.ts +125 -87
- package/client/pages/announcement-view.ts +38 -36
- package/client/utils/announcement-api.ts +3 -50
- package/dist-client/components/announcement-receipts.d.ts +6 -5
- package/dist-client/components/announcement-receipts.js +24 -52
- package/dist-client/components/announcement-receipts.js.map +1 -1
- package/dist-client/components/announcement-sheet.d.ts +2 -1
- package/dist-client/components/announcement-sheet.js +15 -21
- package/dist-client/components/announcement-sheet.js.map +1 -1
- package/dist-client/components/audience-picker.d.ts +8 -4
- package/dist-client/components/audience-picker.js +35 -35
- package/dist-client/components/audience-picker.js.map +1 -1
- package/dist-client/pages/announcement-editor.d.ts +22 -12
- package/dist-client/pages/announcement-editor.js +116 -130
- package/dist-client/pages/announcement-editor.js.map +1 -1
- package/dist-client/pages/announcement-view.d.ts +7 -4
- package/dist-client/pages/announcement-view.js +34 -48
- package/dist-client/pages/announcement-view.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-client/utils/announcement-api.d.ts +0 -47
- package/dist-client/utils/announcement-api.js +3 -21
- package/dist-client/utils/announcement-api.js.map +1 -1
- package/package.json +12 -12
|
@@ -6,10 +6,11 @@ import { __decorate, __metadata } from "tslib";
|
|
|
6
6
|
import '@material/web/icon/icon.js';
|
|
7
7
|
import '@operato/input/ox-select.js';
|
|
8
8
|
import '@operato/popup/ox-popup-list.js';
|
|
9
|
+
import gql from 'graphql-tag';
|
|
9
10
|
import { css, html, LitElement, nothing } from 'lit';
|
|
10
11
|
import { customElement, property, state } from 'lit/decorators.js';
|
|
12
|
+
import { QueryController } from '@operato/graphql';
|
|
11
13
|
import { i18next, localize } from '@operato/i18n';
|
|
12
|
-
import { announcementApi } from '../utils/announcement-api.js';
|
|
13
14
|
import { t } from '../utils/announcement-words.js';
|
|
14
15
|
const ICON = { DOMAIN: 'domain', DEPARTMENT: 'account_tree', ROLE: 'badge', USER: 'person' };
|
|
15
16
|
/** The framework chooser - a popup list with its search box on top. `change` hears only a value other than the one shown. */
|
|
@@ -40,7 +41,19 @@ let AudiencePicker = class AudiencePicker extends localize(i18next)(LitElement)
|
|
|
40
41
|
this.value = [];
|
|
41
42
|
this.disabled = false;
|
|
42
43
|
this.text = '';
|
|
43
|
-
|
|
44
|
+
/* The words searched for and the audience counted - each a moment after it stops changing. */
|
|
45
|
+
this.searched = '';
|
|
46
|
+
this.counted = [];
|
|
47
|
+
/* What may be chosen, the people the words find, and how many the audience names now (ADR-0190). */
|
|
48
|
+
this.choicesRead = new QueryController(this, gql `query { announcementAudienceChoices { domainId domainName departments { id name note } roles { id name note } } }`);
|
|
49
|
+
this.people = new QueryController(this, gql `query ($text: String!) { announcementPeople(text: $text) { id name note } }`, {
|
|
50
|
+
variables: () => (this.searched ? { text: this.searched } : undefined),
|
|
51
|
+
keepPrevious: true
|
|
52
|
+
});
|
|
53
|
+
this.sizeRead = new QueryController(this, gql `query ($audiences: [AnnouncementAudienceInput!]!) { announcementAudienceSize(audiences: $audiences) }`, {
|
|
54
|
+
variables: () => (this.counted.length ? { audiences: this.counted } : undefined),
|
|
55
|
+
keepPrevious: true
|
|
56
|
+
});
|
|
44
57
|
}
|
|
45
58
|
static { this.styles = css `
|
|
46
59
|
:host {
|
|
@@ -110,14 +123,18 @@ let AudiencePicker = class AudiencePicker extends localize(i18next)(LitElement)
|
|
|
110
123
|
color: var(--ui-muted, var(--md-sys-color-on-surface-variant));
|
|
111
124
|
}
|
|
112
125
|
`; }
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
126
|
+
get choices() {
|
|
127
|
+
if (this.choicesRead.error)
|
|
128
|
+
return { domainId: '', domainName: '', departments: [], roles: [] };
|
|
129
|
+
return this.choicesRead.data?.announcementAudienceChoices;
|
|
130
|
+
}
|
|
131
|
+
get found() {
|
|
132
|
+
return (this.searched && !this.people.error && this.people.data?.announcementPeople) || [];
|
|
133
|
+
}
|
|
134
|
+
get size() {
|
|
135
|
+
if (!this.value.length)
|
|
136
|
+
return 0;
|
|
137
|
+
return this.sizeRead.error ? undefined : this.sizeRead.data?.announcementAudienceSize;
|
|
121
138
|
}
|
|
122
139
|
updated(changes) {
|
|
123
140
|
if (changes.has('value'))
|
|
@@ -158,8 +175,9 @@ let AudiencePicker = class AudiencePicker extends localize(i18next)(LitElement)
|
|
|
158
175
|
${this.size === undefined ? nothing : html `<span class="size">${t('announcement.text.audience-size', { count: this.size })}</span>`}`;
|
|
159
176
|
}
|
|
160
177
|
give(target) {
|
|
178
|
+
clearTimeout(this.searching);
|
|
161
179
|
this.text = '';
|
|
162
|
-
this.
|
|
180
|
+
this.searched = '';
|
|
163
181
|
this.tell([...this.value, target]);
|
|
164
182
|
}
|
|
165
183
|
take(target) {
|
|
@@ -172,30 +190,16 @@ let AudiencePicker = class AudiencePicker extends localize(i18next)(LitElement)
|
|
|
172
190
|
search(text) {
|
|
173
191
|
this.text = text;
|
|
174
192
|
clearTimeout(this.searching);
|
|
175
|
-
this.searching = setTimeout(
|
|
176
|
-
try {
|
|
177
|
-
this.found = text.trim() ? await announcementApi.people(text.trim()) : [];
|
|
178
|
-
}
|
|
179
|
-
catch {
|
|
180
|
-
this.found = [];
|
|
181
|
-
}
|
|
182
|
-
}, 250);
|
|
193
|
+
this.searching = setTimeout(() => (this.searched = text.trim()), 250);
|
|
183
194
|
}
|
|
184
195
|
/** How many the audience names now - asked a moment after it stops changing. */
|
|
185
196
|
count() {
|
|
186
197
|
clearTimeout(this.counting);
|
|
187
198
|
if (!this.value.length) {
|
|
188
|
-
this.
|
|
199
|
+
this.counted = [];
|
|
189
200
|
return;
|
|
190
201
|
}
|
|
191
|
-
this.counting = setTimeout(
|
|
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);
|
|
202
|
+
this.counting = setTimeout(() => (this.counted = this.value.map(({ scope, targetId }) => ({ scope, targetId }))), 300);
|
|
199
203
|
}
|
|
200
204
|
};
|
|
201
205
|
__decorate([
|
|
@@ -209,19 +213,15 @@ __decorate([
|
|
|
209
213
|
__decorate([
|
|
210
214
|
state(),
|
|
211
215
|
__metadata("design:type", Object)
|
|
212
|
-
], AudiencePicker.prototype, "
|
|
216
|
+
], AudiencePicker.prototype, "text", void 0);
|
|
213
217
|
__decorate([
|
|
214
218
|
state(),
|
|
215
219
|
__metadata("design:type", Object)
|
|
216
|
-
], AudiencePicker.prototype, "
|
|
220
|
+
], AudiencePicker.prototype, "searched", void 0);
|
|
217
221
|
__decorate([
|
|
218
222
|
state(),
|
|
219
223
|
__metadata("design:type", Array)
|
|
220
|
-
], AudiencePicker.prototype, "
|
|
221
|
-
__decorate([
|
|
222
|
-
state(),
|
|
223
|
-
__metadata("design:type", Number)
|
|
224
|
-
], AudiencePicker.prototype, "size", void 0);
|
|
224
|
+
], AudiencePicker.prototype, "counted", void 0);
|
|
225
225
|
AudiencePicker = __decorate([
|
|
226
226
|
customElement('announcement-audience-picker')
|
|
227
227
|
], AudiencePicker);
|
|
@@ -1 +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"]}
|
|
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 gql from 'graphql-tag'\nimport { css, html, LitElement, nothing } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\n\nimport { QueryController } from '@operato/graphql'\nimport { i18next, localize } from '@operato/i18n'\n\nimport { type Audience } from '../utils/announcement-api.js'\nimport { t } from '../utils/announcement-words.js'\n\ntype Choices = { domainId: string; domainName: string; departments: { id: string; name: string; note?: string }[]; roles: { id: string; name: string; note?: string }[] }\ntype Person = { id: string; name: string; note?: string }\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 text = ''\n /* The words searched for and the audience counted - each a moment after it stops changing. */\n @state() private searched = ''\n @state() private counted: Audience[] = []\n private searching?: ReturnType<typeof setTimeout>\n private counting?: ReturnType<typeof setTimeout>\n\n /* What may be chosen, the people the words find, and how many the audience names now (ADR-0190). */\n private choicesRead = new QueryController(\n this,\n gql`query { announcementAudienceChoices { domainId domainName departments { id name note } roles { id name note } } }`\n )\n\n private people = new QueryController(this, gql`query ($text: String!) { announcementPeople(text: $text) { id name note } }`, {\n variables: () => (this.searched ? { text: this.searched } : undefined),\n keepPrevious: true\n })\n\n private sizeRead = new QueryController(this, gql`query ($audiences: [AnnouncementAudienceInput!]!) { announcementAudienceSize(audiences: $audiences) }`, {\n variables: () => (this.counted.length ? { audiences: this.counted } : undefined),\n keepPrevious: true\n })\n\n private get choices(): Choices | undefined {\n if (this.choicesRead.error) return { domainId: '', domainName: '', departments: [], roles: [] }\n return this.choicesRead.data?.announcementAudienceChoices\n }\n\n private get found(): Person[] {\n return (this.searched && !this.people.error && this.people.data?.announcementPeople) || []\n }\n\n private get size(): number | undefined {\n if (!this.value.length) return 0\n return this.sizeRead.error ? undefined : this.sizeRead.data?.announcementAudienceSize\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 clearTimeout(this.searching)\n this.text = ''\n this.searched = ''\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(() => (this.searched = text.trim()), 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.counted = []\n return\n }\n this.counting = setTimeout(() => (this.counted = this.value.map(({ scope, targetId }) => ({ scope, targetId }))), 300)\n }\n}\n"]}
|
|
@@ -18,6 +18,10 @@ type Form = {
|
|
|
18
18
|
sendEmail: boolean;
|
|
19
19
|
links: AnnouncementLink[];
|
|
20
20
|
audiences: Audience[];
|
|
21
|
+
/** Files picked to attach, attached ones to take off, and whether a changed one tells everyone again. */
|
|
22
|
+
added: File[];
|
|
23
|
+
removed: string[];
|
|
24
|
+
notifyAgain: boolean;
|
|
21
25
|
};
|
|
22
26
|
declare const AnnouncementEditor_base: (new (...args: any[]) => import("lit").LitElement) & typeof PageView;
|
|
23
27
|
/**
|
|
@@ -27,17 +31,22 @@ declare const AnnouncementEditor_base: (new (...args: any[]) => import("lit").Li
|
|
|
27
31
|
*/
|
|
28
32
|
export declare class AnnouncementEditor extends AnnouncementEditor_base {
|
|
29
33
|
static styles: import("lit").CSSResult[];
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
files: FileRow[];
|
|
33
|
-
added: File[];
|
|
34
|
-
removed: string[];
|
|
35
|
-
notifyAgain: boolean;
|
|
34
|
+
/** What the address opens: an announcement by id, a new one copied `from` another, or a new one. */
|
|
35
|
+
private address?;
|
|
36
36
|
preview: boolean;
|
|
37
37
|
progress?: number;
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
38
|
+
private record;
|
|
39
|
+
private draft;
|
|
40
|
+
private saves;
|
|
41
|
+
get announcement(): AnnouncementItem | null;
|
|
42
|
+
/** The form as it shows: each field's edit, else what was read (blank while it is read). */
|
|
43
|
+
get form(): Form;
|
|
44
|
+
/** The files attached so far - a copy starts with none. */
|
|
45
|
+
get files(): FileRow[];
|
|
46
|
+
/** A failed read, or an announcement the address names that is not there. */
|
|
47
|
+
get error(): unknown;
|
|
48
|
+
get loading(): boolean;
|
|
49
|
+
get busy(): boolean;
|
|
41
50
|
get out(): boolean;
|
|
42
51
|
get context(): {
|
|
43
52
|
frame: string;
|
|
@@ -47,12 +56,13 @@ export declare class AnnouncementEditor extends AnnouncementEditor_base {
|
|
|
47
56
|
title: string;
|
|
48
57
|
};
|
|
49
58
|
pageUpdated(changes: any, lifecycle: any): Promise<void>;
|
|
50
|
-
private reset;
|
|
51
|
-
private load;
|
|
52
59
|
render(): import("lit-html").TemplateResult<1>;
|
|
53
60
|
private pick;
|
|
54
61
|
private input;
|
|
55
|
-
/**
|
|
62
|
+
/**
|
|
63
|
+
* Keeps it, then sends it out when asked - a draft that cannot go out stays kept, opened as one. Failures are told by
|
|
64
|
+
* the GraphQL link.
|
|
65
|
+
*/
|
|
56
66
|
private save;
|
|
57
67
|
private deleteDraft;
|
|
58
68
|
}
|
|
@@ -3,18 +3,20 @@ import '@material/web/icon/icon.js';
|
|
|
3
3
|
import '@operato/input/ox-input-file.js';
|
|
4
4
|
import '@operato/list-state/ox-page-state.js';
|
|
5
5
|
import '../components/audience-picker.js';
|
|
6
|
+
import gql from 'graphql-tag';
|
|
6
7
|
import { css, html, nothing } from 'lit';
|
|
7
8
|
import { customElement, state } from 'lit/decorators.js';
|
|
8
9
|
import { unsafeHTML } from 'lit/directives/unsafe-html.js';
|
|
10
|
+
import { MutationController, QueryController, uploadContext } from '@operato/graphql';
|
|
9
11
|
import { i18next, localize } from '@operato/i18n';
|
|
10
12
|
import { OxPrompt } from '@operato/popup/ox-prompt.js';
|
|
11
13
|
import { notify } from '@operato/layout';
|
|
12
|
-
import { navigate, PageView } from '@operato/shell';
|
|
14
|
+
import { navigate, PageView, DraftController } from '@operato/shell';
|
|
13
15
|
import { renderMarkdown } from '@things-factory/ai-assistant/dist-client/index.js';
|
|
14
16
|
import { ButtonStyles, DocumentPageStyles, FormFieldStyles } from '@operato/styles';
|
|
15
17
|
import { chooser } from '../components/audience-picker.js';
|
|
16
|
-
import {
|
|
17
|
-
import { CATEGORIES, categoryWord, IMPORTANCES, importanceWord,
|
|
18
|
+
import { FULL_FIELDS } from '../utils/announcement-api.js';
|
|
19
|
+
import { CATEGORIES, categoryWord, IMPORTANCES, importanceWord, statusWord, t } from '../utils/announcement-words.js';
|
|
18
20
|
const blank = () => ({
|
|
19
21
|
title: '',
|
|
20
22
|
body: '',
|
|
@@ -28,7 +30,10 @@ const blank = () => ({
|
|
|
28
30
|
popup: false,
|
|
29
31
|
sendEmail: false,
|
|
30
32
|
links: [],
|
|
31
|
-
audiences: []
|
|
33
|
+
audiences: [],
|
|
34
|
+
added: [],
|
|
35
|
+
removed: [],
|
|
36
|
+
notifyAgain: false
|
|
32
37
|
});
|
|
33
38
|
/** `datetime-local` speaks the reader's own clock. */
|
|
34
39
|
const pad = (n) => String(n).padStart(2, '0');
|
|
@@ -54,7 +59,10 @@ function formOf(one) {
|
|
|
54
59
|
popup: one.popup,
|
|
55
60
|
sendEmail: !!one.sendEmail,
|
|
56
61
|
links: (one.links ?? []).map(link => ({ ...link })),
|
|
57
|
-
audiences: (one.audiences ?? []).map(({ scope, targetId, targetName }) => ({ scope, targetId, targetName }))
|
|
62
|
+
audiences: (one.audiences ?? []).map(({ scope, targetId, targetName }) => ({ scope, targetId, targetName })),
|
|
63
|
+
added: [],
|
|
64
|
+
removed: [],
|
|
65
|
+
notifyAgain: false
|
|
58
66
|
};
|
|
59
67
|
}
|
|
60
68
|
/**
|
|
@@ -65,16 +73,39 @@ function formOf(one) {
|
|
|
65
73
|
let AnnouncementEditor = class AnnouncementEditor extends localize(i18next)(PageView) {
|
|
66
74
|
constructor() {
|
|
67
75
|
super(...arguments);
|
|
68
|
-
this.announcement = null;
|
|
69
|
-
this.form = blank();
|
|
70
|
-
this.files = [];
|
|
71
|
-
this.added = [];
|
|
72
|
-
this.removed = [];
|
|
73
|
-
this.notifyAgain = false;
|
|
74
76
|
this.preview = false;
|
|
75
|
-
|
|
76
|
-
this.
|
|
77
|
-
|
|
77
|
+
/* The announcement edited, or the one a new one is copied from (ADR-0190). */
|
|
78
|
+
this.record = new QueryController(this, gql(`query ($id: String!) { announcement(id: $id) { ${FULL_FIELDS} readCount acknowledgedCount } }`), {
|
|
79
|
+
variables: () => {
|
|
80
|
+
const id = this.address?.id || this.address?.from;
|
|
81
|
+
return id ? { id } : undefined;
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
/* What is typed over the announcement read - a copy starts without its days, a new one blank. */
|
|
85
|
+
this.draft = new DraftController(this, {
|
|
86
|
+
read: () => {
|
|
87
|
+
if (!this.address)
|
|
88
|
+
return undefined;
|
|
89
|
+
if (!this.address.id && !this.address.from)
|
|
90
|
+
return blank();
|
|
91
|
+
const one = this.record.data?.announcement;
|
|
92
|
+
if (!one)
|
|
93
|
+
return undefined;
|
|
94
|
+
return this.address.from && !this.address.id ? { ...formOf(one), startsAt: '', endsAt: '' } : formOf(one);
|
|
95
|
+
},
|
|
96
|
+
key: () => this.address
|
|
97
|
+
});
|
|
98
|
+
/* A change reads the announcement again once it is done; one that goes out or away leaves the editor. */
|
|
99
|
+
this.saves = {
|
|
100
|
+
create: new MutationController(this, gql `mutation ($input: AnnouncementInput!, $attach: [Upload!]) { createAnnouncement(input: $input, attach: $attach) { id } }`),
|
|
101
|
+
update: new MutationController(this, gql `
|
|
102
|
+
mutation ($id: String!, $input: AnnouncementInput!, $attach: [Upload!], $remove: [String!], $notifyAgain: Boolean) {
|
|
103
|
+
updateAnnouncement(id: $id, input: $input, attach: $attach, remove: $remove, notifyAgain: $notifyAgain) { id status }
|
|
104
|
+
}
|
|
105
|
+
`, { refetch: () => [this.record] }),
|
|
106
|
+
publish: new MutationController(this, gql `mutation ($id: String!) { publishAnnouncement(id: $id) { id status startsAt } }`),
|
|
107
|
+
remove: new MutationController(this, gql `mutation ($id: String!) { deleteAnnouncement(id: $id) }`)
|
|
108
|
+
};
|
|
78
109
|
}
|
|
79
110
|
static { this.styles = [
|
|
80
111
|
FormFieldStyles,
|
|
@@ -190,6 +221,29 @@ let AnnouncementEditor = class AnnouncementEditor extends localize(i18next)(Page
|
|
|
190
221
|
}
|
|
191
222
|
`
|
|
192
223
|
]; }
|
|
224
|
+
get announcement() {
|
|
225
|
+
return (this.address?.id && this.record.data?.announcement) || null;
|
|
226
|
+
}
|
|
227
|
+
/** The form as it shows: each field's edit, else what was read (blank while it is read). */
|
|
228
|
+
get form() {
|
|
229
|
+
return Object.fromEntries(Object.entries(blank()).map(([name, given]) => [name, this.draft.value(name) ?? given]));
|
|
230
|
+
}
|
|
231
|
+
/** The files attached so far - a copy starts with none. */
|
|
232
|
+
get files() {
|
|
233
|
+
return this.announcement?.attachments ?? [];
|
|
234
|
+
}
|
|
235
|
+
/** A failed read, or an announcement the address names that is not there. */
|
|
236
|
+
get error() {
|
|
237
|
+
if (this.record.error)
|
|
238
|
+
return this.record.error;
|
|
239
|
+
return this.record.data && !this.record.data.announcement ? new Error(t('announcement.refusal.unknown-announcement')) : null;
|
|
240
|
+
}
|
|
241
|
+
get loading() {
|
|
242
|
+
return this.record.loading;
|
|
243
|
+
}
|
|
244
|
+
get busy() {
|
|
245
|
+
return Object.values(this.saves).some(save => save.pending);
|
|
246
|
+
}
|
|
193
247
|
get out() {
|
|
194
248
|
return this.announcement?.status === 'PUBLISHED';
|
|
195
249
|
}
|
|
@@ -219,55 +273,21 @@ let AnnouncementEditor = class AnnouncementEditor extends localize(i18next)(Page
|
|
|
219
273
|
if (!this.active || !(changes.active || changes.params))
|
|
220
274
|
return;
|
|
221
275
|
const { id, from } = lifecycle?.params ?? {};
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
else if (from) {
|
|
227
|
-
await this.load(from, true);
|
|
228
|
-
}
|
|
229
|
-
else {
|
|
230
|
-
this.announcement = null;
|
|
231
|
-
this.form = blank();
|
|
232
|
-
this.files = [];
|
|
233
|
-
this.reset();
|
|
234
|
-
this.updateContext();
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
reset() {
|
|
238
|
-
this.added = [];
|
|
239
|
-
this.removed = [];
|
|
240
|
-
this.notifyAgain = false;
|
|
276
|
+
const address = id ? { id } : from ? { from } : {};
|
|
277
|
+
if (JSON.stringify(address) === JSON.stringify(this.address))
|
|
278
|
+
return;
|
|
279
|
+
this.address = address;
|
|
241
280
|
this.preview = false;
|
|
242
281
|
}
|
|
243
|
-
async load(id, asCopy) {
|
|
244
|
-
this.loading = true;
|
|
245
|
-
this.error = null;
|
|
246
|
-
try {
|
|
247
|
-
const one = await announcementApi.one(id);
|
|
248
|
-
if (!one)
|
|
249
|
-
throw new Error(t('announcement.refusal.unknown-announcement'));
|
|
250
|
-
this.announcement = asCopy ? null : one;
|
|
251
|
-
this.form = asCopy ? { ...formOf(one), startsAt: '', endsAt: '' } : formOf(one);
|
|
252
|
-
this.files = asCopy ? [] : (one.attachments ?? []);
|
|
253
|
-
this.reset();
|
|
254
|
-
}
|
|
255
|
-
catch (error) {
|
|
256
|
-
this.error = error;
|
|
257
|
-
}
|
|
258
|
-
finally {
|
|
259
|
-
this.loading = false;
|
|
260
|
-
this.updateContext();
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
282
|
render() {
|
|
264
283
|
const form = this.form;
|
|
265
284
|
const set = (patch) => {
|
|
266
|
-
|
|
285
|
+
for (const [name, value] of Object.entries(patch))
|
|
286
|
+
this.draft.set(name, value);
|
|
267
287
|
this.updateContext();
|
|
268
288
|
};
|
|
269
289
|
const editable = !this.busy && (!this.announcement || !!this.announcement.editable);
|
|
270
|
-
return html `<ox-page-state .loading=${this.loading} .error=${this.error} @retry=${() => this.
|
|
290
|
+
return html `<ox-page-state .loading=${this.loading} .error=${this.error} @retry=${() => this.record.refetch()}>
|
|
271
291
|
<div class="page">
|
|
272
292
|
<section class="page-section">
|
|
273
293
|
<header class="page-section-header"><h3>${t('announcement.text.section-what')}</h3></header>
|
|
@@ -343,17 +363,17 @@ let AnnouncementEditor = class AnnouncementEditor extends localize(i18next)(Page
|
|
|
343
363
|
<section class="page-section">
|
|
344
364
|
<header class="page-section-header"><h3>${t('announcement.field.attachments')}</h3></header>
|
|
345
365
|
<ul class="files">
|
|
346
|
-
${this.files.map(file => html `<li class=${
|
|
366
|
+
${this.files.map(file => html `<li class=${form.removed.includes(file.id) ? 'removed' : ''}>
|
|
347
367
|
<md-icon>attach_file</md-icon>${file.name}
|
|
348
368
|
${editable
|
|
349
|
-
?
|
|
350
|
-
? html `<button @click=${() => (
|
|
351
|
-
: html `<button aria-label=${t('announcement.button.remove')} @click=${() => (
|
|
369
|
+
? form.removed.includes(file.id)
|
|
370
|
+
? html `<button @click=${() => set({ removed: form.removed.filter(id => id !== file.id) })}><md-icon>undo</md-icon></button>`
|
|
371
|
+
: html `<button aria-label=${t('announcement.button.remove')} @click=${() => set({ removed: [...form.removed, file.id] })}><md-icon>close</md-icon></button>`
|
|
352
372
|
: nothing}
|
|
353
373
|
</li>`)}
|
|
354
|
-
${
|
|
374
|
+
${form.added.map((file, index) => html `<li>
|
|
355
375
|
<md-icon>upload_file</md-icon>${file.name}
|
|
356
|
-
<button aria-label=${t('announcement.button.remove')} @click=${() => (
|
|
376
|
+
<button aria-label=${t('announcement.button.remove')} @click=${() => set({ added: form.added.filter((_, i) => i !== index) })}><md-icon>close</md-icon></button>
|
|
357
377
|
</li>`)}
|
|
358
378
|
</ul>
|
|
359
379
|
${editable ? html `<ox-input-file quiet hide-filelist multiple icon="attach_file" .progress=${this.progress} @change=${(e) => this.pick(e)}></ox-input-file>` : nothing}
|
|
@@ -388,7 +408,7 @@ let AnnouncementEditor = class AnnouncementEditor extends localize(i18next)(Page
|
|
|
388
408
|
|
|
389
409
|
${this.out
|
|
390
410
|
? html `<section class="page-section">
|
|
391
|
-
<label class="check"><input type="checkbox" .checked=${
|
|
411
|
+
<label class="check"><input type="checkbox" .checked=${form.notifyAgain} @change=${(e) => set({ notifyAgain: e.target.checked })} />${t('announcement.text.notify-again')}</label>
|
|
392
412
|
<span class="hint">${t('announcement.text.new-version', { version: (this.announcement?.version ?? 1) + 1 })}</span>
|
|
393
413
|
</section>`
|
|
394
414
|
: nothing}
|
|
@@ -401,7 +421,7 @@ let AnnouncementEditor = class AnnouncementEditor extends localize(i18next)(Page
|
|
|
401
421
|
if (!picked.length)
|
|
402
422
|
return;
|
|
403
423
|
e.target.reset?.();
|
|
404
|
-
this.added
|
|
424
|
+
this.draft.set('added', [...this.form.added, ...picked]);
|
|
405
425
|
}
|
|
406
426
|
input() {
|
|
407
427
|
const form = this.form;
|
|
@@ -421,37 +441,39 @@ let AnnouncementEditor = class AnnouncementEditor extends localize(i18next)(Page
|
|
|
421
441
|
audiences: form.audiences.map(({ scope, targetId, targetName }) => ({ scope, targetId, targetName: targetName ?? undefined }))
|
|
422
442
|
};
|
|
423
443
|
}
|
|
424
|
-
/**
|
|
444
|
+
/**
|
|
445
|
+
* Keeps it, then sends it out when asked - a draft that cannot go out stays kept, opened as one. Failures are told by
|
|
446
|
+
* the GraphQL link.
|
|
447
|
+
*/
|
|
425
448
|
async save(publish) {
|
|
426
|
-
this.busy
|
|
449
|
+
if (this.busy)
|
|
450
|
+
return;
|
|
451
|
+
const id = this.announcement?.id;
|
|
452
|
+
const { added, removed, notifyAgain } = this.form;
|
|
453
|
+
const context = added.length ? uploadContext(fraction => (this.progress = fraction)) : undefined;
|
|
454
|
+
const saving = this.draft.submit(() => id
|
|
455
|
+
? this.saves.update.run({ id, input: this.input(), attach: added, remove: removed, notifyAgain }, { context })
|
|
456
|
+
: this.saves.create.run({ input: this.input(), attach: added }, { context }));
|
|
457
|
+
/* The actions wait while it is out. */
|
|
458
|
+
this.updateContext();
|
|
459
|
+
const saved = await saving;
|
|
460
|
+
this.progress = undefined;
|
|
427
461
|
this.updateContext();
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
const sent = await announcementApi.publish(id);
|
|
437
|
-
notify({ message: sent.status === 'SCHEDULED' ? t('announcement.text.scheduled-done') : t('announcement.text.published-done') });
|
|
438
|
-
navigate(`announcement-view?id=${id}`);
|
|
462
|
+
if (!saved)
|
|
463
|
+
return;
|
|
464
|
+
const kept = id ?? saved.createAnnouncement.id;
|
|
465
|
+
if (publish) {
|
|
466
|
+
const sent = await this.saves.publish.run({ id: kept });
|
|
467
|
+
if (sent) {
|
|
468
|
+
notify({ message: sent.publishAnnouncement.status === 'SCHEDULED' ? t('announcement.text.scheduled-done') : t('announcement.text.published-done') });
|
|
469
|
+
navigate(`announcement-view?id=${kept}`);
|
|
439
470
|
return;
|
|
440
471
|
}
|
|
441
|
-
notify({ message: t('announcement.text.saved') });
|
|
442
|
-
if (this.announcement?.id === id)
|
|
443
|
-
await this.load(id, false);
|
|
444
|
-
else
|
|
445
|
-
navigate(`announcement-editor?id=${id}`);
|
|
446
|
-
}
|
|
447
|
-
catch (error) {
|
|
448
|
-
notify({ level: 'error', message: reasonOf(error) });
|
|
449
|
-
}
|
|
450
|
-
finally {
|
|
451
|
-
this.progress = undefined;
|
|
452
|
-
this.busy = false;
|
|
453
|
-
this.updateContext();
|
|
454
472
|
}
|
|
473
|
+
else
|
|
474
|
+
notify({ message: t('announcement.text.saved') });
|
|
475
|
+
if (!id)
|
|
476
|
+
navigate(`announcement-editor?id=${kept}`);
|
|
455
477
|
}
|
|
456
478
|
async deleteDraft() {
|
|
457
479
|
const sure = await OxPrompt.open({
|
|
@@ -462,40 +484,16 @@ let AnnouncementEditor = class AnnouncementEditor extends localize(i18next)(Page
|
|
|
462
484
|
});
|
|
463
485
|
if (!sure)
|
|
464
486
|
return;
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
}
|
|
470
|
-
catch (error) {
|
|
471
|
-
notify({ level: 'error', message: reasonOf(error) });
|
|
472
|
-
}
|
|
487
|
+
if (!(await this.saves.remove.run({ id: this.announcement.id })))
|
|
488
|
+
return;
|
|
489
|
+
notify({ message: t('announcement.text.deleted') });
|
|
490
|
+
navigate('announcement-manage-list');
|
|
473
491
|
}
|
|
474
492
|
};
|
|
475
493
|
__decorate([
|
|
476
494
|
state(),
|
|
477
495
|
__metadata("design:type", Object)
|
|
478
|
-
], AnnouncementEditor.prototype, "
|
|
479
|
-
__decorate([
|
|
480
|
-
state(),
|
|
481
|
-
__metadata("design:type", Object)
|
|
482
|
-
], AnnouncementEditor.prototype, "form", void 0);
|
|
483
|
-
__decorate([
|
|
484
|
-
state(),
|
|
485
|
-
__metadata("design:type", Array)
|
|
486
|
-
], AnnouncementEditor.prototype, "files", void 0);
|
|
487
|
-
__decorate([
|
|
488
|
-
state(),
|
|
489
|
-
__metadata("design:type", Array)
|
|
490
|
-
], AnnouncementEditor.prototype, "added", void 0);
|
|
491
|
-
__decorate([
|
|
492
|
-
state(),
|
|
493
|
-
__metadata("design:type", Array)
|
|
494
|
-
], AnnouncementEditor.prototype, "removed", void 0);
|
|
495
|
-
__decorate([
|
|
496
|
-
state(),
|
|
497
|
-
__metadata("design:type", Object)
|
|
498
|
-
], AnnouncementEditor.prototype, "notifyAgain", void 0);
|
|
496
|
+
], AnnouncementEditor.prototype, "address", void 0);
|
|
499
497
|
__decorate([
|
|
500
498
|
state(),
|
|
501
499
|
__metadata("design:type", Object)
|
|
@@ -504,18 +502,6 @@ __decorate([
|
|
|
504
502
|
state(),
|
|
505
503
|
__metadata("design:type", Number)
|
|
506
504
|
], AnnouncementEditor.prototype, "progress", void 0);
|
|
507
|
-
__decorate([
|
|
508
|
-
state(),
|
|
509
|
-
__metadata("design:type", Object)
|
|
510
|
-
], AnnouncementEditor.prototype, "loading", void 0);
|
|
511
|
-
__decorate([
|
|
512
|
-
state(),
|
|
513
|
-
__metadata("design:type", Object)
|
|
514
|
-
], AnnouncementEditor.prototype, "error", void 0);
|
|
515
|
-
__decorate([
|
|
516
|
-
state(),
|
|
517
|
-
__metadata("design:type", Object)
|
|
518
|
-
], AnnouncementEditor.prototype, "busy", void 0);
|
|
519
505
|
AnnouncementEditor = __decorate([
|
|
520
506
|
customElement('announcement-editor')
|
|
521
507
|
], AnnouncementEditor);
|