@skillcdn/console-plugin-messenger 0.0.0-stage → 0.1.0

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.
@@ -0,0 +1,1660 @@
1
+ // ../../node_modules/.pnpm/@skillcdn+console@0.4.6_@types+react@19.3.0_react-dom@19.3.1_react@19.3.1__react@19.3.1/node_modules/@skillcdn/console/dist/vocabulary.js
2
+ var WEEK_STARTS = ["auto", "monday", "sunday"];
3
+
4
+ // ../../node_modules/.pnpm/@skillcdn+console@0.4.6_@types+react@19.3.0_react-dom@19.3.1_react@19.3.1__react@19.3.1/node_modules/@skillcdn/console/dist/plugin/bridge.js
5
+ var PLUGIN_BRIDGE_VERSION = 1;
6
+ var PLUGIN_WIDGET_SIZE_NAMES = ["small", "medium", "large", "wide", "full"];
7
+ var BRIDGE_METHODS = [
8
+ "command",
9
+ "query",
10
+ "records.list",
11
+ "records.get",
12
+ "records.versions",
13
+ "files.get",
14
+ "files.put",
15
+ "people.picture"
16
+ ];
17
+ var BRIDGE_EVENT_KINDS = ["records", "command", "plugin"];
18
+ var MAX_BRIDGE_PATH_LENGTH = 2048;
19
+ var MAX_BRIDGE_TITLE_LENGTH = 200;
20
+ var MAX_ID_LENGTH = 128;
21
+ var MAX_THEME_TOKENS = 500;
22
+ function isRecord(value) {
23
+ return typeof value === "object" && value !== null && !Array.isArray(value);
24
+ }
25
+ function isLine(value, max) {
26
+ return typeof value === "string" && value.length > 0 && value.length <= max && !/[\r\n]/.test(value);
27
+ }
28
+ function isBridgePath(value) {
29
+ return typeof value === "string" && value.length <= MAX_BRIDGE_PATH_LENGTH && value.startsWith("/") && !value.startsWith("//") && !/[\s\\]/.test(value);
30
+ }
31
+ var OPENABLE_SCHEMES = /* @__PURE__ */ new Set(["https:", "http:", "mailto:"]);
32
+ var MAX_BRIDGE_URL_LENGTH = 2048;
33
+ function isBridgeUrl(value) {
34
+ if (typeof value !== "string" || value.length === 0 || value.length > MAX_BRIDGE_URL_LENGTH || /\s/.test(value) || [...value].some((character) => {
35
+ const code = character.codePointAt(0) ?? 0;
36
+ return code < 32 || code === 127;
37
+ }) || !URL.canParse(value)) {
38
+ return false;
39
+ }
40
+ return OPENABLE_SCHEMES.has(new URL(value).protocol);
41
+ }
42
+ function isTheme(value) {
43
+ if (!isRecord(value)) {
44
+ return false;
45
+ }
46
+ const entries = Object.entries(value);
47
+ return entries.length <= MAX_THEME_TOKENS && entries.every(([token, text]) => /^--sc-[a-z0-9-]+$/.test(token) && typeof text === "string" && text.length <= 200);
48
+ }
49
+ function isWorkspace(value) {
50
+ return isRecord(value) && typeof value.name === "string" && Array.isArray(value.languages) && value.languages.every((language) => typeof language === "string") && isLine(value.timeZone, 100) && WEEK_STARTS.includes(value.weekStart) && typeof value.firstDay === "number" && Number.isInteger(value.firstDay) && value.firstDay >= 1 && value.firstDay <= 7;
51
+ }
52
+ function isEnvelope(value) {
53
+ return isRecord(value) && value.v === PLUGIN_BRIDGE_VERSION && typeof value.t === "string";
54
+ }
55
+ function isBridgeToFrame(value) {
56
+ if (!isEnvelope(value)) {
57
+ return false;
58
+ }
59
+ switch (value.t) {
60
+ case "init":
61
+ return isRecord(value.plugin) && isLine(value.plugin.key, 32) && isLine(value.plugin.version, 64) && (value.page === void 0 || isLine(value.page, 32)) && (value.widget === void 0 || isLine(value.widget, 32)) && (value.size === void 0 || PLUGIN_WIDGET_SIZE_NAMES.includes(value.size)) && (value.project === null || isRecord(value.project) && isLine(value.project.key, 64) && typeof value.project.name === "string") && isRecord(value.person) && typeof value.person.id === "string" && typeof value.person.login === "string" && (value.person.name === null || typeof value.person.name === "string") && typeof value.person.language === "string" && typeof value.person.admin === "boolean" && (value.workspace === void 0 || isWorkspace(value.workspace)) && Array.isArray(value.roles) && value.roles.every((role) => typeof role === "string") && typeof value.path === "string" && value.path.length <= MAX_BRIDGE_PATH_LENGTH && typeof value.language === "string" && isTheme(value.theme);
62
+ case "result":
63
+ return isLine(value.id, MAX_ID_LENGTH) && typeof value.ok === "boolean" && (value.error === void 0 || isRecord(value.error) && typeof value.error.code === "string" && typeof value.error.message === "string");
64
+ case "event":
65
+ return typeof value.kind === "string" && BRIDGE_EVENT_KINDS.includes(value.kind);
66
+ case "theme":
67
+ return isTheme(value.theme);
68
+ case "path":
69
+ return typeof value.path === "string" && value.path.length <= MAX_BRIDGE_PATH_LENGTH;
70
+ case "size":
71
+ return PLUGIN_WIDGET_SIZE_NAMES.includes(value.size);
72
+ default:
73
+ return false;
74
+ }
75
+ }
76
+
77
+ // ../../node_modules/.pnpm/@skillcdn+console@0.4.6_@types+react@19.3.0_react-dom@19.3.1_react@19.3.1__react@19.3.1/node_modules/@skillcdn/console/dist/plugin/web.js
78
+ var PluginBridgeError = class extends Error {
79
+ code;
80
+ data;
81
+ constructor(error) {
82
+ super(error.message);
83
+ this.name = "PluginBridgeError";
84
+ this.code = error.code;
85
+ this.data = error.data;
86
+ }
87
+ };
88
+ var DEFAULT_CONNECT_MS = 1e4;
89
+ var NO_PICTURE = /* @__PURE__ */ new Set([
90
+ "plugin.not_found",
91
+ "picture.refused",
92
+ "picture.unavailable"
93
+ ]);
94
+ function globalWindow() {
95
+ const candidate = globalThis.window;
96
+ if (candidate === void 0) {
97
+ throw new PluginBridgeError({
98
+ code: "plugin.no_window",
99
+ message: "connect() runs in a plugin's frame, where window is"
100
+ });
101
+ }
102
+ return candidate;
103
+ }
104
+ function globalDocument() {
105
+ const candidate = globalThis.document;
106
+ if (candidate === void 0) {
107
+ throw new PluginBridgeError({
108
+ code: "plugin.no_window",
109
+ message: "connect() runs in a plugin's frame, where document is"
110
+ });
111
+ }
112
+ return candidate;
113
+ }
114
+ function applyTheme(document2, theme) {
115
+ for (const [token, value] of Object.entries(theme)) {
116
+ if (token.startsWith("--sc-")) {
117
+ document2.documentElement.style.setProperty(token, value);
118
+ }
119
+ }
120
+ }
121
+ function startHeightWatch(document2, post) {
122
+ const body = document2.body;
123
+ if (body === null) {
124
+ return () => {
125
+ };
126
+ }
127
+ let last = -1;
128
+ const say2 = () => {
129
+ const height = Math.ceil(body.scrollHeight);
130
+ if (height !== last) {
131
+ last = height;
132
+ post(height);
133
+ }
134
+ };
135
+ say2();
136
+ const observerClass = globalThis.ResizeObserver;
137
+ const elementClass = globalThis.Element;
138
+ if (observerClass !== void 0 && elementClass !== void 0 && body instanceof elementClass) {
139
+ const observer = new observerClass(say2);
140
+ observer.observe(body);
141
+ return () => observer.disconnect();
142
+ }
143
+ const timer = setInterval(say2, 500);
144
+ return () => clearInterval(timer);
145
+ }
146
+ function connect(options = {}) {
147
+ const win = options.window ?? globalWindow();
148
+ const doc = options.document ?? globalDocument();
149
+ const origin = doc.documentElement.dataset.console;
150
+ if (origin === void 0 || origin.length === 0) {
151
+ return Promise.reject(new PluginBridgeError({
152
+ code: "plugin.no_console",
153
+ message: "the document names no console in data-console; this is not a plugin's frame"
154
+ }));
155
+ }
156
+ const post = (message, transfer) => {
157
+ win.parent.postMessage(message, origin, transfer);
158
+ };
159
+ const pending = /* @__PURE__ */ new Map();
160
+ const eventHandlers = /* @__PURE__ */ new Set();
161
+ const themeHandlers = /* @__PURE__ */ new Set();
162
+ const pathHandlers = /* @__PURE__ */ new Set();
163
+ const sizeHandlers = /* @__PURE__ */ new Set();
164
+ const subscribed = /* @__PURE__ */ new Set();
165
+ const pictures = /* @__PURE__ */ new Map();
166
+ let counter = 0;
167
+ let stopHeight;
168
+ return new Promise((resolveConnect, rejectConnect) => {
169
+ let connected = false;
170
+ const timer = setTimeout(() => {
171
+ if (!connected) {
172
+ win.removeEventListener("message", listener);
173
+ rejectConnect(new PluginBridgeError({
174
+ code: "plugin.no_init",
175
+ message: `the console's page did not answer within ${options.timeoutMs ?? DEFAULT_CONNECT_MS} ms`
176
+ }));
177
+ }
178
+ }, options.timeoutMs ?? DEFAULT_CONNECT_MS);
179
+ const call = (method, params, transfer) => {
180
+ if (!BRIDGE_METHODS.includes(method)) {
181
+ return Promise.reject(new PluginBridgeError({
182
+ code: "plugin.forbidden",
183
+ message: `${method} is not a method of the bridge`
184
+ }));
185
+ }
186
+ counter += 1;
187
+ const id = String(counter);
188
+ return new Promise((resolve, reject) => {
189
+ pending.set(id, { resolve, reject });
190
+ post({ v: PLUGIN_BRIDGE_VERSION, t: "call", id, method, params }, transfer);
191
+ });
192
+ };
193
+ const listener = (event) => {
194
+ if (event.source !== win.parent || event.origin !== origin || !isBridgeToFrame(event.data)) {
195
+ return;
196
+ }
197
+ const message = event.data;
198
+ switch (message.t) {
199
+ case "init": {
200
+ if (connected) {
201
+ return;
202
+ }
203
+ connected = true;
204
+ clearTimeout(timer);
205
+ applyTheme(doc, message.theme);
206
+ doc.documentElement.lang = message.language;
207
+ const { v: _v, t: _t, ...context } = message;
208
+ const frame = {
209
+ context,
210
+ call: (method, params) => call(method, params),
211
+ command: (name, args = {}) => call("command", { name, args }),
212
+ query: (name, args = {}) => call("query", { name, args }),
213
+ records: {
214
+ list: (collection, query = {}) => call("records.list", { collection, ...query }),
215
+ get: (collection, id, version) => call("records.get", version === void 0 ? { collection, id } : { collection, id, version }),
216
+ versions: (collection, id) => call("records.versions", { collection, id })
217
+ },
218
+ files: {
219
+ get: (id) => call("files.get", { id }),
220
+ put: (bytes, name, type) => {
221
+ const buffer = bytes instanceof Uint8Array ? bytes.slice().buffer : bytes;
222
+ return call("files.put", { bytes: buffer, name, type }, [buffer]);
223
+ }
224
+ },
225
+ people: {
226
+ picture: (id) => {
227
+ const kept = pictures.get(id);
228
+ if (kept !== void 0) {
229
+ return kept;
230
+ }
231
+ const asked = call("people.picture", { id }).then(({ type, bytes }) => URL.createObjectURL(new Blob([bytes], { type })), (error) => {
232
+ if (error instanceof PluginBridgeError && NO_PICTURE.has(error.code)) {
233
+ return null;
234
+ }
235
+ pictures.delete(id);
236
+ throw error;
237
+ });
238
+ pictures.set(id, asked);
239
+ return asked;
240
+ }
241
+ },
242
+ navigate: (path) => {
243
+ if (!isBridgePath(path)) {
244
+ throw new PluginBridgeError({
245
+ code: "plugin.forbidden",
246
+ message: "navigate takes a path of the console's, never a URL"
247
+ });
248
+ }
249
+ post({ v: PLUGIN_BRIDGE_VERSION, t: "navigate", path });
250
+ },
251
+ open: (url) => {
252
+ if (!isBridgeUrl(url)) {
253
+ throw new PluginBridgeError({
254
+ code: "plugin.forbidden",
255
+ message: "open takes a web or a mail address; navigate takes a path of the console's"
256
+ });
257
+ }
258
+ if (win.navigator?.userActivation?.isActive === false) {
259
+ throw new PluginBridgeError({
260
+ code: "plugin.forbidden",
261
+ message: "open opens a link right after a person pressed in the frame, and not otherwise"
262
+ });
263
+ }
264
+ win.open?.(url, "_blank", "noopener,noreferrer");
265
+ },
266
+ download: (id) => post({ v: PLUGIN_BRIDGE_VERSION, t: "download", id }),
267
+ setTitle: (text) => {
268
+ const title = text.slice(0, MAX_BRIDGE_TITLE_LENGTH).replace(/[\r\n]+/g, " ");
269
+ doc.title = title;
270
+ post({ v: PLUGIN_BRIDGE_VERSION, t: "title", text: title });
271
+ },
272
+ onEvent: (kinds, handler) => {
273
+ eventHandlers.add(handler);
274
+ let added = false;
275
+ for (const kind of kinds) {
276
+ if (!subscribed.has(kind)) {
277
+ subscribed.add(kind);
278
+ added = true;
279
+ }
280
+ }
281
+ if (added) {
282
+ post({ v: PLUGIN_BRIDGE_VERSION, t: "subscribe", kinds: [...subscribed] });
283
+ }
284
+ return () => eventHandlers.delete(handler);
285
+ },
286
+ onTheme: (handler) => {
287
+ themeHandlers.add(handler);
288
+ return () => themeHandlers.delete(handler);
289
+ },
290
+ onPath: (handler) => {
291
+ pathHandlers.add(handler);
292
+ return () => pathHandlers.delete(handler);
293
+ },
294
+ onSize: (handler) => {
295
+ sizeHandlers.add(handler);
296
+ return () => sizeHandlers.delete(handler);
297
+ },
298
+ close: () => {
299
+ win.removeEventListener("message", listener);
300
+ stopHeight?.();
301
+ for (const { reject } of pending.values()) {
302
+ reject(new PluginBridgeError({
303
+ code: "plugin.closed",
304
+ message: "the frame closed its connection"
305
+ }));
306
+ }
307
+ pending.clear();
308
+ }
309
+ };
310
+ if (options.resize !== false) {
311
+ stopHeight = startHeightWatch(doc, (height) => post({ v: PLUGIN_BRIDGE_VERSION, t: "resize", height }));
312
+ }
313
+ resolveConnect(frame);
314
+ return;
315
+ }
316
+ case "result": {
317
+ const waiting = pending.get(message.id);
318
+ if (waiting === void 0) {
319
+ return;
320
+ }
321
+ pending.delete(message.id);
322
+ if (message.ok) {
323
+ waiting.resolve(message.value);
324
+ } else {
325
+ waiting.reject(new PluginBridgeError(message.error ?? { code: "plugin.failed", message: "the call failed" }));
326
+ }
327
+ return;
328
+ }
329
+ case "event":
330
+ for (const handler of eventHandlers) {
331
+ handler(message.kind, message.data);
332
+ }
333
+ return;
334
+ case "theme":
335
+ applyTheme(doc, message.theme);
336
+ for (const handler of themeHandlers) {
337
+ handler(message.theme);
338
+ }
339
+ return;
340
+ case "path":
341
+ for (const handler of pathHandlers) {
342
+ handler(message.path);
343
+ }
344
+ return;
345
+ case "size":
346
+ for (const handler of sizeHandlers) {
347
+ handler(message.size);
348
+ }
349
+ return;
350
+ default:
351
+ return;
352
+ }
353
+ };
354
+ win.addEventListener("message", listener);
355
+ post({ v: PLUGIN_BRIDGE_VERSION, t: "ready" });
356
+ });
357
+ }
358
+
359
+ // src/shared/contracts.ts
360
+ var LANGUAGES = ["en", "ko"];
361
+ function languageOf(tag) {
362
+ const primary = (tag ?? "").toLowerCase().split("-")[0] ?? "";
363
+ return LANGUAGES.includes(primary) ? primary : "en";
364
+ }
365
+ var ROUTE_KINDS = ["hr:leave-approved"];
366
+ var ROUTE_AS = ["person", "app"];
367
+ var SECRETS = [
368
+ "slackConfigToken",
369
+ "slackConfigRefreshToken",
370
+ "slackClientSecret",
371
+ "slackSigningSecret",
372
+ "slackBotToken"
373
+ ];
374
+ var NOTES = [
375
+ "thread",
376
+ "nobody",
377
+ "next_step",
378
+ "approved",
379
+ "approved_partly",
380
+ "rejected",
381
+ "returned",
382
+ "cancel_asked",
383
+ "cancelled",
384
+ "kept",
385
+ "no_thread",
386
+ "not_leave",
387
+ "not_connected",
388
+ "no_capability",
389
+ "outside",
390
+ "no_grant",
391
+ "hidden",
392
+ "slack",
393
+ "console",
394
+ "rate_limited",
395
+ "failed"
396
+ ];
397
+
398
+ // src/web/ui.ts
399
+ var PREFIX = "pl-messenger-";
400
+ var DASH = "\u2014";
401
+ function el(tag, classes = "", text) {
402
+ const node = document.createElement(tag);
403
+ if (classes.length > 0) {
404
+ node.className = classes.split(/\s+/).filter((name) => name.length > 0).map((name) => name.startsWith("sc-") ? name : `${PREFIX}${name}`).join(" ");
405
+ }
406
+ if (text !== void 0) {
407
+ node.textContent = text;
408
+ }
409
+ return node;
410
+ }
411
+ function button(label, onClick, kind = "secondary", small = false) {
412
+ const node = el("button", `sc-button sc-button-${kind}${small ? " sc-button-sm" : ""}`, label);
413
+ node.type = "button";
414
+ node.addEventListener("click", onClick);
415
+ return node;
416
+ }
417
+ function submitButton(label, kind = "primary") {
418
+ const node = el("button", `sc-button sc-button-${kind}`, label);
419
+ node.type = "submit";
420
+ return node;
421
+ }
422
+ function input(type, attributes = {}) {
423
+ const node = el("input", "sc-input control");
424
+ node.type = type;
425
+ if (attributes.value !== void 0) {
426
+ node.value = attributes.value;
427
+ }
428
+ if (attributes.placeholder !== void 0) {
429
+ node.placeholder = attributes.placeholder;
430
+ }
431
+ if (attributes.maxLength !== void 0) {
432
+ node.maxLength = attributes.maxLength;
433
+ }
434
+ return node;
435
+ }
436
+ function select(options, value) {
437
+ const node = el("select", "sc-input control");
438
+ for (const option of options) {
439
+ const item = document.createElement("option");
440
+ item.value = option.value;
441
+ item.textContent = option.label;
442
+ node.append(item);
443
+ }
444
+ if (value !== void 0) {
445
+ node.value = value;
446
+ }
447
+ return node;
448
+ }
449
+ var groupCount = 0;
450
+ function pills(options, initial, label, onChange) {
451
+ groupCount += 1;
452
+ const name = `${PREFIX}choice-${groupCount}`;
453
+ const node = el("fieldset", "sc-toggle-chips pills");
454
+ node.setAttribute("aria-label", label);
455
+ const boxes = /* @__PURE__ */ new Map();
456
+ for (const option of options) {
457
+ const chip = el("label", "sc-toggle-chip pill");
458
+ const box = document.createElement("input");
459
+ box.type = "radio";
460
+ box.name = name;
461
+ box.checked = option.value === initial;
462
+ box.addEventListener("change", () => {
463
+ if (box.checked) {
464
+ onChange?.(option.value);
465
+ }
466
+ });
467
+ boxes.set(option.value, box);
468
+ chip.append(box, el("span", "", option.label));
469
+ node.append(chip);
470
+ }
471
+ return {
472
+ node,
473
+ value: () => {
474
+ for (const [key, box] of boxes) {
475
+ if (box.checked) {
476
+ return key;
477
+ }
478
+ }
479
+ return initial;
480
+ }
481
+ };
482
+ }
483
+ function optionCards(options, initial, label) {
484
+ groupCount += 1;
485
+ const name = `${PREFIX}choice-${groupCount}`;
486
+ const node = el("fieldset", "option-cards");
487
+ node.setAttribute("aria-label", label);
488
+ const boxes = /* @__PURE__ */ new Map();
489
+ for (const option of options) {
490
+ const card = el("label", "option-card");
491
+ const box = document.createElement("input");
492
+ box.type = "radio";
493
+ box.name = name;
494
+ box.checked = option.value === initial;
495
+ boxes.set(option.value, box);
496
+ const text = el("span", "option-card-text");
497
+ text.append(el("span", "option-card-label", option.label));
498
+ if (option.hint !== void 0) {
499
+ text.append(el("span", "option-card-hint", option.hint));
500
+ }
501
+ card.append(box, text);
502
+ node.append(card);
503
+ }
504
+ return {
505
+ node,
506
+ value: () => {
507
+ for (const [key, box] of boxes) {
508
+ if (box.checked) {
509
+ return key;
510
+ }
511
+ }
512
+ return initial;
513
+ }
514
+ };
515
+ }
516
+ function field(label, control, hint) {
517
+ const wrapper = el("label", "sc-field");
518
+ wrapper.append(el("span", "sc-field-label", label), control);
519
+ if (hint !== void 0) {
520
+ wrapper.append(el("span", "sc-field-hint", hint));
521
+ }
522
+ return wrapper;
523
+ }
524
+ function group(label, ...controls) {
525
+ const wrapper = el("div", "sc-field");
526
+ wrapper.append(el("span", "sc-field-label", label), ...controls);
527
+ return wrapper;
528
+ }
529
+ function form(...children) {
530
+ const node = el("form", "sc-form");
531
+ node.append(...children);
532
+ return node;
533
+ }
534
+ function actions(...children) {
535
+ const node = el("div", "sc-form-actions");
536
+ node.append(...children);
537
+ return node;
538
+ }
539
+ function badge(text, tone) {
540
+ return el("span", `sc-badge sc-badge-${tone}`, text);
541
+ }
542
+ function pageHead(title, lead, ...buttons) {
543
+ const node = el("div", "sc-page-head head");
544
+ const heading = el("div", "sc-page-heading");
545
+ heading.append(el("h1", "sc-page-title", title));
546
+ if (lead.length > 0) {
547
+ heading.append(el("p", "sc-page-lead", lead));
548
+ }
549
+ node.append(heading);
550
+ if (buttons.length > 0) {
551
+ const side = el("div", "sc-page-actions");
552
+ side.append(...buttons);
553
+ node.append(side);
554
+ }
555
+ return node;
556
+ }
557
+ function panel(title, ...children) {
558
+ const node = el("section", "sc-panel panel");
559
+ if (title !== null) {
560
+ node.append(el("h2", "panel-title", title));
561
+ }
562
+ node.append(...children);
563
+ return node;
564
+ }
565
+ function panelWith(title, controls, ...children) {
566
+ const node = el("section", "sc-panel panel");
567
+ const head = el("div", "panel-head");
568
+ head.append(el("h2", "panel-title", title), ...controls);
569
+ node.append(head, ...children);
570
+ return node;
571
+ }
572
+ function empty(text, title) {
573
+ const node = el("div", "sc-empty");
574
+ if (title !== void 0) {
575
+ node.append(el("p", "sc-empty-title", title));
576
+ }
577
+ node.append(el("p", "sc-empty-body", text));
578
+ return node;
579
+ }
580
+ function callout(title, body, tone = "info") {
581
+ const node = el("div", `sc-callout callout${tone === "info" ? "" : ` sc-callout-${tone}`}`);
582
+ const text = el("div", "");
583
+ text.append(el("p", "sc-callout-title", title));
584
+ if (body !== void 0) {
585
+ text.append(el("p", "sc-callout-body", body));
586
+ }
587
+ node.append(text);
588
+ return node;
589
+ }
590
+ function muted(text) {
591
+ return el("p", "sc-muted line", text);
592
+ }
593
+ function factsRow(pairs) {
594
+ const node = el("dl", "sc-facts-row");
595
+ for (const [label, value] of pairs) {
596
+ const item = el("div", "");
597
+ const detail = el("dd", "");
598
+ detail.append(value);
599
+ item.append(el("dt", "", label), detail);
600
+ node.append(item);
601
+ }
602
+ return node;
603
+ }
604
+ function table(headers, rows) {
605
+ const wrapper = el("div", "scroll");
606
+ const node = el("table", "table");
607
+ const head = document.createElement("thead");
608
+ const headRow = document.createElement("tr");
609
+ for (const header of headers) {
610
+ headRow.append(el("th", "", header));
611
+ }
612
+ head.append(headRow);
613
+ const body = document.createElement("tbody");
614
+ body.append(...rows);
615
+ node.append(head, body);
616
+ wrapper.append(node);
617
+ return wrapper;
618
+ }
619
+ function row(cells) {
620
+ const node = document.createElement("tr");
621
+ for (const content of cells) {
622
+ const cellNode = document.createElement("td");
623
+ cellNode.append(content);
624
+ node.append(cellNode);
625
+ }
626
+ return node;
627
+ }
628
+ function personLabel(person) {
629
+ return person.name ?? person.login;
630
+ }
631
+ function avatar(frame, person) {
632
+ const node = el(
633
+ "span",
634
+ "sc-avatar sc-avatar-sm sc-avatar-initial avatar",
635
+ personLabel(person).trim().slice(0, 1).toUpperCase()
636
+ );
637
+ node.title = personLabel(person);
638
+ void frame.people.picture(person.id).then((address) => {
639
+ if (address !== null) {
640
+ const image = el("img", "sc-avatar sc-avatar-sm avatar");
641
+ image.src = address;
642
+ image.alt = "";
643
+ image.title = personLabel(person);
644
+ node.replaceWith(image);
645
+ }
646
+ }).catch(() => {
647
+ });
648
+ return node;
649
+ }
650
+ function who(frame, person) {
651
+ const node = el("span", "who");
652
+ const text = el("span", "who-text");
653
+ text.append(el("span", "who-name", personLabel(person)));
654
+ if (person.name !== null) {
655
+ text.append(el("span", "who-sub", person.login));
656
+ }
657
+ node.append(avatar(frame, person), text);
658
+ return node;
659
+ }
660
+ function intlLanguage(language) {
661
+ try {
662
+ return Intl.getCanonicalLocales(language)[0] ?? "en";
663
+ } catch {
664
+ return "en";
665
+ }
666
+ }
667
+ function dateTimeIn(language) {
668
+ const format = new Intl.DateTimeFormat(intlLanguage(language), {
669
+ dateStyle: "medium",
670
+ timeStyle: "short"
671
+ });
672
+ return (iso) => {
673
+ if (iso === null) {
674
+ return DASH;
675
+ }
676
+ const at = new Date(iso);
677
+ return Number.isNaN(at.getTime()) ? iso : format.format(at);
678
+ };
679
+ }
680
+ function errorText(error, words) {
681
+ return error instanceof Error && error.message.length > 0 ? error.message : words.failed;
682
+ }
683
+ function statusLine() {
684
+ return el("p", "status");
685
+ }
686
+ function say(status, text, kind) {
687
+ status.textContent = text;
688
+ status.className = kind === "error" ? `${PREFIX}status sc-form-error` : `${PREFIX}status ${PREFIX}ok`;
689
+ }
690
+ function clear(status) {
691
+ status.textContent = "";
692
+ status.className = `${PREFIX}status`;
693
+ }
694
+ async function runCommand(frame, trigger, status, words, name, args = {}) {
695
+ trigger.disabled = true;
696
+ clear(status);
697
+ try {
698
+ const result = await frame.command(name, args);
699
+ say(status, result.text, "ok");
700
+ return result;
701
+ } catch (error) {
702
+ say(status, errorText(error, words), "error");
703
+ return null;
704
+ } finally {
705
+ trigger.disabled = false;
706
+ }
707
+ }
708
+ function onSubmit(target, handler) {
709
+ target.addEventListener("submit", (event) => {
710
+ event.preventDefault();
711
+ void handler();
712
+ });
713
+ }
714
+ function watch(frame, refresh) {
715
+ let timer;
716
+ frame.onEvent(["records", "command", "plugin"], () => {
717
+ clearTimeout(timer);
718
+ timer = setTimeout(() => {
719
+ void refresh();
720
+ }, 150);
721
+ });
722
+ }
723
+ function sequenced(load) {
724
+ let current = 0;
725
+ return async () => {
726
+ current += 1;
727
+ const mine = current;
728
+ await load(() => mine === current);
729
+ };
730
+ }
731
+ function page() {
732
+ return el("div", "page");
733
+ }
734
+ function toolbar(...children) {
735
+ const node = el("div", "toolbar");
736
+ node.append(...children);
737
+ return node;
738
+ }
739
+ function errorBlock(error, words) {
740
+ const node = el("div", "sc-callout sc-callout-danger callout");
741
+ node.append(el("p", "sc-callout-body", errorText(error, words)));
742
+ return node;
743
+ }
744
+
745
+ // src/web/pages/activity.ts
746
+ var OUTCOMES = ["sent", "skipped", "failed"];
747
+ function noteIn(words, note) {
748
+ const at = note.indexOf(":");
749
+ const code = at < 0 ? note : note.slice(0, at);
750
+ const detail = at < 0 ? "" : note.slice(at + 1);
751
+ return NOTES.includes(code) ? words.activity.notes[code](detail) : note;
752
+ }
753
+ var TONES = {
754
+ sent: "success",
755
+ skipped: "neutral",
756
+ failed: "danger"
757
+ };
758
+ async function renderActivity(root2, frame, words) {
759
+ const w = words.activity;
760
+ const when = dateTimeIn(frame.context.language);
761
+ const node = page();
762
+ root2.replaceChildren(node);
763
+ let chosen = "all";
764
+ const draw = sequenced(async (fresh) => {
765
+ let rows;
766
+ try {
767
+ rows = await frame.query("activity", {
768
+ ...chosen === "all" ? {} : { outcome: chosen },
769
+ limit: 100
770
+ });
771
+ } catch (error) {
772
+ node.replaceChildren(errorBlock(error, words));
773
+ return;
774
+ }
775
+ if (!fresh()) {
776
+ return;
777
+ }
778
+ const filter = pills(
779
+ [
780
+ { value: "all", label: w.all },
781
+ ...OUTCOMES.map((outcome) => ({ value: outcome, label: w.outcomes[outcome] }))
782
+ ],
783
+ chosen,
784
+ w.outcome,
785
+ (value) => {
786
+ chosen = value;
787
+ void draw();
788
+ }
789
+ );
790
+ const lines = rows.map(
791
+ (line) => row([
792
+ when(line.at),
793
+ w.kinds[line.kind] ?? line.kind,
794
+ badge(w.outcomes[line.outcome], TONES[line.outcome]),
795
+ noteIn(words, line.note),
796
+ String(line.attempt)
797
+ ])
798
+ );
799
+ node.replaceChildren(
800
+ pageHead(w.title, w.lead),
801
+ panelWith(
802
+ w.title,
803
+ [filter.node],
804
+ lines.length === 0 ? empty(w.none) : table([w.when, w.event, w.outcome, w.note, w.attempt], lines)
805
+ )
806
+ );
807
+ });
808
+ await draw();
809
+ watch(frame, draw);
810
+ }
811
+
812
+ // src/web/pages/connection.ts
813
+ var SETTINGS = "/settings/plugins/messenger";
814
+ function step(done, title, words, ...body) {
815
+ const item = el("li", `step${done ? " step-done" : ""}`);
816
+ const head = el("div", "step-head");
817
+ head.append(
818
+ el("span", "step-title", title),
819
+ badge(done ? words.connection.done : words.connection.todo, done ? "success" : "neutral")
820
+ );
821
+ const content = el("div", "step-body");
822
+ content.append(...body);
823
+ item.append(head, content);
824
+ return item;
825
+ }
826
+ function build(frame, words, status) {
827
+ const w = words.connection;
828
+ const when = dateTimeIn(frame.context.language);
829
+ const connection = status.connection;
830
+ const connected = connection?.phase === "connected";
831
+ const parts = [pageHead(w.title, w.lead)];
832
+ if (connection?.problem !== null && connection?.problem !== void 0) {
833
+ parts.push(
834
+ callout(w.problem, `${connection.problem} (${when(connection.problemAt)})`, "warning")
835
+ );
836
+ }
837
+ if (status.failures > 0) {
838
+ parts.push(callout(w.failures(String(status.failures)), void 0, "warning"));
839
+ }
840
+ const addressOk = status.address.state === "ok";
841
+ const addressStep = step(
842
+ addressOk,
843
+ w.address,
844
+ words,
845
+ toolbar(
846
+ el("span", "mono", status.address.url ?? DASH),
847
+ badge(w.addressState[status.address.state], addressOk ? "success" : "warning")
848
+ ),
849
+ muted(w.addressHint[status.address.state])
850
+ );
851
+ const token = status.secrets.slackConfigToken;
852
+ const tokenText = !token.set ? w.tokenMissing : token.expiresAt === null ? w.tokenPasted : w.tokenSet(when(token.expiresAt));
853
+ const tokenStep = step(
854
+ token.set,
855
+ w.token,
856
+ words,
857
+ muted(tokenText),
858
+ toolbar(button(words.openSettings, () => frame.navigate(SETTINGS), "secondary", true))
859
+ );
860
+ const made = connection !== null && connection.appId !== null;
861
+ const language = pills(
862
+ LANGUAGES.map((value) => ({ value, label: words.languages[value] })),
863
+ connection?.language ?? languageOf(frame.context.workspace?.languages?.[0]),
864
+ w.appLanguage
865
+ );
866
+ const makeStatus = statusLine();
867
+ const make = button(
868
+ made ? w.update : w.make,
869
+ () => {
870
+ void runCommand(frame, make, makeStatus, words, "connect", { language: language.value() });
871
+ },
872
+ made ? "secondary" : "primary"
873
+ );
874
+ make.disabled = !addressOk || !token.set;
875
+ const appStep = step(
876
+ made,
877
+ w.app,
878
+ words,
879
+ muted(
880
+ made && connection?.appId !== null && connection?.appId !== void 0 ? w.appMade(connection.appId) : w.appMissing
881
+ ),
882
+ field(w.appLanguage, language.node),
883
+ toolbar(make),
884
+ makeStatus
885
+ );
886
+ const installBody = [];
887
+ if (connected) {
888
+ installBody.push(muted(w.installed(connection?.teamName ?? connection?.teamId ?? DASH)));
889
+ if (connection?.connectedBy !== null && connection?.connectedBy !== void 0) {
890
+ installBody.push(
891
+ muted(
892
+ w.installedBy(
893
+ connection.connectedBy.name ?? connection.connectedBy.login,
894
+ when(connection.connectedAt)
895
+ )
896
+ )
897
+ );
898
+ }
899
+ } else {
900
+ installBody.push(muted(w.notInstalled));
901
+ const installStatus = statusLine();
902
+ if (status.authorizeUrl !== null) {
903
+ const url = status.authorizeUrl;
904
+ installBody.push(toolbar(button(w.installOpen, () => frame.open(url), "primary")));
905
+ } else {
906
+ const prepare = button(w.installPrepare, () => {
907
+ void runCommand(frame, prepare, installStatus, words, "connect", {
908
+ language: language.value()
909
+ });
910
+ });
911
+ prepare.disabled = !made || !addressOk || !token.set;
912
+ installBody.push(toolbar(prepare));
913
+ }
914
+ installBody.push(muted(w.installHint), installStatus);
915
+ }
916
+ const installStep = step(connected, w.install, words, ...installBody);
917
+ const steps = el("ol", "steps");
918
+ steps.append(addressStep, tokenStep, appStep, installStep);
919
+ parts.push(panel(w.steps, steps));
920
+ if (connection !== null && connection.appId !== null) {
921
+ const secrets = SECRETS.map((name) => {
922
+ const secret = status.secrets[name];
923
+ const stood = !secret.set ? w.secretUnset : secret.source === "administrator" ? w.secretPasted : w.secretFrom(secret.source ?? DASH);
924
+ return row([
925
+ w.secretNames[name],
926
+ badge(stood, secret.set ? "success" : "neutral"),
927
+ secret.expiresAt === null ? DASH : w.expires(when(secret.expiresAt))
928
+ ]);
929
+ });
930
+ parts.push(
931
+ panel(
932
+ w.details,
933
+ factsRow([
934
+ [w.workspace, connection.teamName ?? DASH],
935
+ [w.bot, connection.botUserId ?? DASH],
936
+ [w.shortcut, connection.shortcut ? w.shortcutOn : w.shortcutOff],
937
+ [w.renewed, when(connection.rotatedAt)]
938
+ ]),
939
+ el("h3", "subheading", w.secrets),
940
+ table(["", "", ""], secrets)
941
+ )
942
+ );
943
+ }
944
+ const hr = status.hr;
945
+ const granted = hr.asked.every((role) => hr.granted.includes(role));
946
+ const hrParts = [
947
+ toolbar(badge(w.hrState[hr.state], hr.state === "on" ? "success" : "neutral")),
948
+ muted(w.hrHint[hr.state])
949
+ ];
950
+ if (hr.version !== null) {
951
+ hrParts.push(muted(w.hrVersion(hr.version, hr.range)));
952
+ }
953
+ hrParts.push(
954
+ toolbar(
955
+ el("span", "label", w.hrRole),
956
+ badge(granted ? w.hrGranted : w.hrNotGranted, granted ? "success" : "warning")
957
+ )
958
+ );
959
+ if (!granted || hr.state === "no_capability") {
960
+ hrParts.push(
961
+ muted(w.hrGrantHint),
962
+ toolbar(button(words.openSettings, () => frame.navigate(SETTINGS), "secondary", true))
963
+ );
964
+ }
965
+ parts.push(panel(w.hr, ...hrParts));
966
+ if (connection !== null && connection.appId !== null) {
967
+ const tryParts = [];
968
+ if (connected) {
969
+ const channel = input("text", { placeholder: "C0123ABCD", maxLength: 40 });
970
+ const testStatus = statusLine();
971
+ const send = button(w.test, () => {
972
+ const value = channel.value.trim();
973
+ void runCommand(
974
+ frame,
975
+ send,
976
+ testStatus,
977
+ words,
978
+ "test",
979
+ value.length > 0 ? { channel: value } : {}
980
+ );
981
+ });
982
+ tryParts.push(
983
+ field(w.testChannel, channel, w.testChannelHint),
984
+ toolbar(send),
985
+ testStatus,
986
+ el("hr", "rule")
987
+ );
988
+ }
989
+ const outStatus = statusLine();
990
+ let armed = false;
991
+ const out = button(
992
+ w.disconnect,
993
+ () => {
994
+ if (!armed) {
995
+ armed = true;
996
+ out.textContent = w.disconnectSure;
997
+ return;
998
+ }
999
+ void runCommand(frame, out, outStatus, words, "disconnect");
1000
+ },
1001
+ "danger"
1002
+ );
1003
+ parts.push(
1004
+ panel(
1005
+ connected ? w.tryIt : w.disconnect,
1006
+ ...tryParts,
1007
+ muted(w.disconnectHint),
1008
+ toolbar(out),
1009
+ outStatus
1010
+ )
1011
+ );
1012
+ }
1013
+ return parts;
1014
+ }
1015
+ async function renderConnection(root2, frame, words) {
1016
+ const node = page();
1017
+ root2.replaceChildren(node);
1018
+ const draw = sequenced(async (fresh) => {
1019
+ let status;
1020
+ try {
1021
+ status = await frame.query("status");
1022
+ } catch (error) {
1023
+ node.replaceChildren(errorBlock(error, words));
1024
+ return;
1025
+ }
1026
+ if (fresh()) {
1027
+ node.replaceChildren(...build(frame, words, status));
1028
+ }
1029
+ });
1030
+ await draw();
1031
+ watch(frame, draw);
1032
+ }
1033
+
1034
+ // src/web/pages/people.ts
1035
+ var STATES = ["no_email", "no_account", "not_checked", "matched"];
1036
+ var TONES2 = {
1037
+ matched: "success",
1038
+ no_email: "warning",
1039
+ no_account: "neutral",
1040
+ not_checked: "neutral"
1041
+ };
1042
+ async function renderPeople(root2, frame, words) {
1043
+ const w = words.people;
1044
+ const when = dateTimeIn(frame.context.language);
1045
+ const node = page();
1046
+ root2.replaceChildren(node);
1047
+ let chosen = "all";
1048
+ const draw = sequenced(async (fresh) => {
1049
+ let report;
1050
+ try {
1051
+ report = await frame.query("people", {
1052
+ ...chosen === "all" ? {} : { state: chosen },
1053
+ limit: 200
1054
+ });
1055
+ } catch (error) {
1056
+ node.replaceChildren(errorBlock(error, words));
1057
+ return;
1058
+ }
1059
+ if (!fresh()) {
1060
+ return;
1061
+ }
1062
+ const matchStatus = statusLine();
1063
+ const matchNow = button(w.matchNow, () => {
1064
+ void runCommand(frame, matchNow, matchStatus, words, "match");
1065
+ });
1066
+ matchNow.disabled = !report.connected;
1067
+ const parts = [pageHead(w.title, w.lead, matchNow)];
1068
+ parts.push(matchStatus);
1069
+ if (!report.connected) {
1070
+ parts.push(callout(w.notConnected, void 0, "warning"));
1071
+ }
1072
+ parts.push(
1073
+ factsRow([
1074
+ [
1075
+ w.lastMatched,
1076
+ report.matchedAt === null ? w.neverMatched : w.matchedAt(when(report.matchedAt))
1077
+ ],
1078
+ ...STATES.map((state) => [w.states[state], String(report.counts[state])])
1079
+ ])
1080
+ );
1081
+ const filter = pills(
1082
+ [
1083
+ { value: "all", label: w.all },
1084
+ ...STATES.map((state) => ({
1085
+ value: state,
1086
+ label: `${w.states[state]} ${report.counts[state]}`
1087
+ }))
1088
+ ],
1089
+ chosen,
1090
+ w.state,
1091
+ (value) => {
1092
+ chosen = value;
1093
+ void draw();
1094
+ }
1095
+ );
1096
+ const rows = report.people.map(
1097
+ (match) => row([
1098
+ who(frame, match.person),
1099
+ badge(w.states[match.state], TONES2[match.state]),
1100
+ match.slack === null ? muted(w.stateHints[match.state]) : match.slack.name
1101
+ ])
1102
+ );
1103
+ parts.push(
1104
+ panelWith(
1105
+ w.title,
1106
+ [filter.node],
1107
+ rows.length === 0 ? empty(w.nobody) : table([w.person, w.state, w.account], rows),
1108
+ ...report.total > report.people.length ? [muted(w.more(String(report.people.length), String(report.total)))] : []
1109
+ )
1110
+ );
1111
+ node.replaceChildren(...parts);
1112
+ });
1113
+ await draw();
1114
+ watch(frame, draw);
1115
+ }
1116
+
1117
+ // src/web/pages/routes.ts
1118
+ function routeRows(frame, words, routes) {
1119
+ const w = words.routes;
1120
+ return routes.map((route) => {
1121
+ const status = statusLine();
1122
+ const remove = button(
1123
+ w.remove,
1124
+ () => {
1125
+ void runCommand(frame, remove, status, words, "unroute", { id: route.id });
1126
+ },
1127
+ "ghost",
1128
+ true
1129
+ );
1130
+ const cellContent = document.createElement("span");
1131
+ cellContent.append(remove, status);
1132
+ return row([
1133
+ w.kinds[route.kind],
1134
+ `#${route.channelName}`,
1135
+ words.languages[route.language],
1136
+ w.asNames[route.as],
1137
+ cellContent
1138
+ ]);
1139
+ });
1140
+ }
1141
+ function addForm(frame, words, language, connected) {
1142
+ const w = words.routes;
1143
+ const kind = select(
1144
+ ROUTE_KINDS.map((value) => ({ value, label: w.kinds[value] })),
1145
+ ROUTE_KINDS[0]
1146
+ );
1147
+ const kindHint = muted(w.kindHints[ROUTE_KINDS[0]]);
1148
+ kind.addEventListener("change", () => {
1149
+ kindHint.textContent = w.kindHints[kind.value] ?? "";
1150
+ });
1151
+ const channel = select([{ value: "", label: connected ? w.channelPick : w.connectFirst }], "");
1152
+ channel.disabled = true;
1153
+ const said = statusLine();
1154
+ if (connected) {
1155
+ void frame.query("channels").then((answer) => {
1156
+ const channels = answer;
1157
+ channel.replaceChildren();
1158
+ const first = document.createElement("option");
1159
+ first.value = "";
1160
+ first.textContent = channels.length === 0 ? w.noChannels : w.channelPick;
1161
+ channel.append(first);
1162
+ for (const found of channels) {
1163
+ const option = document.createElement("option");
1164
+ option.value = found.id;
1165
+ option.textContent = found.private ? `#${found.name} (${w.channelPrivate})` : `#${found.name}`;
1166
+ channel.append(option);
1167
+ }
1168
+ channel.disabled = channels.length === 0;
1169
+ }).catch((error) => {
1170
+ say(said, errorText(error, words), "error");
1171
+ });
1172
+ }
1173
+ const languages = pills(
1174
+ LANGUAGES.map((value) => ({ value, label: words.languages[value] })),
1175
+ language,
1176
+ w.language
1177
+ );
1178
+ const as = optionCards(
1179
+ ROUTE_AS.map((value) => ({ value, label: w.asNames[value], hint: w.asHints[value] })),
1180
+ "person",
1181
+ w.as
1182
+ );
1183
+ const submit = submitButton(w.save);
1184
+ submit.disabled = !connected;
1185
+ const node = form(
1186
+ field(w.kind, kind),
1187
+ kindHint,
1188
+ field(w.channel, channel),
1189
+ group(w.language, languages.node),
1190
+ group(w.as, as.node),
1191
+ actions(submit),
1192
+ said
1193
+ );
1194
+ onSubmit(node, async () => {
1195
+ if (channel.value === "") {
1196
+ say(said, w.channelPick, "error");
1197
+ return;
1198
+ }
1199
+ await runCommand(frame, submit, said, words, "route", {
1200
+ channel: channel.value,
1201
+ kind: kind.value,
1202
+ language: languages.value(),
1203
+ as: as.value()
1204
+ });
1205
+ });
1206
+ return panel(w.add, node);
1207
+ }
1208
+ async function renderRoutes(root2, frame, words) {
1209
+ const w = words.routes;
1210
+ const node = page();
1211
+ root2.replaceChildren(node);
1212
+ const draw = sequenced(async (fresh) => {
1213
+ let routes;
1214
+ let status;
1215
+ try {
1216
+ [routes, status] = await Promise.all([
1217
+ frame.query("routes"),
1218
+ frame.query("status")
1219
+ ]);
1220
+ } catch (error) {
1221
+ node.replaceChildren(errorBlock(error, words));
1222
+ return;
1223
+ }
1224
+ if (!fresh()) {
1225
+ return;
1226
+ }
1227
+ const connected = status.connection?.phase === "connected";
1228
+ const parts = [pageHead(w.title, w.lead)];
1229
+ if (!connected) {
1230
+ parts.push(callout(w.notConnected, void 0, "warning"));
1231
+ }
1232
+ parts.push(
1233
+ panel(
1234
+ w.list,
1235
+ routes.length === 0 ? empty(w.none) : table([w.kind, w.channel, w.language, w.as, ""], routeRows(frame, words, routes))
1236
+ )
1237
+ );
1238
+ parts.push(
1239
+ addForm(frame, words, languageOf(frame.context.workspace?.languages?.[0]), connected)
1240
+ );
1241
+ node.replaceChildren(...parts);
1242
+ });
1243
+ await draw();
1244
+ watch(frame, draw);
1245
+ }
1246
+
1247
+ // src/web/words.ts
1248
+ var EN = {
1249
+ failed: "Something went wrong.",
1250
+ yes: "Yes",
1251
+ no: "No",
1252
+ never: "Never",
1253
+ refresh: "Refresh",
1254
+ openSettings: "Open the plugin's settings",
1255
+ languages: { en: "English", ko: "Korean" },
1256
+ connection: {
1257
+ title: "Slack",
1258
+ lead: "The workspace's Slack, connected through an app the console makes and keeps.",
1259
+ problem: "Something went wrong",
1260
+ steps: "Getting connected",
1261
+ address: "The console's address",
1262
+ addressState: {
1263
+ ok: "Reachable",
1264
+ none: "None",
1265
+ not_https: "Not https",
1266
+ private: "Private"
1267
+ },
1268
+ addressHint: {
1269
+ ok: "Slack reaches the console at this address.",
1270
+ none: "The deployment names no public address (PUBLIC_URL), so Slack cannot reach the console.",
1271
+ not_https: "Slack reaches an app over https alone; the deployment serves the console over https first.",
1272
+ private: "This address is on this machine or a private network, where Slack cannot reach it. Slack needs a public https address."
1273
+ },
1274
+ token: "App configuration token",
1275
+ tokenSet: (expires) => `Set; renewed by the plugin, next before ${expires}`,
1276
+ tokenPasted: "Set by an administrator; the plugin renews it within the hour",
1277
+ tokenMissing: "Not set. At api.slack.com/apps, under Your App Configuration Tokens, generate a token for the workspace; an administrator pastes the access token and the refresh token on the plugin's settings page.",
1278
+ app: "The app",
1279
+ appMissing: "Not made yet. The plugin makes it from its manifest, with the configuration token.",
1280
+ appMade: (appId) => `Made in Slack (${appId}).`,
1281
+ appLanguage: "Names in Slack",
1282
+ make: "Make the app",
1283
+ update: "Update the app",
1284
+ install: "Installed in the workspace",
1285
+ installed: (team) => `Installed in ${team}.`,
1286
+ notInstalled: "Not installed yet.",
1287
+ installOpen: "Install in Slack",
1288
+ installPrepare: "Prepare the installation",
1289
+ installHint: "Someone who may install apps in the workspace presses it; Slack's page opens in a new tab and comes back here.",
1290
+ installedBy: (who2, when) => `By ${who2}, ${when}`,
1291
+ done: "Done",
1292
+ todo: "To do",
1293
+ details: "The connection",
1294
+ workspace: "Workspace",
1295
+ bot: "Bot user",
1296
+ shortcut: "Leave shortcut",
1297
+ shortcutOn: "In the app",
1298
+ shortcutOff: "Not in the app",
1299
+ renewed: "Token renewed",
1300
+ secrets: "Secrets",
1301
+ secretNames: {
1302
+ slackConfigToken: "App configuration token",
1303
+ slackConfigRefreshToken: "Configuration refresh token",
1304
+ slackClientSecret: "Client secret",
1305
+ slackSigningSecret: "Signing secret",
1306
+ slackBotToken: "Bot token"
1307
+ },
1308
+ secretFrom: (source) => `Sealed from ${source}`,
1309
+ secretPasted: "Set by an administrator",
1310
+ secretUnset: "Not set",
1311
+ expires: (when) => `expires ${when}`,
1312
+ hr: "HR",
1313
+ hrState: {
1314
+ on: "Connected",
1315
+ off: "Off",
1316
+ no_capability: "Not allowed",
1317
+ outside: "Other version"
1318
+ },
1319
+ hrHint: {
1320
+ on: "Leave is requested from Slack with the shortcut, and each request's thread follows HR's events.",
1321
+ off: "HR is not enabled, so the shortcut is not in the app and no thread is begun.",
1322
+ no_capability: "HR's api is not turned on for the messenger: an administrator turns on plugins:hr on the plugin's settings page.",
1323
+ outside: "This version of HR is not one the messenger was written for, so it leaves HR alone."
1324
+ },
1325
+ hrVersion: (version, range) => `HR ${version}; the messenger reads ${range}`,
1326
+ hrRole: "Role asked of HR",
1327
+ hrGranted: "Granted",
1328
+ hrNotGranted: "Not granted",
1329
+ hrGrantHint: "Without HR's role the messenger cannot read a request, and no thread is begun. An administrator grants it on the plugin's settings page.",
1330
+ tryIt: "Try it",
1331
+ testChannel: "Channel",
1332
+ testChannelHint: "A channel's id (C0123ABCD); left empty, a direct message to you.",
1333
+ test: "Send a test message",
1334
+ disconnect: "Disconnect",
1335
+ disconnectSure: "Press again to uninstall and delete",
1336
+ disconnectHint: "Uninstalls the app from the workspace and deletes it. Connecting again makes a new app: in this workspace, the routes and the matched accounts stay; in another, they go, and its people are matched within the hour.",
1337
+ failures: (count) => `${count} deliveries failed in the last day; the Activity page says why.`
1338
+ },
1339
+ routes: {
1340
+ title: "Routes",
1341
+ lead: "Which events go to which channel, in which language, under whose name.",
1342
+ notConnected: "Connect Slack first: a route names a channel of the workspace.",
1343
+ list: "Routes",
1344
+ none: "No route yet: nothing goes to a channel.",
1345
+ kind: "What",
1346
+ channel: "Channel",
1347
+ language: "Language",
1348
+ as: "Under whose name",
1349
+ kinds: { "hr:leave-approved": "Leave, once approved" },
1350
+ kindHints: {
1351
+ "hr:leave-approved": "Who is out, when, and the kind of leave; never the reason. Struck through if it is cancelled."
1352
+ },
1353
+ asNames: { person: "The person's", app: "The app's" },
1354
+ asHints: {
1355
+ person: "In the person's name and picture, under the app's mark; the app's, naming them, where their account is not matched.",
1356
+ app: "Always the app's, naming the person."
1357
+ },
1358
+ remove: "Remove",
1359
+ add: "Add a route",
1360
+ save: "Save the route",
1361
+ channelPick: "Choose a channel",
1362
+ channelPrivate: "private",
1363
+ noChannels: "Slack lists no channel the app can post to.",
1364
+ connectFirst: "Connect Slack to choose a channel",
1365
+ saved: "Saved."
1366
+ },
1367
+ people: {
1368
+ title: "People",
1369
+ lead: "Each person matched to their Slack account by email: the console's address against the workspace's members.",
1370
+ notConnected: "Connect Slack first; then the people are matched within the hour, and every day after.",
1371
+ matchNow: "Match now",
1372
+ lastMatched: "Last matched",
1373
+ matchedAt: (when) => when,
1374
+ neverMatched: "Not matched yet",
1375
+ states: {
1376
+ matched: "Matched",
1377
+ no_email: "No email",
1378
+ no_account: "No account",
1379
+ not_checked: "Not checked"
1380
+ },
1381
+ stateHints: {
1382
+ matched: "Messages about them carry their name; they may use the shortcut and the buttons.",
1383
+ no_email: "The console knows no address for them: an administrator writes it on the console's People page.",
1384
+ no_account: "No member of the workspace has their address.",
1385
+ not_checked: "Matched at the next run."
1386
+ },
1387
+ all: "Everyone",
1388
+ person: "Person",
1389
+ state: "State",
1390
+ account: "Slack account",
1391
+ nobody: "Nobody here.",
1392
+ more: (shown, total) => `${shown} of ${total} shown.`
1393
+ },
1394
+ activity: {
1395
+ title: "Activity",
1396
+ lead: "What the messenger did with each event it heard, kept for a month.",
1397
+ all: "All",
1398
+ outcomes: { sent: "Sent", skipped: "Passed over", failed: "Failed" },
1399
+ when: "When",
1400
+ event: "Event",
1401
+ outcome: "Outcome",
1402
+ note: "What happened",
1403
+ attempt: "Try",
1404
+ none: "Nothing yet.",
1405
+ kinds: {
1406
+ "plugin:hr:leave-requested": "Leave requested",
1407
+ "plugin:hr:leave-advanced": "Next step",
1408
+ "plugin:hr:leave-approved": "Leave approved",
1409
+ "plugin:hr:leave-rejected": "Leave rejected",
1410
+ "plugin:hr:leave-returned": "Sent back",
1411
+ "plugin:hr:leave-cancelling": "Cancellation asked",
1412
+ "plugin:hr:leave-cancelled": "Leave cancelled",
1413
+ "plugin:hr:leave-kept": "Leave kept"
1414
+ },
1415
+ notes: {
1416
+ thread: () => "The conversation is begun and the step asked.",
1417
+ nobody: () => "Nobody of this request is in Slack.",
1418
+ next_step: () => "The step is closed and the next asked.",
1419
+ approved: (posts) => `Said in the thread; ${posts} channel posts.`,
1420
+ approved_partly: (channels) => `Said in the thread; not posted to ${channels}.`,
1421
+ rejected: () => "The rejection is said in the thread.",
1422
+ returned: () => "The request sent back is said in the thread.",
1423
+ cancel_asked: () => "The cancellation is asked in the thread.",
1424
+ cancelled: (places) => `Struck through in ${places} places.`,
1425
+ kept: () => "The leave kept is said in the thread.",
1426
+ no_thread: () => "Nothing was told of this request before.",
1427
+ not_leave: () => "Not an event the messenger tells.",
1428
+ not_connected: () => "Slack is not connected.",
1429
+ no_capability: () => "HR's api is not turned on for the messenger.",
1430
+ outside: (version) => `HR ${version} is not a version the messenger reads.`,
1431
+ no_grant: () => "The messenger holds no role in HR to read the request.",
1432
+ hidden: () => "HR does not show the messenger the request.",
1433
+ slack: (code) => `Slack refused: ${code}.`,
1434
+ console: (code) => `The console refused: ${code}.`,
1435
+ rate_limited: () => "Slack asked to wait; the console tries again.",
1436
+ failed: () => "Something went wrong."
1437
+ }
1438
+ }
1439
+ };
1440
+ var KO = {
1441
+ failed: "\uBB38\uC81C\uAC00 \uC0DD\uACBC\uC2B5\uB2C8\uB2E4.",
1442
+ yes: "\uC608",
1443
+ no: "\uC544\uB2C8\uC694",
1444
+ never: "\uC5C6\uC74C",
1445
+ refresh: "\uC0C8\uB85C\uACE0\uCE68",
1446
+ openSettings: "\uD50C\uB7EC\uADF8\uC778 \uC124\uC815 \uC5F4\uAE30",
1447
+ languages: { en: "\uC601\uC5B4", ko: "\uD55C\uAD6D\uC5B4" },
1448
+ connection: {
1449
+ title: "Slack",
1450
+ lead: "\uCF58\uC194\uC774 \uB9CC\uB4E4\uACE0 \uAD00\uB9AC\uD558\uB294 \uC571\uC73C\uB85C \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uC758 Slack\uC744 \uC5F0\uACB0\uD569\uB2C8\uB2E4.",
1451
+ problem: "\uBB38\uC81C\uAC00 \uC0DD\uACBC\uC2B5\uB2C8\uB2E4",
1452
+ steps: "\uC5F0\uACB0\uD558\uAE30",
1453
+ address: "\uCF58\uC194 \uC8FC\uC18C",
1454
+ addressState: {
1455
+ ok: "\uC5F0\uACB0 \uAC00\uB2A5",
1456
+ none: "\uC5C6\uC74C",
1457
+ not_https: "https \uC544\uB2D8",
1458
+ private: "\uC0AC\uC124 \uC8FC\uC18C"
1459
+ },
1460
+ addressHint: {
1461
+ ok: "Slack\uC774 \uC774 \uC8FC\uC18C\uB85C \uCF58\uC194\uC5D0 \uB2FF\uC2B5\uB2C8\uB2E4.",
1462
+ none: "\uBC30\uD3EC\uC5D0 \uACF5\uAC1C \uC8FC\uC18C(PUBLIC_URL)\uAC00 \uC5C6\uC5B4 Slack\uC774 \uCF58\uC194\uC5D0 \uB2FF\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.",
1463
+ not_https: "Slack\uC740 https\uB85C\uB9CC \uC571\uC5D0 \uB2FF\uC2B5\uB2C8\uB2E4. \uBC30\uD3EC\uC5D0\uC11C \uBA3C\uC800 \uCF58\uC194\uC744 https\uB85C \uC11C\uBE44\uC2A4\uD574\uC57C \uD569\uB2C8\uB2E4.",
1464
+ private: "\uC774 \uC8FC\uC18C\uB294 \uC774 \uCEF4\uD4E8\uD130\uB098 \uC0AC\uC124\uB9DD\uC5D0 \uC788\uC5B4 Slack\uC774 \uB2FF\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. Slack\uC5D0\uB294 \uACF5\uAC1C\uB41C https \uC8FC\uC18C\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4."
1465
+ },
1466
+ token: "\uC571 \uC124\uC815 \uD1A0\uD070",
1467
+ tokenSet: (expires) => `\uC124\uC815\uB428. \uD50C\uB7EC\uADF8\uC778\uC774 \uAC31\uC2E0\uD558\uBA70, \uB2E4\uC74C \uAC31\uC2E0\uC740 ${expires} \uC804`,
1468
+ tokenPasted: "\uAD00\uB9AC\uC790\uAC00 \uC124\uC815\uD568. \uD50C\uB7EC\uADF8\uC778\uC774 \uD55C \uC2DC\uAC04 \uC548\uC5D0 \uAC31\uC2E0\uD569\uB2C8\uB2E4",
1469
+ tokenMissing: "\uC124\uC815\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. api.slack.com/apps\uC758 Your App Configuration Tokens\uC5D0\uC11C \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uC758 \uD1A0\uD070\uC744 \uB9CC\uB4E4\uACE0, \uAD00\uB9AC\uC790\uAC00 Access Token\uACFC Refresh Token\uC744 \uD50C\uB7EC\uADF8\uC778 \uC124\uC815 \uD398\uC774\uC9C0\uC5D0 \uBD99\uC5EC \uB123\uC73C\uC138\uC694.",
1470
+ app: "\uC571",
1471
+ appMissing: "\uC544\uC9C1 \uB9CC\uB4E4\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. \uD50C\uB7EC\uADF8\uC778\uC774 \uC124\uC815 \uD1A0\uD070\uC73C\uB85C \uB9E4\uB2C8\uD398\uC2A4\uD2B8\uC5D0\uC11C \uC571\uC744 \uB9CC\uB4ED\uB2C8\uB2E4.",
1472
+ appMade: (appId) => `Slack\uC5D0 \uB9CC\uB4E4\uC5B4\uC9D0 (${appId}).`,
1473
+ appLanguage: "Slack\uC5D0 \uBCF4\uC774\uB294 \uC774\uB984",
1474
+ make: "\uC571 \uB9CC\uB4E4\uAE30",
1475
+ update: "\uC571 \uC5C5\uB370\uC774\uD2B8",
1476
+ install: "\uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uC5D0 \uC124\uCE58",
1477
+ installed: (team) => `${team}\uC5D0 \uC124\uCE58\uB428.`,
1478
+ notInstalled: "\uC544\uC9C1 \uC124\uCE58\uD558\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.",
1479
+ installOpen: "Slack\uC5D0 \uC124\uCE58",
1480
+ installPrepare: "\uC124\uCE58 \uC900\uBE44",
1481
+ installHint: "\uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uC5D0 \uC571\uC744 \uC124\uCE58\uD560 \uC218 \uC788\uB294 \uC0AC\uB78C\uC774 \uB204\uB974\uC138\uC694. \uC0C8 \uD0ED\uC5D0 Slack \uD398\uC774\uC9C0\uAC00 \uC5F4\uB9AC\uACE0, \uC124\uCE58\uD558\uBA74 \uC774 \uD398\uC774\uC9C0\uB85C \uB3CC\uC544\uC635\uB2C8\uB2E4.",
1482
+ installedBy: (who2, when) => `${who2}, ${when}`,
1483
+ done: "\uC644\uB8CC",
1484
+ todo: "\uD560 \uC77C",
1485
+ details: "\uC5F0\uACB0",
1486
+ workspace: "\uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4",
1487
+ bot: "\uBD07 \uC0AC\uC6A9\uC790",
1488
+ shortcut: "\uD734\uAC00 \uB2E8\uCD95\uD0A4",
1489
+ shortcutOn: "\uC571\uC5D0 \uC788\uC74C",
1490
+ shortcutOff: "\uC571\uC5D0 \uC5C6\uC74C",
1491
+ renewed: "\uD1A0\uD070 \uAC31\uC2E0",
1492
+ secrets: "\uBE44\uBC00 \uAC12",
1493
+ secretNames: {
1494
+ slackConfigToken: "\uC571 \uC124\uC815 \uD1A0\uD070",
1495
+ slackConfigRefreshToken: "\uC124\uC815 \uAC31\uC2E0 \uD1A0\uD070",
1496
+ slackClientSecret: "\uD074\uB77C\uC774\uC5B8\uD2B8 \uC2DC\uD06C\uB9BF",
1497
+ slackSigningSecret: "\uC11C\uBA85 \uC2DC\uD06C\uB9BF",
1498
+ slackBotToken: "\uBD07 \uD1A0\uD070"
1499
+ },
1500
+ secretFrom: (source) => `${source}\uC5D0\uC11C \uBD09\uC778`,
1501
+ secretPasted: "\uAD00\uB9AC\uC790\uAC00 \uC124\uC815",
1502
+ secretUnset: "\uC5C6\uC74C",
1503
+ expires: (when) => `${when} \uB9CC\uB8CC`,
1504
+ hr: "HR",
1505
+ hrState: {
1506
+ on: "\uC5F0\uACB0\uB428",
1507
+ off: "\uAEBC\uC9D0",
1508
+ no_capability: "\uD5C8\uC6A9 \uC548 \uB428",
1509
+ outside: "\uB2E4\uB978 \uBC84\uC804"
1510
+ },
1511
+ hrHint: {
1512
+ on: "Slack\uC5D0\uC11C \uB2E8\uCD95\uD0A4\uB85C \uD734\uAC00\uB97C \uC2E0\uCCAD\uD558\uACE0, \uC2E0\uCCAD\uB9C8\uB2E4 HR\uC758 \uC54C\uB9BC\uC744 \uB530\uB77C \uC2A4\uB808\uB4DC\uAC00 \uC774\uC5B4\uC9D1\uB2C8\uB2E4.",
1513
+ off: "HR\uC774 \uCF1C\uC838 \uC788\uC9C0 \uC54A\uC544 \uB2E8\uCD95\uD0A4\uAC00 \uC571\uC5D0 \uC5C6\uACE0 \uC2A4\uB808\uB4DC\uB3C4 \uB9CC\uB4E4\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.",
1514
+ no_capability: "\uBA54\uC2E0\uC800\uC5D0 HR\uC758 api\uAC00 \uCF1C\uC838 \uC788\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. \uAD00\uB9AC\uC790\uAC00 \uD50C\uB7EC\uADF8\uC778 \uC124\uC815 \uD398\uC774\uC9C0\uC5D0\uC11C plugins:hr\uC744 \uCF1C\uC57C \uD569\uB2C8\uB2E4.",
1515
+ outside: "\uBA54\uC2E0\uC800\uAC00 \uC77D\uB3C4\uB85D \uB9CC\uB4E4\uC5B4\uC9C4 HR \uBC84\uC804\uC774 \uC544\uB2C8\uB77C HR\uC744 \uAC74\uB4DC\uB9AC\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4."
1516
+ },
1517
+ hrVersion: (version, range) => `HR ${version}, \uBA54\uC2E0\uC800\uAC00 \uC77D\uB294 \uBC94\uC704 ${range}`,
1518
+ hrRole: "HR\uC5D0 \uC694\uCCAD\uD55C \uC5ED\uD560",
1519
+ hrGranted: "\uBD80\uC5EC\uB428",
1520
+ hrNotGranted: "\uBD80\uC5EC\uB418\uC9C0 \uC54A\uC74C",
1521
+ hrGrantHint: "HR\uC758 \uC5ED\uD560\uC774 \uC5C6\uC73C\uBA74 \uBA54\uC2E0\uC800\uAC00 \uC2E0\uCCAD\uC744 \uC77D\uC9C0 \uBABB\uD574 \uC2A4\uB808\uB4DC\uB97C \uB9CC\uB4E4\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. \uAD00\uB9AC\uC790\uAC00 \uD50C\uB7EC\uADF8\uC778 \uC124\uC815 \uD398\uC774\uC9C0\uC5D0\uC11C \uBD80\uC5EC\uD569\uB2C8\uB2E4.",
1522
+ tryIt: "\uC2DC\uD5D8\uD574 \uBCF4\uAE30",
1523
+ testChannel: "\uCC44\uB110",
1524
+ testChannelHint: "\uCC44\uB110 ID(C0123ABCD). \uBE44\uC6CC \uB450\uBA74 \uB098\uC5D0\uAC8C DM\uC73C\uB85C \uBCF4\uB0C5\uB2C8\uB2E4.",
1525
+ test: "\uC2DC\uD5D8 \uBA54\uC2DC\uC9C0 \uBCF4\uB0B4\uAE30",
1526
+ disconnect: "\uC5F0\uACB0 \uD574\uC81C",
1527
+ disconnectSure: "\uD55C \uBC88 \uB354 \uB204\uB974\uBA74 \uC81C\uAC70\uD558\uACE0 \uC9C0\uC6C1\uB2C8\uB2E4",
1528
+ disconnectHint: "\uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uC5D0\uC11C \uC571\uC744 \uC81C\uAC70\uD558\uACE0 \uC9C0\uC6C1\uB2C8\uB2E4. \uB2E4\uC2DC \uC5F0\uACB0\uD558\uBA74 \uC0C8 \uC571\uC744 \uB9CC\uB4ED\uB2C8\uB2E4. \uAC19\uC740 \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uBA74 \uCC44\uB110 \uC54C\uB9BC\uACFC \uACC4\uC815 \uC5F0\uACB0\uC774 \uADF8\uB300\uB85C \uB0A8\uACE0, \uB2E4\uB978 \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uBA74 \uC9C0\uC6CC\uC9C0\uBA70 \uADF8\uACF3 \uC0AC\uB78C\uB4E4\uC758 \uACC4\uC815\uC740 \uD55C \uC2DC\uAC04 \uC548\uC5D0 \uB2E4\uC2DC \uC5F0\uACB0\uB429\uB2C8\uB2E4.",
1529
+ failures: (count) => `\uC9C0\uB09C \uD558\uB8E8 \uB3D9\uC548 ${count}\uAC74\uC744 \uBCF4\uB0B4\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4. \uC804\uC1A1 \uAE30\uB85D\uC5D0\uC11C \uC774\uC720\uB97C \uBCF4\uC138\uC694.`
1530
+ },
1531
+ routes: {
1532
+ title: "\uCC44\uB110 \uC54C\uB9BC",
1533
+ lead: "\uC5B4\uB5A4 \uC54C\uB9BC\uC744 \uC5B4\uB290 \uCC44\uB110\uC5D0, \uC5B4\uB290 \uC5B8\uC5B4\uB85C, \uB204\uAD6C \uC774\uB984\uC73C\uB85C \uBCF4\uB0BC\uC9C0 \uC815\uD569\uB2C8\uB2E4.",
1534
+ notConnected: "\uBA3C\uC800 Slack\uC744 \uC5F0\uACB0\uD558\uC138\uC694. \uACBD\uB85C\uB294 \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uC758 \uCC44\uB110\uC744 \uAC00\uB9AC\uD0B5\uB2C8\uB2E4.",
1535
+ list: "\uACBD\uB85C",
1536
+ none: "\uC544\uC9C1 \uACBD\uB85C\uAC00 \uC5C6\uC5B4 \uCC44\uB110\uB85C \uAC00\uB294 \uC54C\uB9BC\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.",
1537
+ kind: "\uC54C\uB9BC",
1538
+ channel: "\uCC44\uB110",
1539
+ language: "\uC5B8\uC5B4",
1540
+ as: "\uB204\uAD6C \uC774\uB984\uC73C\uB85C",
1541
+ kinds: { "hr:leave-approved": "\uC2B9\uC778\uB41C \uD734\uAC00" },
1542
+ kindHints: {
1543
+ "hr:leave-approved": "\uB204\uAC00, \uC5B8\uC81C, \uC5B4\uB5A4 \uD734\uAC00\uB85C \uC26C\uB294\uC9C0. \uC0AC\uC720\uB294 \uB123\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. \uCDE8\uC18C\uB418\uBA74 \uCDE8\uC18C\uC120\uC744 \uAE0B\uC2B5\uB2C8\uB2E4."
1544
+ },
1545
+ asNames: { person: "\uB2F9\uC0AC\uC790 \uC774\uB984", app: "\uC571 \uC774\uB984" },
1546
+ asHints: {
1547
+ person: "\uB2F9\uC0AC\uC790\uC758 \uC774\uB984\uACFC \uC0AC\uC9C4\uC73C\uB85C, \uC571 \uD45C\uC2DC\uC640 \uD568\uAED8 \uC62C\uB9BD\uB2C8\uB2E4. \uACC4\uC815\uC774 \uC5F0\uACB0\uB418\uC9C0 \uC54A\uC740 \uC0AC\uB78C\uC740 \uC571 \uC774\uB984\uC73C\uB85C \uC62C\uB9AC\uACE0 \uBCF8\uBB38\uC5D0 \uC774\uB984\uC744 \uC801\uC2B5\uB2C8\uB2E4.",
1548
+ app: "\uD56D\uC0C1 \uC571 \uC774\uB984\uC73C\uB85C \uC62C\uB9AC\uACE0 \uBCF8\uBB38\uC5D0 \uC774\uB984\uC744 \uC801\uC2B5\uB2C8\uB2E4."
1549
+ },
1550
+ remove: "\uC0AD\uC81C",
1551
+ add: "\uACBD\uB85C \uCD94\uAC00",
1552
+ save: "\uACBD\uB85C \uC800\uC7A5",
1553
+ channelPick: "\uCC44\uB110 \uC120\uD0DD",
1554
+ channelPrivate: "\uBE44\uACF5\uAC1C",
1555
+ noChannels: "\uC571\uC774 \uAE00\uC744 \uC62C\uB9B4 \uC218 \uC788\uB294 \uCC44\uB110\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.",
1556
+ connectFirst: "Slack\uC744 \uC5F0\uACB0\uD558\uBA74 \uCC44\uB110\uC744 \uACE0\uB97C \uC218 \uC788\uC2B5\uB2C8\uB2E4",
1557
+ saved: "\uC800\uC7A5\uD588\uC2B5\uB2C8\uB2E4."
1558
+ },
1559
+ people: {
1560
+ title: "\uACC4\uC815 \uC5F0\uACB0",
1561
+ lead: "\uCF58\uC194\uC758 \uC774\uBA54\uC77C\uACFC \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4 \uBA64\uBC84\uB97C \uB9DE\uCDB0, \uC0AC\uB78C\uB9C8\uB2E4 Slack \uACC4\uC815\uC744 \uC5F0\uACB0\uD569\uB2C8\uB2E4.",
1562
+ notConnected: "\uBA3C\uC800 Slack\uC744 \uC5F0\uACB0\uD558\uC138\uC694. \uC5F0\uACB0\uD558\uBA74 \uD55C \uC2DC\uAC04 \uC548\uC5D0, \uADF8 \uB4A4\uB85C\uB294 \uB9E4\uC77C \uB9DE\uCDA5\uB2C8\uB2E4.",
1563
+ matchNow: "\uC9C0\uAE08 \uB9DE\uCD94\uAE30",
1564
+ lastMatched: "\uB9C8\uC9C0\uB9C9\uC73C\uB85C \uB9DE\uCD98 \uB54C",
1565
+ matchedAt: (when) => when,
1566
+ neverMatched: "\uC544\uC9C1 \uB9DE\uCD94\uC9C0 \uC54A\uC74C",
1567
+ states: {
1568
+ matched: "\uC5F0\uACB0\uB428",
1569
+ no_email: "\uC774\uBA54\uC77C \uC5C6\uC74C",
1570
+ no_account: "\uACC4\uC815 \uC5C6\uC74C",
1571
+ not_checked: "\uD655\uC778 \uC804"
1572
+ },
1573
+ stateHints: {
1574
+ matched: "\uC774 \uC0AC\uB78C\uC5D0 \uAD00\uD55C \uBA54\uC2DC\uC9C0\uC5D0 \uC774\uB984\uC774 \uC2E4\uB9AC\uACE0, \uB2E8\uCD95\uD0A4\uC640 \uBC84\uD2BC\uC744 \uC4F8 \uC218 \uC788\uC2B5\uB2C8\uB2E4.",
1575
+ no_email: "\uCF58\uC194\uC5D0 \uC774\uBA54\uC77C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. \uAD00\uB9AC\uC790\uAC00 \uCF58\uC194\uC758 \uC0AC\uB78C \uD398\uC774\uC9C0\uC5D0\uC11C \uC801\uC5B4\uC57C \uD569\uB2C8\uB2E4.",
1576
+ no_account: "\uC774 \uC774\uBA54\uC77C\uC744 \uC4F0\uB294 \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4 \uBA64\uBC84\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.",
1577
+ not_checked: "\uB2E4\uC74C \uB9DE\uCD94\uAE30\uC5D0\uC11C \uD655\uC778\uD569\uB2C8\uB2E4."
1578
+ },
1579
+ all: "\uBAA8\uB450",
1580
+ person: "\uC0AC\uB78C",
1581
+ state: "\uC0C1\uD0DC",
1582
+ account: "Slack \uACC4\uC815",
1583
+ nobody: "\uD574\uB2F9\uD558\uB294 \uC0AC\uB78C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.",
1584
+ more: (shown, total) => `${total}\uBA85 \uC911 ${shown}\uBA85\uC744 \uBCF4\uC5EC \uC90D\uB2C8\uB2E4.`
1585
+ },
1586
+ activity: {
1587
+ title: "\uC804\uC1A1 \uAE30\uB85D",
1588
+ lead: "\uBA54\uC2E0\uC800\uAC00 \uBC1B\uC740 \uC54C\uB9BC\uB9C8\uB2E4 \uD55C \uC77C\uC744 \uD55C \uB2EC \uB3D9\uC548 \uBCF4\uAD00\uD569\uB2C8\uB2E4.",
1589
+ all: "\uBAA8\uB450",
1590
+ outcomes: { sent: "\uBCF4\uB0C4", skipped: "\uAC74\uB108\uB700", failed: "\uC2E4\uD328" },
1591
+ when: "\uC2DC\uAC01",
1592
+ event: "\uC54C\uB9BC",
1593
+ outcome: "\uACB0\uACFC",
1594
+ note: "\uB0B4\uC6A9",
1595
+ attempt: "\uC2DC\uB3C4",
1596
+ none: "\uC544\uC9C1 \uC5C6\uC2B5\uB2C8\uB2E4.",
1597
+ kinds: {
1598
+ "plugin:hr:leave-requested": "\uD734\uAC00 \uC2E0\uCCAD",
1599
+ "plugin:hr:leave-advanced": "\uB2E4\uC74C \uACB0\uC7AC",
1600
+ "plugin:hr:leave-approved": "\uD734\uAC00 \uC2B9\uC778",
1601
+ "plugin:hr:leave-rejected": "\uD734\uAC00 \uBC18\uB824",
1602
+ "plugin:hr:leave-returned": "\uBCF4\uC644 \uC694\uCCAD",
1603
+ "plugin:hr:leave-cancelling": "\uCDE8\uC18C \uACB0\uC7AC \uC694\uCCAD",
1604
+ "plugin:hr:leave-cancelled": "\uD734\uAC00 \uCDE8\uC18C",
1605
+ "plugin:hr:leave-kept": "\uD734\uAC00 \uC720\uC9C0"
1606
+ },
1607
+ notes: {
1608
+ thread: () => "\uB300\uD654\uB97C \uC5F4\uACE0 \uACB0\uC7AC\uB97C \uC694\uCCAD\uD588\uC2B5\uB2C8\uB2E4.",
1609
+ nobody: () => "\uC774 \uC2E0\uCCAD\uC5D0 \uAD00\uB828\uB41C \uC0AC\uB78C\uC774 Slack\uC5D0 \uC5C6\uC2B5\uB2C8\uB2E4.",
1610
+ next_step: () => "\uACB0\uC7AC \uB2E8\uACC4\uB97C \uB2EB\uACE0 \uB2E4\uC74C \uB2E8\uACC4\uB97C \uC694\uCCAD\uD588\uC2B5\uB2C8\uB2E4.",
1611
+ approved: (posts) => `\uC2A4\uB808\uB4DC\uC5D0 \uC54C\uB9AC\uACE0 \uCC44\uB110\uC5D0 ${posts}\uAC74 \uC62C\uB838\uC2B5\uB2C8\uB2E4.`,
1612
+ approved_partly: (channels) => `\uC2A4\uB808\uB4DC\uC5D0 \uC54C\uB838\uC9C0\uB9CC ${channels}\uC5D0\uB294 \uC62C\uB9AC\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4.`,
1613
+ rejected: () => "\uBC18\uB824\uB97C \uC2A4\uB808\uB4DC\uC5D0 \uC54C\uB838\uC2B5\uB2C8\uB2E4.",
1614
+ returned: () => "\uBCF4\uC644 \uC694\uCCAD\uC744 \uC2A4\uB808\uB4DC\uC5D0 \uC54C\uB838\uC2B5\uB2C8\uB2E4.",
1615
+ cancel_asked: () => "\uCDE8\uC18C \uACB0\uC7AC\uB97C \uC2A4\uB808\uB4DC\uC5D0\uC11C \uC694\uCCAD\uD588\uC2B5\uB2C8\uB2E4.",
1616
+ cancelled: (places) => `${places}\uACF3\uC5D0 \uCDE8\uC18C\uC120\uC744 \uADF8\uC5C8\uC2B5\uB2C8\uB2E4.`,
1617
+ kept: () => "\uD734\uAC00 \uC720\uC9C0\uB97C \uC2A4\uB808\uB4DC\uC5D0 \uC54C\uB838\uC2B5\uB2C8\uB2E4.",
1618
+ no_thread: () => "\uC774 \uC2E0\uCCAD\uC740 \uC804\uC5D0 \uC54C\uB9B0 \uAC83\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.",
1619
+ not_leave: () => "\uBA54\uC2E0\uC800\uAC00 \uC54C\uB9AC\uB294 \uC54C\uB9BC\uC774 \uC544\uB2D9\uB2C8\uB2E4.",
1620
+ not_connected: () => "Slack\uC774 \uC5F0\uACB0\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.",
1621
+ no_capability: () => "\uBA54\uC2E0\uC800\uC5D0 HR\uC758 api\uAC00 \uCF1C\uC838 \uC788\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.",
1622
+ outside: (version) => `\uBA54\uC2E0\uC800\uAC00 \uC77D\uC9C0 \uC54A\uB294 HR \uBC84\uC804\uC785\uB2C8\uB2E4(${version}).`,
1623
+ no_grant: () => "\uBA54\uC2E0\uC800\uC5D0 \uC2E0\uCCAD\uC744 \uC77D\uC744 HR \uC5ED\uD560\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.",
1624
+ hidden: () => "HR\uC774 \uBA54\uC2E0\uC800\uC5D0\uAC8C \uC774 \uC2E0\uCCAD\uC744 \uBCF4\uC5EC \uC8FC\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.",
1625
+ slack: (code) => `Slack\uC774 \uAC70\uC808\uD588\uC2B5\uB2C8\uB2E4: ${code}.`,
1626
+ console: (code) => `\uCF58\uC194\uC774 \uAC70\uC808\uD588\uC2B5\uB2C8\uB2E4: ${code}.`,
1627
+ rate_limited: () => "Slack\uC774 \uAE30\uB2E4\uB824 \uB2EC\uB77C\uACE0 \uD574\uC11C \uCF58\uC194\uC774 \uB2E4\uC2DC \uC2DC\uB3C4\uD569\uB2C8\uB2E4.",
1628
+ failed: () => "\uBB38\uC81C\uAC00 \uC0DD\uACBC\uC2B5\uB2C8\uB2E4."
1629
+ }
1630
+ }
1631
+ };
1632
+ var PACKS = { en: EN, ko: KO };
1633
+ function wordsFor(language) {
1634
+ const tag = language.toLowerCase();
1635
+ return PACKS[tag] ?? PACKS[tag.split("-")[0] ?? ""] ?? EN;
1636
+ }
1637
+
1638
+ // src/web/index.ts
1639
+ async function render(root2, frame, words) {
1640
+ switch (frame.context.page) {
1641
+ case "routes":
1642
+ return renderRoutes(root2, frame, words);
1643
+ case "people":
1644
+ return renderPeople(root2, frame, words);
1645
+ case "activity":
1646
+ return renderActivity(root2, frame, words);
1647
+ default:
1648
+ return renderConnection(root2, frame, words);
1649
+ }
1650
+ }
1651
+ var root = document.getElementById("root");
1652
+ if (root !== null) {
1653
+ const frame = await connect();
1654
+ const words = wordsFor(frame.context.language);
1655
+ try {
1656
+ await render(root, frame, words);
1657
+ } catch (error) {
1658
+ root.replaceChildren(errorBlock(error, words));
1659
+ }
1660
+ }