@things-factory/inquiry 10.1.251 → 10.1.253

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.
@@ -7,92 +7,55 @@ import '@material/web/icon/icon.js'
7
7
  import '@material/web/button/filled-button.js'
8
8
  import '@material/web/button/text-button.js'
9
9
  import '@operato/input/ox-input-file.js'
10
+ import '@operato/input/ox-input-select-buttons.js'
11
+ import '@operato/popup/ox-popup-actions.js'
10
12
 
11
13
  import { css, html, LitElement, nothing } from 'lit'
12
14
  import { customElement, property, state } from 'lit/decorators.js'
13
15
 
14
16
  import { i18next, localize } from '@operato/i18n'
15
17
  import { notify, openPopup } from '@operato/layout'
16
- import { ButtonStyles, FormFieldStyles } from '@operato/styles'
18
+ import { CommonPopupStyles, FormFieldStyles, PopupBodyStyles, PopupFieldStyles } from '@operato/styles'
17
19
 
18
20
  import { inquiriesChanged, inquiryApi } from '../utils/inquiry-api.js'
19
- import { KIND_ICONS, KINDS, kindWord, reasonOf, t } from '../utils/inquiry-words.js'
21
+ import { KINDS, kindWord, reasonOf, t } from '../utils/inquiry-words.js'
20
22
 
21
23
  const MAX_TITLE = 200
22
24
 
23
25
  /** Opens the window, carrying the screen it is opened on. */
24
26
  export function openInquiryWriter(kind = 'QUESTION') {
25
27
  const pageUrl = location.href
26
- const handle: any = openPopup(html`<inquiry-writer .kind=${kind} .pageUrl=${pageUrl} @done=${() => handle.close()}></inquiry-writer>`, {
27
- backdrop: true,
28
- size: 'large',
29
- title: t('inquiry.title.write')
30
- })
28
+ const handle: any = openPopup(
29
+ html`<inquiry-writer .kind=${kind} .pageUrl=${pageUrl} @close=${() => handle.close()}></inquiry-writer>`,
30
+ {
31
+ backdrop: true,
32
+ size: 'large',
33
+ title: t('inquiry.title.write')
34
+ }
35
+ )
31
36
  return handle
32
37
  }
33
38
 
34
39
  @customElement('inquiry-writer')
35
40
  export class InquiryWriter extends localize(i18next)(LitElement) {
41
+ /* The house shape of a popup (page-standard sections 7, 8): only the body scrolls, fields are labels above native
42
+ inputs, the one button row is `ox-popup-actions`. */
36
43
  static styles = [
44
+ CommonPopupStyles,
45
+ PopupBodyStyles,
37
46
  FormFieldStyles,
38
- ButtonStyles,
47
+ PopupFieldStyles,
39
48
  css`
40
- :host {
41
- display: flex;
42
- flex-direction: column;
43
- gap: var(--spacing-medium, 8px);
44
- padding: var(--spacing-large, 12px);
45
- color: var(--md-sys-color-on-surface);
46
- overflow: auto;
47
- }
48
- label {
49
- display: flex;
50
- flex-direction: column;
51
- gap: 2px;
52
- }
53
- label.check {
54
- flex-direction: row;
55
- align-items: center;
56
- gap: var(--spacing-small, 4px);
57
- }
58
- .kinds {
59
- display: inline-flex;
60
- gap: 4px;
61
- padding: 3px;
62
- border-radius: 10px;
63
- background: var(--ui-panel-2, var(--md-sys-color-surface-container));
49
+ ox-input-select-buttons {
64
50
  align-self: flex-start;
65
51
  }
66
- .kinds button {
67
- display: inline-flex;
68
- align-items: center;
69
- gap: 4px;
70
- padding: 4px 14px;
71
- border: 0;
72
- border-radius: 8px;
73
- background: transparent;
74
- box-shadow: none;
75
- color: var(--ui-muted, var(--md-sys-color-on-surface-variant));
76
- font: inherit;
77
- font-weight: 600;
78
- cursor: pointer;
79
- }
80
- .kinds button[aria-pressed='true'] {
81
- background: var(--ui-panel, var(--md-sys-color-surface-container-lowest));
82
- color: var(--ui-ink, var(--md-sys-color-on-surface));
83
- }
84
- .kinds md-icon {
85
- --md-icon-size: 16px;
86
- }
87
52
  textarea {
88
53
  min-height: 160px;
89
- font-family: inherit;
90
- box-sizing: border-box;
91
- width: 100%;
54
+ resize: vertical;
92
55
  }
93
- .hint {
94
- font-size: var(--ui-t-small, 12px);
95
- color: var(--ui-muted, var(--md-sys-color-on-surface-variant));
56
+ .note {
57
+ color: var(--ui-muted);
58
+ font-size: var(--ui-t-small, 11.5px);
96
59
  word-break: break-all;
97
60
  }
98
61
  .files {
@@ -100,15 +63,15 @@ export class InquiryWriter extends localize(i18next)(LitElement) {
100
63
  flex-wrap: wrap;
101
64
  gap: var(--spacing-small, 4px);
102
65
  align-items: center;
66
+ color: var(--ui-ink);
103
67
  }
104
68
  .file {
105
69
  display: inline-flex;
106
70
  align-items: center;
107
71
  gap: 2px;
108
- padding: 0 var(--spacing-small, 4px);
109
- border: 1px solid var(--ui-line-soft, var(--md-sys-color-outline-variant));
110
- border-radius: var(--border-radius, 4px);
111
- font-size: var(--ui-t-small, 12px);
72
+ }
73
+ .file md-icon {
74
+ --md-icon-size: 14px;
112
75
  }
113
76
  .file button {
114
77
  border: 0;
@@ -116,15 +79,18 @@ export class InquiryWriter extends localize(i18next)(LitElement) {
116
79
  box-shadow: none;
117
80
  padding: 0;
118
81
  cursor: pointer;
119
- color: inherit;
82
+ color: var(--ui-muted);
120
83
  }
121
- .file md-icon {
122
- --md-icon-size: 14px;
123
- }
124
- .buttons {
84
+ .check {
125
85
  display: flex;
126
- justify-content: flex-end;
127
- gap: var(--spacing-medium, 8px);
86
+ align-items: center;
87
+ gap: var(--spacing-small, 4px);
88
+ color: var(--ui-ink);
89
+ }
90
+ /* The field rule stretches its inputs to the row; a checkbox keeps its own size. */
91
+ .field .check input {
92
+ width: auto;
93
+ margin: 0;
128
94
  }
129
95
  `
130
96
  ]
@@ -142,39 +108,71 @@ export class InquiryWriter extends localize(i18next)(LitElement) {
142
108
  render() {
143
109
  const title = this.title.trim()
144
110
  return html`
145
- <div class="kinds" role="group" aria-label=${t('inquiry.field.kind')}>
146
- ${KINDS.map(
147
- code => html`<button aria-pressed=${String(this.kind === code)} @click=${() => (this.kind = code)}><md-icon>${KIND_ICONS[code]}</md-icon>${kindWord(code)}</button>`
148
- )}
149
- </div>
150
- <span class="hint">${t(`inquiry.text.kind-hint-${this.kind.toLowerCase()}`)}</span>
151
- <label
152
- >${t('inquiry.field.title')}
153
- <input .value=${this.title} maxlength=${MAX_TITLE} @input=${(e: any) => (this.title = e.target.value)} />
154
- <span class="hint">${t('inquiry.text.title-limit', { count: this.title.length, max: MAX_TITLE })}</span>
155
- </label>
156
- <label
157
- >${t('inquiry.field.body')}
158
- <textarea .value=${this.body} placeholder=${t(`inquiry.text.body-placeholder-${this.kind.toLowerCase()}`)} @input=${(e: any) => (this.body = e.target.value)}></textarea>
159
- </label>
160
- <div class="files">
161
- ${this.files.map(
162
- (file, index) =>
163
- html`<span class="file"
164
- ><md-icon>attach_file</md-icon>${file.name}<button aria-label=${t('inquiry.button.remove')} @click=${() => (this.files = this.files.filter((_, i) => i !== index))}>
165
- <md-icon>close</md-icon>
166
- </button></span
167
- >`
168
- )}
169
- <ox-input-file quiet hide-filelist multiple icon="attach_file" .progress=${this.progress} @change=${(e: CustomEvent) => this.pick(e)}></ox-input-file>
170
- </div>
171
- <label class="check"><input type="checkbox" .checked=${this.privately} @change=${(e: any) => (this.privately = e.target.checked)} />${t('inquiry.text.private-label')}</label>
172
- <span class="hint">${this.privately ? t('inquiry.text.private-hint') : t('inquiry.text.domain-hint')}</span>
173
- ${this.pageUrl ? html`<span class="hint">${t('inquiry.text.page-hint', { page: this.pageUrl })}</span>` : nothing}
174
- <div class="buttons">
175
- <md-text-button @click=${() => this.dispatchEvent(new CustomEvent('done'))}>${t('inquiry.button.cancel')}</md-text-button>
176
- <md-filled-button ?disabled=${!title || this.sending} @click=${() => this.send()}>${t('inquiry.button.submit')}</md-filled-button>
111
+ <div class="body">
112
+ <div class="field">
113
+ <ox-input-select-buttons
114
+ .value=${this.kind}
115
+ .options=${KINDS.map(code => ({ display: kindWord(code), value: code }))}
116
+ @change=${(e: CustomEvent) => e.detail && (this.kind = e.detail)}
117
+ ></ox-input-select-buttons>
118
+ <span>${t(`inquiry.text.kind-hint-${this.kind.toLowerCase()}`)}</span>
119
+ </div>
120
+ <label class="field">
121
+ <span
122
+ >${t('inquiry.field.title')} ·
123
+ ${t('inquiry.text.title-limit', { count: this.title.length, max: MAX_TITLE })}</span
124
+ >
125
+ <input .value=${this.title} maxlength=${MAX_TITLE} @input=${(e: any) => (this.title = e.target.value)} />
126
+ </label>
127
+ <label class="field">
128
+ <span>${t('inquiry.field.body')}</span>
129
+ <textarea
130
+ .value=${this.body}
131
+ placeholder=${t(`inquiry.text.body-placeholder-${this.kind.toLowerCase()}`)}
132
+ @input=${(e: any) => (this.body = e.target.value)}
133
+ ></textarea>
134
+ </label>
135
+ <div class="files">
136
+ ${this.files.map(
137
+ (file, index) =>
138
+ html`<span class="file"
139
+ ><md-icon>attach_file</md-icon>${file.name}<button
140
+ aria-label=${t('inquiry.button.remove')}
141
+ @click=${() => (this.files = this.files.filter((_, i) => i !== index))}
142
+ >
143
+ <md-icon>close</md-icon>
144
+ </button></span
145
+ >`
146
+ )}
147
+ <ox-input-file
148
+ quiet
149
+ hide-filelist
150
+ multiple
151
+ icon="attach_file"
152
+ .progress=${this.progress}
153
+ @change=${(e: CustomEvent) => this.pick(e)}
154
+ ></ox-input-file>
155
+ </div>
156
+ <div class="field">
157
+ <label class="check"
158
+ ><input
159
+ type="checkbox"
160
+ .checked=${this.privately}
161
+ @change=${(e: any) => (this.privately = e.target.checked)}
162
+ />${t('inquiry.text.private-label')}</label
163
+ >
164
+ <span>${this.privately ? t('inquiry.text.private-hint') : t('inquiry.text.domain-hint')}</span>
165
+ </div>
166
+ ${this.pageUrl ? html`<div class="note">${t('inquiry.text.page-hint', { page: this.pageUrl })}</div>` : nothing}
177
167
  </div>
168
+ <ox-popup-actions>
169
+ <md-text-button slot="cancel" @click=${() => this.dispatchEvent(new CustomEvent('close'))}
170
+ >${t('inquiry.button.cancel')}</md-text-button
171
+ >
172
+ <md-filled-button slot="confirm" ?disabled=${!title || this.sending} @click=${() => this.send()}
173
+ >${t('inquiry.button.submit')}</md-filled-button
174
+ >
175
+ </ox-popup-actions>
178
176
  `
179
177
  }
180
178
 
@@ -190,14 +188,20 @@ export class InquiryWriter extends localize(i18next)(LitElement) {
190
188
  this.sending = true
191
189
  try {
192
190
  await inquiryApi.create(
193
- { kind: this.kind, title: this.title.trim(), body: this.body, visibility: this.privately ? 'PRIVATE' : 'DOMAIN', pageUrl: this.pageUrl },
191
+ {
192
+ kind: this.kind,
193
+ title: this.title.trim(),
194
+ body: this.body,
195
+ visibility: this.privately ? 'PRIVATE' : 'DOMAIN',
196
+ pageUrl: this.pageUrl
197
+ },
194
198
  this.files,
195
199
  fraction => (this.progress = fraction)
196
200
  )
197
201
  inquiriesChanged()
198
202
  notify({ message: t('inquiry.text.sent') })
199
203
  /* The writer stays on the screen they were on - the answer comes as a notice and on the menu's badge. */
200
- this.dispatchEvent(new CustomEvent('done'))
204
+ this.dispatchEvent(new CustomEvent('close'))
201
205
  } catch (error) {
202
206
  notify({ level: 'error', message: reasonOf(error) })
203
207
  } finally {
@@ -2,6 +2,8 @@ import '@material/web/icon/icon.js';
2
2
  import '@material/web/button/filled-button.js';
3
3
  import '@material/web/button/text-button.js';
4
4
  import '@operato/input/ox-input-file.js';
5
+ import '@operato/input/ox-input-select-buttons.js';
6
+ import '@operato/popup/ox-popup-actions.js';
5
7
  import { LitElement } from 'lit';
6
8
  /** Opens the window, carrying the screen it is opened on. */
7
9
  export declare function openInquiryWriter(kind?: string): any;
@@ -8,18 +8,20 @@ import '@material/web/icon/icon.js';
8
8
  import '@material/web/button/filled-button.js';
9
9
  import '@material/web/button/text-button.js';
10
10
  import '@operato/input/ox-input-file.js';
11
+ import '@operato/input/ox-input-select-buttons.js';
12
+ import '@operato/popup/ox-popup-actions.js';
11
13
  import { css, html, LitElement, nothing } from 'lit';
12
14
  import { customElement, property, state } from 'lit/decorators.js';
13
15
  import { i18next, localize } from '@operato/i18n';
14
16
  import { notify, openPopup } from '@operato/layout';
15
- import { ButtonStyles, FormFieldStyles } from '@operato/styles';
17
+ import { CommonPopupStyles, FormFieldStyles, PopupBodyStyles, PopupFieldStyles } from '@operato/styles';
16
18
  import { inquiriesChanged, inquiryApi } from '../utils/inquiry-api.js';
17
- import { KIND_ICONS, KINDS, kindWord, reasonOf, t } from '../utils/inquiry-words.js';
19
+ import { KINDS, kindWord, reasonOf, t } from '../utils/inquiry-words.js';
18
20
  const MAX_TITLE = 200;
19
21
  /** Opens the window, carrying the screen it is opened on. */
20
22
  export function openInquiryWriter(kind = 'QUESTION') {
21
23
  const pageUrl = location.href;
22
- const handle = openPopup(html `<inquiry-writer .kind=${kind} .pageUrl=${pageUrl} @done=${() => handle.close()}></inquiry-writer>`, {
24
+ const handle = openPopup(html `<inquiry-writer .kind=${kind} .pageUrl=${pageUrl} @close=${() => handle.close()}></inquiry-writer>`, {
23
25
  backdrop: true,
24
26
  size: 'large',
25
27
  title: t('inquiry.title.write')
@@ -37,66 +39,24 @@ let InquiryWriter = class InquiryWriter extends localize(i18next)(LitElement) {
37
39
  this.files = [];
38
40
  this.sending = false;
39
41
  }
42
+ /* The house shape of a popup (page-standard sections 7, 8): only the body scrolls, fields are labels above native
43
+ inputs, the one button row is `ox-popup-actions`. */
40
44
  static { this.styles = [
45
+ CommonPopupStyles,
46
+ PopupBodyStyles,
41
47
  FormFieldStyles,
42
- ButtonStyles,
48
+ PopupFieldStyles,
43
49
  css `
44
- :host {
45
- display: flex;
46
- flex-direction: column;
47
- gap: var(--spacing-medium, 8px);
48
- padding: var(--spacing-large, 12px);
49
- color: var(--md-sys-color-on-surface);
50
- overflow: auto;
51
- }
52
- label {
53
- display: flex;
54
- flex-direction: column;
55
- gap: 2px;
56
- }
57
- label.check {
58
- flex-direction: row;
59
- align-items: center;
60
- gap: var(--spacing-small, 4px);
61
- }
62
- .kinds {
63
- display: inline-flex;
64
- gap: 4px;
65
- padding: 3px;
66
- border-radius: 10px;
67
- background: var(--ui-panel-2, var(--md-sys-color-surface-container));
50
+ ox-input-select-buttons {
68
51
  align-self: flex-start;
69
52
  }
70
- .kinds button {
71
- display: inline-flex;
72
- align-items: center;
73
- gap: 4px;
74
- padding: 4px 14px;
75
- border: 0;
76
- border-radius: 8px;
77
- background: transparent;
78
- box-shadow: none;
79
- color: var(--ui-muted, var(--md-sys-color-on-surface-variant));
80
- font: inherit;
81
- font-weight: 600;
82
- cursor: pointer;
83
- }
84
- .kinds button[aria-pressed='true'] {
85
- background: var(--ui-panel, var(--md-sys-color-surface-container-lowest));
86
- color: var(--ui-ink, var(--md-sys-color-on-surface));
87
- }
88
- .kinds md-icon {
89
- --md-icon-size: 16px;
90
- }
91
53
  textarea {
92
54
  min-height: 160px;
93
- font-family: inherit;
94
- box-sizing: border-box;
95
- width: 100%;
55
+ resize: vertical;
96
56
  }
97
- .hint {
98
- font-size: var(--ui-t-small, 12px);
99
- color: var(--ui-muted, var(--md-sys-color-on-surface-variant));
57
+ .note {
58
+ color: var(--ui-muted);
59
+ font-size: var(--ui-t-small, 11.5px);
100
60
  word-break: break-all;
101
61
  }
102
62
  .files {
@@ -104,15 +64,15 @@ let InquiryWriter = class InquiryWriter extends localize(i18next)(LitElement) {
104
64
  flex-wrap: wrap;
105
65
  gap: var(--spacing-small, 4px);
106
66
  align-items: center;
67
+ color: var(--ui-ink);
107
68
  }
108
69
  .file {
109
70
  display: inline-flex;
110
71
  align-items: center;
111
72
  gap: 2px;
112
- padding: 0 var(--spacing-small, 4px);
113
- border: 1px solid var(--ui-line-soft, var(--md-sys-color-outline-variant));
114
- border-radius: var(--border-radius, 4px);
115
- font-size: var(--ui-t-small, 12px);
73
+ }
74
+ .file md-icon {
75
+ --md-icon-size: 14px;
116
76
  }
117
77
  .file button {
118
78
  border: 0;
@@ -120,49 +80,86 @@ let InquiryWriter = class InquiryWriter extends localize(i18next)(LitElement) {
120
80
  box-shadow: none;
121
81
  padding: 0;
122
82
  cursor: pointer;
123
- color: inherit;
124
- }
125
- .file md-icon {
126
- --md-icon-size: 14px;
83
+ color: var(--ui-muted);
127
84
  }
128
- .buttons {
85
+ .check {
129
86
  display: flex;
130
- justify-content: flex-end;
131
- gap: var(--spacing-medium, 8px);
87
+ align-items: center;
88
+ gap: var(--spacing-small, 4px);
89
+ color: var(--ui-ink);
90
+ }
91
+ /* The field rule stretches its inputs to the row; a checkbox keeps its own size. */
92
+ .field .check input {
93
+ width: auto;
94
+ margin: 0;
132
95
  }
133
96
  `
134
97
  ]; }
135
98
  render() {
136
99
  const title = this.title.trim();
137
100
  return html `
138
- <div class="kinds" role="group" aria-label=${t('inquiry.field.kind')}>
139
- ${KINDS.map(code => html `<button aria-pressed=${String(this.kind === code)} @click=${() => (this.kind = code)}><md-icon>${KIND_ICONS[code]}</md-icon>${kindWord(code)}</button>`)}
140
- </div>
141
- <span class="hint">${t(`inquiry.text.kind-hint-${this.kind.toLowerCase()}`)}</span>
142
- <label
143
- >${t('inquiry.field.title')}
144
- <input .value=${this.title} maxlength=${MAX_TITLE} @input=${(e) => (this.title = e.target.value)} />
145
- <span class="hint">${t('inquiry.text.title-limit', { count: this.title.length, max: MAX_TITLE })}</span>
146
- </label>
147
- <label
148
- >${t('inquiry.field.body')}
149
- <textarea .value=${this.body} placeholder=${t(`inquiry.text.body-placeholder-${this.kind.toLowerCase()}`)} @input=${(e) => (this.body = e.target.value)}></textarea>
150
- </label>
151
- <div class="files">
152
- ${this.files.map((file, index) => html `<span class="file"
153
- ><md-icon>attach_file</md-icon>${file.name}<button aria-label=${t('inquiry.button.remove')} @click=${() => (this.files = this.files.filter((_, i) => i !== index))}>
154
- <md-icon>close</md-icon>
155
- </button></span
156
- >`)}
157
- <ox-input-file quiet hide-filelist multiple icon="attach_file" .progress=${this.progress} @change=${(e) => this.pick(e)}></ox-input-file>
158
- </div>
159
- <label class="check"><input type="checkbox" .checked=${this.privately} @change=${(e) => (this.privately = e.target.checked)} />${t('inquiry.text.private-label')}</label>
160
- <span class="hint">${this.privately ? t('inquiry.text.private-hint') : t('inquiry.text.domain-hint')}</span>
161
- ${this.pageUrl ? html `<span class="hint">${t('inquiry.text.page-hint', { page: this.pageUrl })}</span>` : nothing}
162
- <div class="buttons">
163
- <md-text-button @click=${() => this.dispatchEvent(new CustomEvent('done'))}>${t('inquiry.button.cancel')}</md-text-button>
164
- <md-filled-button ?disabled=${!title || this.sending} @click=${() => this.send()}>${t('inquiry.button.submit')}</md-filled-button>
101
+ <div class="body">
102
+ <div class="field">
103
+ <ox-input-select-buttons
104
+ .value=${this.kind}
105
+ .options=${KINDS.map(code => ({ display: kindWord(code), value: code }))}
106
+ @change=${(e) => e.detail && (this.kind = e.detail)}
107
+ ></ox-input-select-buttons>
108
+ <span>${t(`inquiry.text.kind-hint-${this.kind.toLowerCase()}`)}</span>
109
+ </div>
110
+ <label class="field">
111
+ <span
112
+ >${t('inquiry.field.title')} ·
113
+ ${t('inquiry.text.title-limit', { count: this.title.length, max: MAX_TITLE })}</span
114
+ >
115
+ <input .value=${this.title} maxlength=${MAX_TITLE} @input=${(e) => (this.title = e.target.value)} />
116
+ </label>
117
+ <label class="field">
118
+ <span>${t('inquiry.field.body')}</span>
119
+ <textarea
120
+ .value=${this.body}
121
+ placeholder=${t(`inquiry.text.body-placeholder-${this.kind.toLowerCase()}`)}
122
+ @input=${(e) => (this.body = e.target.value)}
123
+ ></textarea>
124
+ </label>
125
+ <div class="files">
126
+ ${this.files.map((file, index) => html `<span class="file"
127
+ ><md-icon>attach_file</md-icon>${file.name}<button
128
+ aria-label=${t('inquiry.button.remove')}
129
+ @click=${() => (this.files = this.files.filter((_, i) => i !== index))}
130
+ >
131
+ <md-icon>close</md-icon>
132
+ </button></span
133
+ >`)}
134
+ <ox-input-file
135
+ quiet
136
+ hide-filelist
137
+ multiple
138
+ icon="attach_file"
139
+ .progress=${this.progress}
140
+ @change=${(e) => this.pick(e)}
141
+ ></ox-input-file>
142
+ </div>
143
+ <div class="field">
144
+ <label class="check"
145
+ ><input
146
+ type="checkbox"
147
+ .checked=${this.privately}
148
+ @change=${(e) => (this.privately = e.target.checked)}
149
+ />${t('inquiry.text.private-label')}</label
150
+ >
151
+ <span>${this.privately ? t('inquiry.text.private-hint') : t('inquiry.text.domain-hint')}</span>
152
+ </div>
153
+ ${this.pageUrl ? html `<div class="note">${t('inquiry.text.page-hint', { page: this.pageUrl })}</div>` : nothing}
165
154
  </div>
155
+ <ox-popup-actions>
156
+ <md-text-button slot="cancel" @click=${() => this.dispatchEvent(new CustomEvent('close'))}
157
+ >${t('inquiry.button.cancel')}</md-text-button
158
+ >
159
+ <md-filled-button slot="confirm" ?disabled=${!title || this.sending} @click=${() => this.send()}
160
+ >${t('inquiry.button.submit')}</md-filled-button
161
+ >
162
+ </ox-popup-actions>
166
163
  `;
167
164
  }
168
165
  pick(e) {
@@ -176,11 +173,17 @@ let InquiryWriter = class InquiryWriter extends localize(i18next)(LitElement) {
176
173
  async send() {
177
174
  this.sending = true;
178
175
  try {
179
- await inquiryApi.create({ kind: this.kind, title: this.title.trim(), body: this.body, visibility: this.privately ? 'PRIVATE' : 'DOMAIN', pageUrl: this.pageUrl }, this.files, fraction => (this.progress = fraction));
176
+ await inquiryApi.create({
177
+ kind: this.kind,
178
+ title: this.title.trim(),
179
+ body: this.body,
180
+ visibility: this.privately ? 'PRIVATE' : 'DOMAIN',
181
+ pageUrl: this.pageUrl
182
+ }, this.files, fraction => (this.progress = fraction));
180
183
  inquiriesChanged();
181
184
  notify({ message: t('inquiry.text.sent') });
182
185
  /* The writer stays on the screen they were on - the answer comes as a notice and on the menu's badge. */
183
- this.dispatchEvent(new CustomEvent('done'));
186
+ this.dispatchEvent(new CustomEvent('close'));
184
187
  }
185
188
  catch (error) {
186
189
  notify({ level: 'error', message: reasonOf(error) });
@@ -1 +1 @@
1
- {"version":3,"file":"inquiry-writer.js","sourceRoot":"","sources":["../../client/components/inquiry-writer.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * 문의 · 제안 보내기 (ADR-0184 decisions 1, 4, 5, 9) - the window the avatar menu opens on any screen: the kind, the title,\n * the words, files, and who reads it. The screen it was opened on goes with it; the application, its version, the\n * writer and the domain are the server's to take.\n */\nimport '@material/web/icon/icon.js'\nimport '@material/web/button/filled-button.js'\nimport '@material/web/button/text-button.js'\nimport '@operato/input/ox-input-file.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, openPopup } from '@operato/layout'\nimport { ButtonStyles, FormFieldStyles } from '@operato/styles'\n\nimport { inquiriesChanged, inquiryApi } from '../utils/inquiry-api.js'\nimport { KIND_ICONS, KINDS, kindWord, reasonOf, t } from '../utils/inquiry-words.js'\n\nconst MAX_TITLE = 200\n\n/** Opens the window, carrying the screen it is opened on. */\nexport function openInquiryWriter(kind = 'QUESTION') {\n const pageUrl = location.href\n const handle: any = openPopup(html`<inquiry-writer .kind=${kind} .pageUrl=${pageUrl} @done=${() => handle.close()}></inquiry-writer>`, {\n backdrop: true,\n size: 'large',\n title: t('inquiry.title.write')\n })\n return handle\n}\n\n@customElement('inquiry-writer')\nexport class InquiryWriter extends localize(i18next)(LitElement) {\n static styles = [\n FormFieldStyles,\n ButtonStyles,\n css`\n :host {\n display: flex;\n flex-direction: column;\n gap: var(--spacing-medium, 8px);\n padding: var(--spacing-large, 12px);\n color: var(--md-sys-color-on-surface);\n overflow: auto;\n }\n label {\n display: flex;\n flex-direction: column;\n gap: 2px;\n }\n label.check {\n flex-direction: row;\n align-items: center;\n gap: var(--spacing-small, 4px);\n }\n .kinds {\n display: inline-flex;\n gap: 4px;\n padding: 3px;\n border-radius: 10px;\n background: var(--ui-panel-2, var(--md-sys-color-surface-container));\n align-self: flex-start;\n }\n .kinds button {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n padding: 4px 14px;\n border: 0;\n border-radius: 8px;\n background: transparent;\n box-shadow: none;\n color: var(--ui-muted, var(--md-sys-color-on-surface-variant));\n font: inherit;\n font-weight: 600;\n cursor: pointer;\n }\n .kinds button[aria-pressed='true'] {\n background: var(--ui-panel, var(--md-sys-color-surface-container-lowest));\n color: var(--ui-ink, var(--md-sys-color-on-surface));\n }\n .kinds md-icon {\n --md-icon-size: 16px;\n }\n textarea {\n min-height: 160px;\n font-family: inherit;\n box-sizing: border-box;\n width: 100%;\n }\n .hint {\n font-size: var(--ui-t-small, 12px);\n color: var(--ui-muted, var(--md-sys-color-on-surface-variant));\n word-break: break-all;\n }\n .files {\n display: flex;\n flex-wrap: wrap;\n gap: var(--spacing-small, 4px);\n align-items: center;\n }\n .file {\n display: inline-flex;\n align-items: center;\n gap: 2px;\n padding: 0 var(--spacing-small, 4px);\n border: 1px solid var(--ui-line-soft, var(--md-sys-color-outline-variant));\n border-radius: var(--border-radius, 4px);\n font-size: var(--ui-t-small, 12px);\n }\n .file button {\n border: 0;\n background: transparent;\n box-shadow: none;\n padding: 0;\n cursor: pointer;\n color: inherit;\n }\n .file md-icon {\n --md-icon-size: 14px;\n }\n .buttons {\n display: flex;\n justify-content: flex-end;\n gap: var(--spacing-medium, 8px);\n }\n `\n ]\n\n @property() kind = 'QUESTION'\n @property() pageUrl = ''\n @state() title = ''\n @state() body = ''\n @state() privately = false\n @state() files: File[] = []\n @state() sending = false\n /* Set only while files go up - the picker shows its bar then. */\n @state() progress?: number\n\n render() {\n const title = this.title.trim()\n return html`\n <div class=\"kinds\" role=\"group\" aria-label=${t('inquiry.field.kind')}>\n ${KINDS.map(\n code => html`<button aria-pressed=${String(this.kind === code)} @click=${() => (this.kind = code)}><md-icon>${KIND_ICONS[code]}</md-icon>${kindWord(code)}</button>`\n )}\n </div>\n <span class=\"hint\">${t(`inquiry.text.kind-hint-${this.kind.toLowerCase()}`)}</span>\n <label\n >${t('inquiry.field.title')}\n <input .value=${this.title} maxlength=${MAX_TITLE} @input=${(e: any) => (this.title = e.target.value)} />\n <span class=\"hint\">${t('inquiry.text.title-limit', { count: this.title.length, max: MAX_TITLE })}</span>\n </label>\n <label\n >${t('inquiry.field.body')}\n <textarea .value=${this.body} placeholder=${t(`inquiry.text.body-placeholder-${this.kind.toLowerCase()}`)} @input=${(e: any) => (this.body = e.target.value)}></textarea>\n </label>\n <div class=\"files\">\n ${this.files.map(\n (file, index) =>\n html`<span class=\"file\"\n ><md-icon>attach_file</md-icon>${file.name}<button aria-label=${t('inquiry.button.remove')} @click=${() => (this.files = this.files.filter((_, i) => i !== index))}>\n <md-icon>close</md-icon>\n </button></span\n >`\n )}\n <ox-input-file quiet hide-filelist multiple icon=\"attach_file\" .progress=${this.progress} @change=${(e: CustomEvent) => this.pick(e)}></ox-input-file>\n </div>\n <label class=\"check\"><input type=\"checkbox\" .checked=${this.privately} @change=${(e: any) => (this.privately = e.target.checked)} />${t('inquiry.text.private-label')}</label>\n <span class=\"hint\">${this.privately ? t('inquiry.text.private-hint') : t('inquiry.text.domain-hint')}</span>\n ${this.pageUrl ? html`<span class=\"hint\">${t('inquiry.text.page-hint', { page: this.pageUrl })}</span>` : nothing}\n <div class=\"buttons\">\n <md-text-button @click=${() => this.dispatchEvent(new CustomEvent('done'))}>${t('inquiry.button.cancel')}</md-text-button>\n <md-filled-button ?disabled=${!title || this.sending} @click=${() => this.send()}>${t('inquiry.button.submit')}</md-filled-button>\n </div>\n `\n }\n\n private pick(e: CustomEvent) {\n const picked = [...((e.detail as File[]) ?? [])]\n /* The picker's own reset says `change` again, with nothing - that one is let go. */\n if (!picked.length) return\n ;(e.target as any).reset?.()\n this.files = [...this.files, ...picked]\n }\n\n private async send() {\n this.sending = true\n try {\n await inquiryApi.create(\n { kind: this.kind, title: this.title.trim(), body: this.body, visibility: this.privately ? 'PRIVATE' : 'DOMAIN', pageUrl: this.pageUrl },\n this.files,\n fraction => (this.progress = fraction)\n )\n inquiriesChanged()\n notify({ message: t('inquiry.text.sent') })\n /* The writer stays on the screen they were on - the answer comes as a notice and on the menu's badge. */\n this.dispatchEvent(new CustomEvent('done'))\n } catch (error) {\n notify({ level: 'error', message: reasonOf(error) })\n } finally {\n this.sending = false\n this.progress = undefined\n }\n }\n}\n"]}
1
+ {"version":3,"file":"inquiry-writer.js","sourceRoot":"","sources":["../../client/components/inquiry-writer.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * 문의 · 제안 보내기 (ADR-0184 decisions 1, 4, 5, 9) - the window the avatar menu opens on any screen: the kind, the title,\n * the words, files, and who reads it. The screen it was opened on goes with it; the application, its version, the\n * writer and the domain are the server's to take.\n */\nimport '@material/web/icon/icon.js'\nimport '@material/web/button/filled-button.js'\nimport '@material/web/button/text-button.js'\nimport '@operato/input/ox-input-file.js'\nimport '@operato/input/ox-input-select-buttons.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, openPopup } from '@operato/layout'\nimport { CommonPopupStyles, FormFieldStyles, PopupBodyStyles, PopupFieldStyles } from '@operato/styles'\n\nimport { inquiriesChanged, inquiryApi } from '../utils/inquiry-api.js'\nimport { KINDS, kindWord, reasonOf, t } from '../utils/inquiry-words.js'\n\nconst MAX_TITLE = 200\n\n/** Opens the window, carrying the screen it is opened on. */\nexport function openInquiryWriter(kind = 'QUESTION') {\n const pageUrl = location.href\n const handle: any = openPopup(\n html`<inquiry-writer .kind=${kind} .pageUrl=${pageUrl} @close=${() => handle.close()}></inquiry-writer>`,\n {\n backdrop: true,\n size: 'large',\n title: t('inquiry.title.write')\n }\n )\n return handle\n}\n\n@customElement('inquiry-writer')\nexport class InquiryWriter extends localize(i18next)(LitElement) {\n /* The house shape of a popup (page-standard sections 7, 8): only the body scrolls, fields are labels above native\n inputs, the one button row is `ox-popup-actions`. */\n static styles = [\n CommonPopupStyles,\n PopupBodyStyles,\n FormFieldStyles,\n PopupFieldStyles,\n css`\n ox-input-select-buttons {\n align-self: flex-start;\n }\n textarea {\n min-height: 160px;\n resize: vertical;\n }\n .note {\n color: var(--ui-muted);\n font-size: var(--ui-t-small, 11.5px);\n word-break: break-all;\n }\n .files {\n display: flex;\n flex-wrap: wrap;\n gap: var(--spacing-small, 4px);\n align-items: center;\n color: var(--ui-ink);\n }\n .file {\n display: inline-flex;\n align-items: center;\n gap: 2px;\n }\n .file md-icon {\n --md-icon-size: 14px;\n }\n .file button {\n border: 0;\n background: transparent;\n box-shadow: none;\n padding: 0;\n cursor: pointer;\n color: var(--ui-muted);\n }\n .check {\n display: flex;\n align-items: center;\n gap: var(--spacing-small, 4px);\n color: var(--ui-ink);\n }\n /* The field rule stretches its inputs to the row; a checkbox keeps its own size. */\n .field .check input {\n width: auto;\n margin: 0;\n }\n `\n ]\n\n @property() kind = 'QUESTION'\n @property() pageUrl = ''\n @state() title = ''\n @state() body = ''\n @state() privately = false\n @state() files: File[] = []\n @state() sending = false\n /* Set only while files go up - the picker shows its bar then. */\n @state() progress?: number\n\n render() {\n const title = this.title.trim()\n return html`\n <div class=\"body\">\n <div class=\"field\">\n <ox-input-select-buttons\n .value=${this.kind}\n .options=${KINDS.map(code => ({ display: kindWord(code), value: code }))}\n @change=${(e: CustomEvent) => e.detail && (this.kind = e.detail)}\n ></ox-input-select-buttons>\n <span>${t(`inquiry.text.kind-hint-${this.kind.toLowerCase()}`)}</span>\n </div>\n <label class=\"field\">\n <span\n >${t('inquiry.field.title')} ·\n ${t('inquiry.text.title-limit', { count: this.title.length, max: MAX_TITLE })}</span\n >\n <input .value=${this.title} maxlength=${MAX_TITLE} @input=${(e: any) => (this.title = e.target.value)} />\n </label>\n <label class=\"field\">\n <span>${t('inquiry.field.body')}</span>\n <textarea\n .value=${this.body}\n placeholder=${t(`inquiry.text.body-placeholder-${this.kind.toLowerCase()}`)}\n @input=${(e: any) => (this.body = e.target.value)}\n ></textarea>\n </label>\n <div class=\"files\">\n ${this.files.map(\n (file, index) =>\n html`<span class=\"file\"\n ><md-icon>attach_file</md-icon>${file.name}<button\n aria-label=${t('inquiry.button.remove')}\n @click=${() => (this.files = this.files.filter((_, i) => i !== index))}\n >\n <md-icon>close</md-icon>\n </button></span\n >`\n )}\n <ox-input-file\n quiet\n hide-filelist\n multiple\n icon=\"attach_file\"\n .progress=${this.progress}\n @change=${(e: CustomEvent) => this.pick(e)}\n ></ox-input-file>\n </div>\n <div class=\"field\">\n <label class=\"check\"\n ><input\n type=\"checkbox\"\n .checked=${this.privately}\n @change=${(e: any) => (this.privately = e.target.checked)}\n />${t('inquiry.text.private-label')}</label\n >\n <span>${this.privately ? t('inquiry.text.private-hint') : t('inquiry.text.domain-hint')}</span>\n </div>\n ${this.pageUrl ? html`<div class=\"note\">${t('inquiry.text.page-hint', { page: this.pageUrl })}</div>` : nothing}\n </div>\n <ox-popup-actions>\n <md-text-button slot=\"cancel\" @click=${() => this.dispatchEvent(new CustomEvent('close'))}\n >${t('inquiry.button.cancel')}</md-text-button\n >\n <md-filled-button slot=\"confirm\" ?disabled=${!title || this.sending} @click=${() => this.send()}\n >${t('inquiry.button.submit')}</md-filled-button\n >\n </ox-popup-actions>\n `\n }\n\n private pick(e: CustomEvent) {\n const picked = [...((e.detail as File[]) ?? [])]\n /* The picker's own reset says `change` again, with nothing - that one is let go. */\n if (!picked.length) return\n ;(e.target as any).reset?.()\n this.files = [...this.files, ...picked]\n }\n\n private async send() {\n this.sending = true\n try {\n await inquiryApi.create(\n {\n kind: this.kind,\n title: this.title.trim(),\n body: this.body,\n visibility: this.privately ? 'PRIVATE' : 'DOMAIN',\n pageUrl: this.pageUrl\n },\n this.files,\n fraction => (this.progress = fraction)\n )\n inquiriesChanged()\n notify({ message: t('inquiry.text.sent') })\n /* The writer stays on the screen they were on - the answer comes as a notice and on the menu's badge. */\n this.dispatchEvent(new CustomEvent('close'))\n } catch (error) {\n notify({ level: 'error', message: reasonOf(error) })\n } finally {\n this.sending = false\n this.progress = undefined\n }\n }\n}\n"]}