dsh-plugin-t-expert 0.2.3 → 0.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js CHANGED
@@ -19723,6 +19723,7 @@ var client_bundle_default = 'window.__ModuleLoader__.load({\n id: "dsh-plugin-t-
19723
19723
  var NS = "t-team";
19724
19724
  var PLUGIN_ID = "dsh-plugin-t-expert";
19725
19725
  var DIVISION_FALLBACK = "specialized";
19726
+ var NEW_DIVISION = "__new";
19726
19727
  var zh = {
19727
19728
  "settings.title": "T\u4E13\u5BB6",
19728
19729
  "settings.loading": "\u6B63\u5728\u8BFB\u53D6\u540D\u518C\u2026",
@@ -19761,6 +19762,7 @@ var zh = {
19761
19762
  "button.close": "\u5173\u95ED",
19762
19763
  "trigger.group": "T\u4E13\u5BB6 \xB7 {division}",
19763
19764
  "tab.experts": "\u4E13\u5BB6",
19765
+ "tab.categories": "\u5206\u7C7B",
19764
19766
  "tab.squads": "\u5C0F\u961F",
19765
19767
  "tab.teams": "\u56E2\u961F",
19766
19768
  "tab.roster": "\u961F\u4F0D",
@@ -19768,7 +19770,45 @@ var zh = {
19768
19770
  "roster.squadsHint": "\u9884\u5148\u6311\u597D\u7684\u73ED\u5E95\uFF1A\u6210\u5458\u3001\u522B\u540D\u3001\u542F\u505C\u3002\u7528\u5B83\u5C31\u8BF4 /t <\u5C0F\u961F> <\u76EE\u6807>\uFF0C\u6216\u5728\u8F93\u5165\u6846\u5F39\u7A97\u91CC\u70B9\u300C\u586B\u5165\u300D\uFF1B\u6539\u5B8C\u4FDD\u5B58\u540E\u9700\u91CD\u542F DSH \u624D\u80FD\u7528\u4E8E\u5EFA\u961F\uFF08/t \u5217\u8868\u4F1A\u7ACB\u5373\u5237\u65B0\uFF09\u3002",
19769
19771
  "roster.teams": "\u8FD0\u884C\u4E2D\u7684\u56E2\u961F",
19770
19772
  "roster.teamsHint": "\u6BCF\u6B21\u7528\u6A21\u677F\u8DD1\u8D77\u6765\u7684\u4E00\u6B21\u5B9E\u4F8B\uFF1A\u53EF\u505C\u6B62\u3001\u770B\u8FDB\u5EA6\u3001\u6253\u5F00\u6D3B\u52A8\u9762\u677F\uFF1B\u5F52\u6863\u540E\u4ECD\u53EF\u5728\u8FD9\u91CC\u56DE\u770B\u3002",
19771
- "roster.expertsHint": "\u540D\u518C\uFF1A\u80FD\u88AB @ \u53EC\u5524\u3001\u4E5F\u80FD\u88AB\u5C0F\u961F\u6311\u8FDB\u73ED\u5E95\u7684\u4E13\u5BB6\u3002",
19773
+ "roster.expertsHint": "\u540D\u518C\uFF1A\u80FD\u88AB @ \u53EC\u5524\u3001\u4E5F\u80FD\u88AB\u5C0F\u961F\u6311\u8FDB\u73ED\u5E95\u7684\u4E13\u5BB6\u3002\u5E26\u300C\u81EA\u5EFA\u300D\u6807\u8BB0\u7684\u53EF\u4EE5\u5728\u8FD9\u91CC\u6539\u6216\u5220\u3002",
19774
+ "custom.new": "\uFF0B \u65B0\u5EFA\u4E13\u5BB6",
19775
+ "custom.titleNew": "\u65B0\u5EFA\u4E13\u5BB6",
19776
+ "custom.titleEdit": "\u7F16\u8F91\u300C{name}\u300D",
19777
+ "custom.slug": "slug\uFF08\u6587\u4EF6\u540D\uFF1A\u5C0F\u5199\u5B57\u6BCD\u3001\u6570\u5B57\u3001\u8FDE\u5B57\u7B26\uFF09",
19778
+ "custom.name": "\u540D\u79F0\uFF08\u4E2D\u6587\uFF09*",
19779
+ "custom.nameEn": "\u82F1\u6587\u540D\uFF08\u53EF\u9009\uFF09",
19780
+ "custom.emoji": "\u56FE\u6807 emoji",
19781
+ "custom.description": "\u4E00\u53E5\u8BDD\u7B80\u4ECB*\uFF08\u7F3A\u7B80\u4ECB\u7684\u4E13\u5BB6\u4E0D\u4F1A\u8FDB\u540D\u518C\uFF09",
19782
+ "custom.body": "\u4EBA\u683C\u6B63\u6587\uFF08Markdown\uFF09*\uFF1A\u5199\u6E05\u5B83\u662F\u8C01\u3001\u600E\u4E48\u5E72\u6D3B\u3001\u4EA4\u4ED8\u4EC0\u4E48",
19783
+ "custom.hint": "\u81EA\u5EFA\u4E13\u5BB6\u5355\u72EC\u5B58\u653E\uFF0C\u4E0E\u968F\u5305\u53D1\u5E03\u7684\u5185\u7F6E\u540D\u518C\u5206\u5F00\u2014\u2014\u5347\u7EA7\u63D2\u4EF6\u4E0D\u4F1A\u8986\u76D6\u5B83\u3002\u5B98\u65B9\u5206\u7C7B\u5C5E\u4E8E\u5305\u5185\u540D\u518C\uFF0C\u81EA\u5EFA\u4E13\u5BB6\u53EA\u80FD\u843D\u5728\u81EA\u5EFA\u5206\u7C7B\u91CC\u3002\u5EFA\u597D\u540E\u8BB0\u5F97\u53BB\u542F\u7528\u5B83\u3002",
19784
+ "custom.division": "\u5206\u7C7B",
19785
+ "custom.divisionNew": "\uFF0B \u65B0\u5EFA\u5206\u7C7B\u2026",
19786
+ "custom.divisionKey": "\u5206\u7C7B\u76EE\u5F55\u540D\uFF08\u5C0F\u5199\u5B57\u6BCD\u3001\u6570\u5B57\u3001\u8FDE\u5B57\u7B26\uFF0C\u4EC5 ASCII\uFF09",
19787
+ "custom.divisionLabel": "\u5206\u7C7B\u663E\u793A\u540D\uFF08\u53EF\u9009\uFF0C\u4E2D\u6587\uFF09",
19788
+ "cat.hint": "\u5206\u7C7B\u662F\u4E13\u5BB6\u7684\u5F52\u7C7B\uFF1A\u5B98\u65B9\u5206\u7C7B\u6765\u81EA\u5185\u7F6E\u540D\u518C\uFF08\u53EA\u8BFB\uFF0C\u540D\u5B57\u8DDF\u968F\u4E0A\u6E38\u4E2D\u6587\uFF09\uFF0C\u81EA\u5EFA\u5206\u7C7B\u53EF\u4EE5\u65B0\u5EFA\u3001\u6539\u540D\u3001\u5220\u9664\u3002\u81EA\u5EFA\u4E13\u5BB6\u80FD\u5F52\u5230\u4EFB\u610F\u5206\u7C7B\uFF08\u542B\u5B98\u65B9\u5206\u7C7B\u540D\uFF09\uFF0C\u4F46\u6587\u4EF6\u59CB\u7EC8\u5199\u5728\u4F60\u81EA\u5DF1\u7684\u6570\u636E\u76EE\u5F55\u91CC\uFF0C\u4E0D\u4F1A\u78B0\u5B98\u65B9\u955C\u50CF\u3002",
19789
+ "cat.key": "\u5206\u7C7B\u76EE\u5F55\u540D\uFF08\u5C0F\u5199\u5B57\u6BCD\u3001\u6570\u5B57\u3001\u8FDE\u5B57\u7B26\uFF0C\u4EC5 ASCII\uFF09",
19790
+ "cat.label": "\u663E\u793A\u540D\uFF08\u53EF\u9009\uFF0C\u4E2D\u6587\uFF09",
19791
+ "cat.create": "\u521B\u5EFA\u5206\u7C7B",
19792
+ "cat.mine": "\u81EA\u5EFA\u5206\u7C7B",
19793
+ "cat.official": "\u5B98\u65B9\u5206\u7C7B",
19794
+ "cat.officialNote": "\u5B98\u65B9\u5206\u7C7B\u7684\u540D\u5B57\u4E0E\u5B58\u4EA1\u90FD\u8DDF\u968F\u540D\u518C\u540C\u6B65\uFF0C\u8FD9\u91CC\u53EA\u8BFB\u3002",
19795
+ "cat.empty": "\u8FD8\u6CA1\u6709\u81EA\u5EFA\u5206\u7C7B\uFF1A\u4E0A\u9762\u586B\u76EE\u5F55\u540D\u4E0E\u663E\u793A\u540D\u5373\u53EF\u65B0\u5EFA\u4E00\u4E2A\u3002",
19796
+ "cat.officialBadge": "\u5B98\u65B9",
19797
+ "cat.customBadge": "\u81EA\u5EFA",
19798
+ "cat.count": "{count} \u4F4D\uFF08\u81EA\u5EFA {custom}\uFF09",
19799
+ "cat.rename": "\u6539\u540D",
19800
+ "cat.save": "\u4FDD\u5B58",
19801
+ "cat.delete": "\u5220\u9664",
19802
+ "cat.confirmDelete": "\u786E\u8BA4\u5220\u9664",
19803
+ "custom.save": "\u4FDD\u5B58",
19804
+ "custom.saving": "\u4FDD\u5B58\u4E2D\u2026",
19805
+ "custom.cancel": "\u53D6\u6D88",
19806
+ "custom.edit": "\u7F16\u8F91",
19807
+ "custom.delete": "\u5220\u9664",
19808
+ "custom.confirmDelete": "\u786E\u8BA4\u5220\u9664",
19809
+ "custom.deleteHint": "\u5220\u9664\u300C{name}\u300D\uFF1F\u53EA\u5220\u8FD9\u4E2A\u81EA\u5EFA\u4E13\u5BB6\u6587\u4EF6\uFF0C\u4E0D\u53EF\u64A4\u9500\u3002",
19810
+ "custom.badge": "\u81EA\u5EFA",
19811
+ "custom.loadingBody": "\u6B63\u5728\u8BFB\u53D6\u4EBA\u683C\u6B63\u6587\u2026",
19772
19812
  "squads.loading": "\u6B63\u5728\u8BFB\u53D6\u5C0F\u961F\u2026",
19773
19813
  "squads.count": "{count} \u652F\u5C0F\u961F \xB7 {experts} \u4F4D\u4E13\u5BB6\u53EF\u9009",
19774
19814
  "squads.new": "\u65B0\u5EFA\u5C0F\u961F",
@@ -19867,6 +19907,7 @@ var en = {
19867
19907
  "button.close": "Close",
19868
19908
  "trigger.group": "T Expert \xB7 {division}",
19869
19909
  "tab.experts": "Experts",
19910
+ "tab.categories": "Divisions",
19870
19911
  "tab.squads": "Squads",
19871
19912
  "tab.teams": "Teams",
19872
19913
  "tab.roster": "Roster",
@@ -19874,7 +19915,45 @@ var en = {
19874
19915
  "roster.squadsHint": "A pre-picked roster: members, aliases, enable/disable. Run it with /t <squad> <goal>, or tap Fill in the composer popup. After saving, restart DSH before the squad can start a team (the /t list refreshes immediately).",
19875
19916
  "roster.teams": "Running teams",
19876
19917
  "roster.teamsHint": "One instance per run: stop it, watch progress, open the activity panel; archived teams stay reviewable here.",
19877
- "roster.expertsHint": "The roster: experts available for @-mention and for squad templates.",
19918
+ "roster.expertsHint": "The roster: experts available for @-mention and for squad templates. Custom ones can be edited or deleted here.",
19919
+ "custom.new": "\uFF0B New expert",
19920
+ "custom.titleNew": "New expert",
19921
+ "custom.titleEdit": "Edit \u201C{name}\u201D",
19922
+ "custom.slug": "slug (file name: lowercase letters, digits, hyphen)",
19923
+ "custom.name": "Name (Chinese)*",
19924
+ "custom.nameEn": "English name (optional)",
19925
+ "custom.emoji": "Emoji",
19926
+ "custom.description": "One-line description* (without it the expert never enters the roster)",
19927
+ "custom.body": "Persona body (Markdown)*: who it is, how it works, what it delivers",
19928
+ "custom.hint": "Custom experts live in their own directory, kept apart from the bundled roster \u2014 upgrades never overwrite them. Official divisions belong to that bundled roster, so custom experts can only live in custom divisions. Remember to enable them afterwards.",
19929
+ "custom.division": "Division",
19930
+ "custom.divisionNew": "\uFF0B New division\u2026",
19931
+ "custom.divisionKey": "Division directory name (lowercase ASCII letters, digits, hyphen)",
19932
+ "custom.divisionLabel": "Division display name (optional)",
19933
+ "cat.hint": "A division groups experts: the official ones come from the built-in roster (read-only, names follow upstream Chinese), while custom ones can be created, renamed and deleted. A custom expert can be filed under any division (including an official name), but its file always lives in your own data directory \u2014 the built-in mirror is never touched.",
19934
+ "cat.key": "Division directory name (lowercase ASCII letters, digits, hyphen)",
19935
+ "cat.label": "Display name (optional)",
19936
+ "cat.create": "Create division",
19937
+ "cat.mine": "Custom divisions",
19938
+ "cat.official": "Official divisions",
19939
+ "cat.officialNote": "Official divisions follow the roster sync \u2014 their names and existence are read-only here.",
19940
+ "cat.empty": "No custom division yet: fill in a directory name and a display name above to create one.",
19941
+ "cat.officialBadge": "Official",
19942
+ "cat.customBadge": "Custom",
19943
+ "cat.count": "{count} ({custom} custom)",
19944
+ "cat.rename": "Rename",
19945
+ "cat.save": "Save",
19946
+ "cat.delete": "Delete",
19947
+ "cat.confirmDelete": "Confirm delete",
19948
+ "custom.save": "Save",
19949
+ "custom.saving": "Saving\u2026",
19950
+ "custom.cancel": "Cancel",
19951
+ "custom.edit": "Edit",
19952
+ "custom.delete": "Delete",
19953
+ "custom.confirmDelete": "Confirm delete",
19954
+ "custom.deleteHint": "Delete \u201C{name}\u201D? Only this custom expert file is removed; this cannot be undone.",
19955
+ "custom.badge": "Custom",
19956
+ "custom.loadingBody": "Loading persona body\u2026",
19878
19957
  "squads.loading": "Loading squads\u2026",
19879
19958
  "squads.count": "{count} squads \xB7 {experts} experts available",
19880
19959
  "squads.new": "New squad",
@@ -19965,6 +20044,11 @@ var CSS = `
19965
20044
  /* \u8868\u5355\u91CC\u7684\u8F93\u5165\u6846\uFF1A.t-team-input \u7684 flex-basis \u662F\u7ED9\u6A2A\u5411\u641C\u7D22\u6761\u7528\u7684\uFF0C\u7AD6\u5411\u5BB9\u5668\u91CC\u4F1A\u628A\u5B83\u6491\u6210 200px \u9AD8 */
19966
20045
  .t-team-field .t-team-input{flex:0 0 auto;width:100%;min-width:0;height:30px}
19967
20046
  .t-team-editor .t-team-input{flex:0 0 auto;height:30px}
20047
+ /* \u81EA\u5EFA\u4E13\u5BB6\u7F16\u8F91\u5668\uFF1A\u6B63\u6587\u662F\u591A\u884C Markdown\uFF0C\u6587\u672C\u6846\u8981\u80FD\u62C9\u9AD8 */
20048
+ .t-team-editor textarea.t-team-input{height:auto;min-height:150px;resize:vertical;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px;line-height:18px}
20049
+ .t-team-editor .t-team-editor-actions{display:flex;gap:8px;justify-content:flex-end}
20050
+ .t-team-badge-custom{display:inline-block;margin-left:6px;padding:0 6px;border-radius:999px;border:1px solid var(--dsw-alias-border-l2);font-size:10px;line-height:16px;vertical-align:middle;color:var(--dsw-alias-label-caption)}
20051
+ .t-team-danger{color:var(--dsw-alias-state-error-primary);border-color:var(--dsw-alias-state-error-primary)}
19968
20052
  /* \u7AD6\u5411 tab \u5BB9\u5668\u91CC\u7684\u641C\u7D22\u6846\u540C\u7406\uFF1A\u8BBE\u7F6E\u9875\u300C\u961F\u4F0D\u300Dtab \u7684\u641C\u7D22\u6846\u66FE\u7ECF\u88AB\u6491\u6210 200px \u9AD8 */
19969
20053
  .t-team-tabbody > .t-team-input{flex:0 0 auto;width:100%;min-width:0;height:30px}
19970
20054
  .t-team-picker{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:10px}
@@ -20019,6 +20103,20 @@ var CSS = `
20019
20103
  .t-team-card-division{font-size:13px;line-height:18px;margin-top:2px;color:var(--dsw-alias-label-secondary)}
20020
20104
  .t-team-card-slug{font-size:11px;line-height:16px;color:var(--dsw-alias-label-tertiary);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
20021
20105
  .t-team-card-desc{grid-column:1/-1;margin:0;min-height:60px;font-size:14px;line-height:20px;color:var(--dsw-alias-label-secondary);display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:3;overflow:hidden}
20106
+ .t-team-cat-list{display:flex;flex-direction:column;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-bg-layer-2);overflow:hidden}
20107
+ .t-team-cat-list[data-scroll="true"]{max-height:300px;overflow-y:auto}
20108
+ .t-team-cat-row{display:flex;flex-direction:row;align-items:center;gap:8px;min-height:28px;padding:3px 10px;border-top:1px solid var(--dsw-alias-border-l2)}
20109
+ .t-team-cat-row:first-child{border-top:0}
20110
+ .t-team-cat-row:hover{background:var(--dsw-alias-interactive-bg-hover)}
20111
+ .t-team-cat-name{flex:0 1 auto;min-width:0;font-size:13px;font-weight:600;line-height:18px;color:var(--dsw-alias-label-primary);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
20112
+ .t-team-cat-key{flex:0 1 auto;min-width:0;font-size:11px;line-height:16px;color:var(--dsw-alias-label-tertiary);font-family:ui-monospace,SFMono-Regular,Menlo,monospace;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
20113
+ .t-team-cat-count{flex:none;margin-left:auto;font-size:12px;line-height:16px;color:var(--dsw-alias-label-secondary);white-space:nowrap}
20114
+ .t-team-cat-actions{flex:none;display:flex;align-items:center;gap:6px}
20115
+ .t-team-cat-row .t-team-input{flex:0 1 180px;min-width:120px;height:26px;padding:2px 8px;font-size:12px}
20116
+ .t-team-cat-row .t-team-chip{flex:none;white-space:nowrap}
20117
+ .t-team-cat-empty{padding:8px 10px;font-size:12px;color:var(--dsw-alias-label-caption)}
20118
+ .t-team-cat-list .t-team-badge{flex:none}
20119
+ .t-team-badge[data-tone="custom"]{color:var(--dsw-alias-label-primary);border:1px solid var(--dsw-alias-border-l2);background:transparent}
20022
20120
  .t-team-switch{position:relative;flex:none;width:28px;height:16px;padding:0;border-radius:999px;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);cursor:pointer;transition:background .15s ease,border-color .15s ease}
20023
20121
  .t-team-switch-knob{position:absolute;top:1px;left:1px;width:12px;height:12px;border-radius:50%;background:var(--dsw-alias-label-tertiary);transition:transform .15s ease,background .15s ease}
20024
20122
  .t-team-switch[data-enabled="true"]{background:var(--dsw-alias-button-primary-fill);border-color:var(--dsw-alias-button-primary-fill)}
@@ -20057,12 +20155,40 @@ var expertSchema = external_exports.object({
20057
20155
  division: external_exports.string(),
20058
20156
  divisionZh: external_exports.string(),
20059
20157
  divisionEn: external_exports.string(),
20060
- conflict: external_exports.boolean().optional()
20158
+ conflict: external_exports.boolean().optional(),
20159
+ /** 用户自建专家:卡片上给编辑/删除入口。 */
20160
+ custom: external_exports.boolean().optional(),
20161
+ /** 自建专家内容指纹:编辑/删除时回传,服务端据此拦并发覆盖。 */
20162
+ hash: external_exports.string().optional()
20061
20163
  });
20062
20164
  var catalogSchema = external_exports.object({
20063
20165
  experts: external_exports.array(expertSchema),
20064
20166
  enabled: external_exports.array(external_exports.string()),
20065
- revision: external_exports.number().int().min(0)
20167
+ revision: external_exports.number().int().min(0),
20168
+ /** 自建专家固定落在这个分类(服务端告知,避免两端各写一份常量)。 */
20169
+ customDivision: external_exports.string().optional(),
20170
+ customDivisionLabel: external_exports.string().optional(),
20171
+ /** 全部分类:官方在前(official=true,名字随包内名册、只读),自建在后(可改名/删除)。 */
20172
+ categories: external_exports.array(external_exports.object({
20173
+ key: external_exports.string(),
20174
+ label: external_exports.string(),
20175
+ official: external_exports.boolean(),
20176
+ count: external_exports.number().int().min(0),
20177
+ customCount: external_exports.number().int().min(0)
20178
+ })).optional()
20179
+ });
20180
+ var customExpertInputSchema = external_exports.object({
20181
+ slug: external_exports.string().min(1).max(128).optional(),
20182
+ name: external_exports.string().min(1).max(200),
20183
+ nameEn: external_exports.string().max(200).optional(),
20184
+ description: external_exports.string().min(1).max(1e3),
20185
+ descriptionEn: external_exports.string().max(1e3).optional(),
20186
+ emoji: external_exports.string().max(16).optional(),
20187
+ body: external_exports.string().min(1).max(6e4),
20188
+ /** 落点分区(自建分区目录名);留空=默认分区。 */
20189
+ division: external_exports.string().max(128).optional(),
20190
+ /** 新建分区时的中文显示名。 */
20191
+ divisionLabel: external_exports.string().max(40).optional()
20066
20192
  });
20067
20193
  var enabledStateSchema = external_exports.object({
20068
20194
  enabled: external_exports.array(external_exports.string()),
@@ -20174,7 +20300,30 @@ var TYPERT_REMOTE = {
20174
20300
  ], "TTeamTeams", teamsSnapshotSchema),
20175
20301
  direct("stopTeam", [
20176
20302
  { name: "teamId", wire: "teamId", source: "json", codec: { mode: "strict", typeSymbol: "string", schema: external_exports.string().min(1).max(128) } }
20177
- ], "TTeamStopResult", stopResultSchema)
20303
+ ], "TTeamStopResult", stopResultSchema),
20304
+ direct("createExpert", [
20305
+ { name: "expert", wire: "expert", source: "json", codec: { mode: "strict", typeSymbol: "TTeamCustomExpertInput", schema: customExpertInputSchema } }
20306
+ ], "TTeamCatalog", catalogSchema),
20307
+ direct("updateExpert", [
20308
+ { name: "slug", wire: "slug", source: "json", codec: { mode: "strict", typeSymbol: "string", schema: external_exports.string().min(1).max(128) } },
20309
+ { name: "expert", wire: "expert", source: "json", codec: { mode: "strict", typeSymbol: "TTeamCustomExpertInput", schema: customExpertInputSchema } },
20310
+ { name: "expectedHash", wire: "expectedHash", source: "json", codec: { mode: "strict", typeSymbol: "string", schema: external_exports.string().max(64) } }
20311
+ ], "TTeamCatalog", catalogSchema),
20312
+ direct("deleteExpert", [
20313
+ { name: "slug", wire: "slug", source: "json", codec: { mode: "strict", typeSymbol: "string", schema: external_exports.string().min(1).max(128) } },
20314
+ { name: "expectedHash", wire: "expectedHash", source: "json", codec: { mode: "strict", typeSymbol: "string", schema: external_exports.string().max(64) } }
20315
+ ], "TTeamCatalog", catalogSchema),
20316
+ direct("createCategory", [
20317
+ { name: "key", wire: "key", source: "json", codec: { mode: "strict", typeSymbol: "string", schema: external_exports.string().min(1).max(128) } },
20318
+ { name: "label", wire: "label", source: "json", codec: { mode: "strict", typeSymbol: "string", schema: external_exports.string().max(40) } }
20319
+ ], "TTeamCatalog", catalogSchema),
20320
+ direct("updateCategory", [
20321
+ { name: "key", wire: "key", source: "json", codec: { mode: "strict", typeSymbol: "string", schema: external_exports.string().min(1).max(128) } },
20322
+ { name: "label", wire: "label", source: "json", codec: { mode: "strict", typeSymbol: "string", schema: external_exports.string().max(40) } }
20323
+ ], "TTeamCatalog", catalogSchema),
20324
+ direct("deleteCategory", [
20325
+ { name: "key", wire: "key", source: "json", codec: { mode: "strict", typeSymbol: "string", schema: external_exports.string().min(1).max(128) } }
20326
+ ], "TTeamCatalog", catalogSchema)
20178
20327
  ]
20179
20328
  };
20180
20329
  var catalogCache = /* @__PURE__ */ new Map();
@@ -20265,6 +20414,8 @@ function SettingsPanel({ t, remote, getActive, refreshCatalog, initialTab, squad
20265
20414
  const [error62, setError] = import_react.default.useState("");
20266
20415
  const [busy, setBusy] = import_react.default.useState(false);
20267
20416
  const [promptView, setPromptView] = import_react.default.useState(null);
20417
+ const [editor, setEditor] = import_react.default.useState(null);
20418
+ const [pendingDelete, setPendingDelete] = import_react.default.useState("");
20268
20419
  const [tab, setTab] = import_react.default.useState(initialTab ?? "experts");
20269
20420
  const reload = import_react.default.useCallback(async (force) => {
20270
20421
  try {
@@ -20315,6 +20466,106 @@ function SettingsPanel({ t, remote, getActive, refreshCatalog, initialTab, squad
20315
20466
  document.addEventListener("keydown", onKey);
20316
20467
  return () => document.removeEventListener("keydown", onKey);
20317
20468
  }, [promptView]);
20469
+ const customDivisionLabel = snapshot?.customDivisionLabel ?? "";
20470
+ const categories = snapshot?.categories ?? [];
20471
+ const divisionOptions = (() => {
20472
+ const base = categories.length > 0 ? categories.map((item) => ({ key: item.key, label: item.label })) : [{ key: snapshot?.customDivision ?? "", label: customDivisionLabel }];
20473
+ const fallback = snapshot?.customDivision ?? "";
20474
+ if (fallback !== "" && !base.some((item) => item.key === fallback)) {
20475
+ base.unshift({ key: fallback, label: snapshot?.customDivisionLabel ?? fallback });
20476
+ }
20477
+ return base;
20478
+ })();
20479
+ const editorDivisionOptions = editor !== null && editor.division !== "" && !divisionOptions.some((item) => item.key === editor.division) ? [{ key: editor.division, label: editor.division }, ...divisionOptions] : divisionOptions;
20480
+ function openEditorNew() {
20481
+ setError("");
20482
+ setEditor({
20483
+ mode: "new",
20484
+ slug: "",
20485
+ hash: "",
20486
+ name: "",
20487
+ nameEn: "",
20488
+ emoji: "\u{1F9E9}",
20489
+ description: "",
20490
+ body: "",
20491
+ division: snapshot?.customDivision ?? "",
20492
+ divisionNew: false,
20493
+ divisionKey: "",
20494
+ divisionLabel: "",
20495
+ loading: false,
20496
+ saving: false,
20497
+ error: ""
20498
+ });
20499
+ }
20500
+ async function openEditorEdit(expert) {
20501
+ setError("");
20502
+ setEditor({
20503
+ mode: "edit",
20504
+ slug: expert.slug,
20505
+ hash: expert.hash ?? "",
20506
+ name: expert.name ?? "",
20507
+ nameEn: expert.nameEn ?? "",
20508
+ emoji: expert.emoji || "\u{1F9E9}",
20509
+ description: expert.description ?? "",
20510
+ body: "",
20511
+ division: expert.division ?? "",
20512
+ divisionNew: false,
20513
+ divisionKey: "",
20514
+ divisionLabel: "",
20515
+ loading: true,
20516
+ saving: false,
20517
+ error: ""
20518
+ });
20519
+ try {
20520
+ const data = unwrap(await remote.getPrompt(expert.slug, expert.division), "getPrompt");
20521
+ setEditor((current) => current === null ? current : { ...current, body: data?.prompt ?? "", loading: false });
20522
+ } catch (cause) {
20523
+ setEditor((current) => current === null ? current : {
20524
+ ...current,
20525
+ loading: false,
20526
+ error: cause instanceof Error ? cause.message : String(cause)
20527
+ });
20528
+ }
20529
+ }
20530
+ function patchEditor(patch) {
20531
+ setEditor((current) => current === null ? current : { ...current, ...patch });
20532
+ }
20533
+ async function saveEditor() {
20534
+ if (editor === null || editor.saving) return;
20535
+ patchEditor({ saving: true, error: "" });
20536
+ const payload = {
20537
+ slug: editor.slug.trim(),
20538
+ name: editor.name.trim(),
20539
+ nameEn: editor.nameEn.trim(),
20540
+ description: editor.description.trim(),
20541
+ emoji: editor.emoji.trim(),
20542
+ body: editor.body,
20543
+ // 新建分区时用的是输入框里的目录名,否则用下拉选中的分区(空=服务端默认分区)。
20544
+ division: (editor.divisionNew ? editor.divisionKey : editor.division).trim(),
20545
+ divisionLabel: editor.divisionNew ? editor.divisionLabel.trim() : ""
20546
+ };
20547
+ try {
20548
+ const result = editor.mode === "new" ? await remote.createExpert(payload) : await remote.updateExpert(editor.slug, payload, editor.hash);
20549
+ unwrap(result, editor.mode === "new" ? "createExpert" : "updateExpert");
20550
+ setEditor(null);
20551
+ await reload(true);
20552
+ } catch (cause) {
20553
+ const message = cause instanceof Error ? cause.message : String(cause);
20554
+ if (message.includes("custom-slug-taken") || message.includes("custom-name-taken")) await reload(true);
20555
+ patchEditor({ saving: false, error: message });
20556
+ }
20557
+ }
20558
+ async function removeExpert(expert) {
20559
+ setError("");
20560
+ try {
20561
+ unwrap(await remote.deleteExpert(expert.slug, expert.hash ?? ""), "deleteExpert");
20562
+ setPendingDelete("");
20563
+ await reload(true);
20564
+ } catch (cause) {
20565
+ setPendingDelete("");
20566
+ setError(cause instanceof Error ? cause.message : String(cause));
20567
+ }
20568
+ }
20318
20569
  const experts = snapshot?.experts ?? [];
20319
20570
  const enabledSet = snapshot !== void 0 ? new Set(snapshot.enabled) : /* @__PURE__ */ new Set();
20320
20571
  const divisions = [];
@@ -20367,7 +20618,7 @@ function SettingsPanel({ t, remote, getActive, refreshCatalog, initialTab, squad
20367
20618
  return /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-settings" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-title" }, t("settings.title")), /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-meta" }, t("settings.loading")), error62 !== "" && /* @__PURE__ */ import_react.default.createElement(import_react.default.Fragment, null, /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-error" }, t("settings.loadFailed", { detail: error62 })), /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-row" }, /* @__PURE__ */ import_react.default.createElement("button", { className: "t-team-chip", type: "button", onClick: () => void reload(true) }, t("settings.retry")))));
20368
20619
  }
20369
20620
  const enabledCount = snapshot.enabled.length;
20370
- return /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-settings" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-head" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-title" }, t("settings.title")), /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-meta" }, t("settings.enabledCount", { enabled: enabledCount, total: experts.length }))), /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-tabs", role: "tablist" }, [["experts", "tab.experts"], ["roster", "tab.roster"]].map(([key, label]) => /* @__PURE__ */ import_react.default.createElement(
20621
+ return /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-settings" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-head" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-title" }, t("settings.title")), /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-meta" }, t("settings.enabledCount", { enabled: enabledCount, total: experts.length }))), /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-tabs", role: "tablist" }, [["experts", "tab.experts"], ["categories", "tab.categories"], ["roster", "tab.roster"]].map(([key, label]) => /* @__PURE__ */ import_react.default.createElement(
20371
20622
  "button",
20372
20623
  {
20373
20624
  className: "t-team-tab",
@@ -20379,7 +20630,7 @@ function SettingsPanel({ t, remote, getActive, refreshCatalog, initialTab, squad
20379
20630
  onClick: () => setTab(key)
20380
20631
  },
20381
20632
  t(label)
20382
- ))), tab === "roster" && /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-tabbody" }, /* @__PURE__ */ import_react.default.createElement(SectionBoundary, { t, reloadKey: tab }, /* @__PURE__ */ import_react.default.createElement("div", null, /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-section-head" }, t("roster.squads")), /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-note" }, t("roster.squadsHint"))), /* @__PURE__ */ import_react.default.createElement(SquadsTab, { t, remote, initialData: squadsPreview, initialDraft: draftPreview }), /* @__PURE__ */ import_react.default.createElement("div", null, /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-section-head" }, t("roster.teams")), /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-note" }, t("roster.teamsHint"))), /* @__PURE__ */ import_react.default.createElement(TeamsTab, { t, remote, initialTeams: teamsPreview }))), /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-tabbody", hidden: tab !== "experts" }, /* @__PURE__ */ import_react.default.createElement(SectionBoundary, { t, reloadKey: tab }, /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-note" }, t("roster.expertsHint")), /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-bar" }, /* @__PURE__ */ import_react.default.createElement(
20633
+ ))), /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-tabbody", hidden: tab !== "categories" }, /* @__PURE__ */ import_react.default.createElement(SectionBoundary, { t, reloadKey: tab }, /* @__PURE__ */ import_react.default.createElement(CategoriesTab, { t, remote, categories, onChanged: () => reload(true) }))), tab === "roster" && /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-tabbody" }, /* @__PURE__ */ import_react.default.createElement(SectionBoundary, { t, reloadKey: tab }, /* @__PURE__ */ import_react.default.createElement("div", null, /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-section-head" }, t("roster.squads")), /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-note" }, t("roster.squadsHint"))), /* @__PURE__ */ import_react.default.createElement(SquadsTab, { t, remote, initialData: squadsPreview, initialDraft: draftPreview }), /* @__PURE__ */ import_react.default.createElement("div", null, /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-section-head" }, t("roster.teams")), /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-note" }, t("roster.teamsHint"))), /* @__PURE__ */ import_react.default.createElement(TeamsTab, { t, remote, initialTeams: teamsPreview }))), /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-tabbody", hidden: tab !== "experts" }, /* @__PURE__ */ import_react.default.createElement(SectionBoundary, { t, reloadKey: tab }, /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-note" }, t("roster.expertsHint")), /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-bar" }, /* @__PURE__ */ import_react.default.createElement(
20383
20634
  "input",
20384
20635
  {
20385
20636
  className: "t-team-input",
@@ -20425,10 +20676,10 @@ function SettingsPanel({ t, remote, getActive, refreshCatalog, initialTab, squad
20425
20676
  }
20426
20677
  },
20427
20678
  t("filter.clear")
20428
- )), /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-meta" }, t("filter.summary", { shown: visible.length, total: experts.length, enabled: enabledSet.size })), error62 !== "" && /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-error" }, error62 === "CONFLICT" ? t("settings.conflictRetry") : t("settings.saveFailed", { detail: error62 })), /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-groups" }, grouped.map((group) => /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-group", key: group.division }, /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-division" }, divisionLabel(group.items[0], active), " \xB7 ", group.items.length), /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-grid" }, group.items.map((expert) => {
20679
+ ), /* @__PURE__ */ import_react.default.createElement("button", { className: "t-team-chip", type: "button", onClick: openEditorNew }, t("custom.new"))), /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-meta" }, t("filter.summary", { shown: visible.length, total: experts.length, enabled: enabledSet.size })), error62 !== "" && /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-error" }, error62 === "CONFLICT" ? t("settings.conflictRetry") : t("settings.saveFailed", { detail: error62 })), /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-groups" }, grouped.map((group) => /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-group", key: group.division }, /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-division" }, divisionLabel(group.items[0], active), " \xB7 ", group.items.length), /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-grid" }, group.items.map((expert) => {
20429
20680
  const on = enabledSet.has(expert.slug);
20430
20681
  const locked = busy || expert.conflict === true;
20431
- return /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-card", "data-enabled": on, key: expert.slug }, /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-card-body" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-avatar" }, expert.emoji || "\u{1F9E9}"), /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-identity" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-card-name" }, displayName(expert, active)), /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-card-division" }, divisionLabel(expert, active)), /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-card-slug" }, expert.slug)), /* @__PURE__ */ import_react.default.createElement("p", { className: "t-team-card-desc" }, displayDescription(expert, active)), expert.conflict === true && /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-conflict", style: { gridColumn: "1/-1" } }, t("settings.conflict"))), /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-card-tools" }, /* @__PURE__ */ import_react.default.createElement(
20682
+ return /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-card", "data-enabled": on, key: expert.slug }, /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-card-body" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-avatar" }, expert.emoji || "\u{1F9E9}"), /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-identity" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-card-name" }, displayName(expert, active), expert.custom === true && /* @__PURE__ */ import_react.default.createElement("span", { className: "t-team-badge-custom" }, t("custom.badge"))), /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-card-division" }, divisionLabel(expert, active)), /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-card-slug" }, expert.slug)), /* @__PURE__ */ import_react.default.createElement("p", { className: "t-team-card-desc" }, displayDescription(expert, active)), expert.conflict === true && /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-conflict", style: { gridColumn: "1/-1" } }, t("settings.conflict"))), /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-card-tools" }, /* @__PURE__ */ import_react.default.createElement(
20432
20683
  "button",
20433
20684
  {
20434
20685
  className: "t-team-icon-btn",
@@ -20438,7 +20689,36 @@ function SettingsPanel({ t, remote, getActive, refreshCatalog, initialTab, squad
20438
20689
  onClick: () => void openPrompt(expert)
20439
20690
  },
20440
20691
  "\u{1F4C4}"
20441
- ), /* @__PURE__ */ import_react.default.createElement(
20692
+ ), expert.custom === true && /* @__PURE__ */ import_react.default.createElement(
20693
+ "button",
20694
+ {
20695
+ className: "t-team-icon-btn",
20696
+ type: "button",
20697
+ title: t("custom.edit"),
20698
+ "aria-label": t("custom.edit"),
20699
+ onClick: () => void openEditorEdit(expert)
20700
+ },
20701
+ "\u270F\uFE0F"
20702
+ ), expert.custom === true && (pendingDelete === expert.slug ? /* @__PURE__ */ import_react.default.createElement(
20703
+ "button",
20704
+ {
20705
+ className: "t-team-chip t-team-danger",
20706
+ type: "button",
20707
+ title: t("custom.deleteHint", { name: displayName(expert, active) }),
20708
+ onClick: () => void removeExpert(expert)
20709
+ },
20710
+ t("custom.confirmDelete")
20711
+ ) : /* @__PURE__ */ import_react.default.createElement(
20712
+ "button",
20713
+ {
20714
+ className: "t-team-icon-btn",
20715
+ type: "button",
20716
+ title: t("custom.delete"),
20717
+ "aria-label": t("custom.delete"),
20718
+ onClick: () => setPendingDelete(expert.slug)
20719
+ },
20720
+ "\u{1F5D1}"
20721
+ )), /* @__PURE__ */ import_react.default.createElement(
20442
20722
  "button",
20443
20723
  {
20444
20724
  className: "t-team-switch",
@@ -20462,6 +20742,82 @@ function SettingsPanel({ t, remote, getActive, refreshCatalog, initialTab, squad
20462
20742
  }
20463
20743
  },
20464
20744
  /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-modal", role: "dialog", "aria-modal": "true", "aria-label": t("card.promptTitle", { name: promptView.name }) }, /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-modal-head" }, /* @__PURE__ */ import_react.default.createElement("span", null, t("card.promptTitle", { name: promptView.name })), /* @__PURE__ */ import_react.default.createElement("button", { className: "t-team-chip", type: "button", onClick: () => setPromptView(null) }, t("card.close"))), /* @__PURE__ */ import_react.default.createElement("pre", { className: "t-team-modal-body" }, promptView.loading ? t("card.promptLoading") : promptView.error !== "" ? t("card.promptFailed", { detail: promptView.error }) : promptView.text === "" ? t("card.promptEmpty") : promptView.text))
20745
+ ), editor !== null && /* @__PURE__ */ import_react.default.createElement(
20746
+ "div",
20747
+ {
20748
+ className: "t-team-mask",
20749
+ onClick: (event) => {
20750
+ if (event.target === event.currentTarget && !editor.saving) setEditor(null);
20751
+ }
20752
+ },
20753
+ /* @__PURE__ */ import_react.default.createElement(
20754
+ "div",
20755
+ {
20756
+ className: "t-team-modal",
20757
+ role: "dialog",
20758
+ "aria-modal": "true",
20759
+ "aria-label": editor.mode === "new" ? t("custom.titleNew") : t("custom.titleEdit", { name: editor.name || editor.slug })
20760
+ },
20761
+ /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-modal-head" }, /* @__PURE__ */ import_react.default.createElement("span", null, editor.mode === "new" ? t("custom.titleNew") : t("custom.titleEdit", { name: editor.name || editor.slug })), /* @__PURE__ */ import_react.default.createElement("button", { className: "t-team-chip", type: "button", disabled: editor.saving, onClick: () => setEditor(null) }, t("custom.cancel"))),
20762
+ /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-editor", style: { padding: 14, overflow: "auto" } }, /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-note" }, t("custom.hint")), /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-field" }, /* @__PURE__ */ import_react.default.createElement("span", null, t("custom.division")), /* @__PURE__ */ import_react.default.createElement(
20763
+ "select",
20764
+ {
20765
+ className: "t-team-select",
20766
+ value: editor.divisionNew ? NEW_DIVISION : editor.division,
20767
+ onChange: (event) => {
20768
+ const value = event.target.value;
20769
+ if (value === NEW_DIVISION) patchEditor({ divisionNew: true, divisionKey: "", divisionLabel: "" });
20770
+ else patchEditor({ divisionNew: false, division: value });
20771
+ }
20772
+ },
20773
+ editorDivisionOptions.map((item) => /* @__PURE__ */ import_react.default.createElement("option", { key: item.key, value: item.key }, `${item.label} \xB7 ${item.key}`)),
20774
+ /* @__PURE__ */ import_react.default.createElement("option", { value: NEW_DIVISION }, t("custom.divisionNew"))
20775
+ ), editor.divisionNew && /* @__PURE__ */ import_react.default.createElement("div", { style: { display: "flex", flexDirection: "column", gap: 6 } }, /* @__PURE__ */ import_react.default.createElement(
20776
+ "input",
20777
+ {
20778
+ className: "t-team-input",
20779
+ placeholder: t("custom.divisionKey"),
20780
+ spellCheck: false,
20781
+ value: editor.divisionKey,
20782
+ onChange: (event) => patchEditor({ divisionKey: event.target.value })
20783
+ }
20784
+ ), /* @__PURE__ */ import_react.default.createElement(
20785
+ "input",
20786
+ {
20787
+ className: "t-team-input",
20788
+ placeholder: t("custom.divisionLabel"),
20789
+ value: editor.divisionLabel,
20790
+ onChange: (event) => patchEditor({ divisionLabel: event.target.value })
20791
+ }
20792
+ ))), /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-field" }, /* @__PURE__ */ import_react.default.createElement("span", null, t("custom.slug")), /* @__PURE__ */ import_react.default.createElement(
20793
+ "input",
20794
+ {
20795
+ className: "t-team-input",
20796
+ value: editor.slug,
20797
+ readOnly: editor.mode === "edit",
20798
+ spellCheck: false,
20799
+ onChange: (event) => patchEditor({ slug: event.target.value })
20800
+ }
20801
+ )), /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-field" }, /* @__PURE__ */ import_react.default.createElement("span", null, t("custom.name")), /* @__PURE__ */ import_react.default.createElement("input", { className: "t-team-input", value: editor.name, onChange: (event) => patchEditor({ name: event.target.value }) })), /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-field" }, /* @__PURE__ */ import_react.default.createElement("span", null, t("custom.nameEn")), /* @__PURE__ */ import_react.default.createElement("input", { className: "t-team-input", value: editor.nameEn, onChange: (event) => patchEditor({ nameEn: event.target.value }) })), /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-field" }, /* @__PURE__ */ import_react.default.createElement("span", null, t("custom.emoji")), /* @__PURE__ */ import_react.default.createElement("input", { className: "t-team-input", maxLength: 8, value: editor.emoji, onChange: (event) => patchEditor({ emoji: event.target.value }) })), /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-field" }, /* @__PURE__ */ import_react.default.createElement("span", null, t("custom.description")), /* @__PURE__ */ import_react.default.createElement("input", { className: "t-team-input", value: editor.description, onChange: (event) => patchEditor({ description: event.target.value }) })), /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-field" }, /* @__PURE__ */ import_react.default.createElement("span", null, t("custom.body")), /* @__PURE__ */ import_react.default.createElement(
20802
+ "textarea",
20803
+ {
20804
+ className: "t-team-input",
20805
+ value: editor.body,
20806
+ disabled: editor.loading,
20807
+ onChange: (event) => patchEditor({ body: event.target.value })
20808
+ }
20809
+ )), editor.loading && /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-meta" }, t("custom.loadingBody")), editor.error !== "" && /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-error" }, editor.error), /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-editor-actions" }, /* @__PURE__ */ import_react.default.createElement("button", { className: "t-team-chip", type: "button", disabled: editor.saving, onClick: () => setEditor(null) }, t("custom.cancel")), /* @__PURE__ */ import_react.default.createElement(
20810
+ "button",
20811
+ {
20812
+ className: "t-team-chip",
20813
+ type: "button",
20814
+ "data-active": "true",
20815
+ disabled: editor.saving || editor.loading,
20816
+ onClick: () => void saveEditor()
20817
+ },
20818
+ editor.saving ? t("custom.saving") : t("custom.save")
20819
+ )))
20820
+ )
20465
20821
  ));
20466
20822
  }
20467
20823
  function squadToInput(squad) {
@@ -20474,6 +20830,98 @@ function squadToInput(squad) {
20474
20830
  members: (squad.members ?? []).map((member) => typeof member === "string" ? member : member.slug).filter((slug) => slug !== "")
20475
20831
  };
20476
20832
  }
20833
+ function CategoriesTab({ t, remote, categories, onChanged }) {
20834
+ const [key, setKey] = import_react.default.useState("");
20835
+ const [label, setLabel] = import_react.default.useState("");
20836
+ const [editing, setEditing] = import_react.default.useState(null);
20837
+ const [pendingDelete, setPendingDelete] = import_react.default.useState("");
20838
+ const [error62, setError] = import_react.default.useState("");
20839
+ const [busy, setBusy] = import_react.default.useState(false);
20840
+ const list = categories ?? [];
20841
+ const mine = list.filter((item) => !item.official);
20842
+ const official = list.filter((item) => item.official);
20843
+ async function run(action) {
20844
+ setBusy(true);
20845
+ setError("");
20846
+ try {
20847
+ unwrap(await action(), "category");
20848
+ setPendingDelete("");
20849
+ setEditing(null);
20850
+ setKey("");
20851
+ setLabel("");
20852
+ await onChanged();
20853
+ } catch (cause) {
20854
+ setError(cause instanceof Error ? cause.message : String(cause));
20855
+ } finally {
20856
+ setBusy(false);
20857
+ }
20858
+ }
20859
+ const row = (item) => /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-cat-row", key: item.key }, /* @__PURE__ */ import_react.default.createElement("span", { className: "t-team-cat-name", title: item.label }, item.label), /* @__PURE__ */ import_react.default.createElement("span", { className: "t-team-badge", "data-tone": item.official ? void 0 : "custom" }, item.official ? t("cat.officialBadge") : t("cat.customBadge")), /* @__PURE__ */ import_react.default.createElement("span", { className: "t-team-cat-key", title: item.key }, item.key), /* @__PURE__ */ import_react.default.createElement("span", { className: "t-team-cat-count" }, t("cat.count", { count: item.count, custom: item.customCount })), !item.official && /* @__PURE__ */ import_react.default.createElement("span", { className: "t-team-cat-actions" }, editing !== null && editing.key === item.key ? /* @__PURE__ */ import_react.default.createElement(import_react.default.Fragment, null, /* @__PURE__ */ import_react.default.createElement(
20860
+ "input",
20861
+ {
20862
+ className: "t-team-input",
20863
+ value: editing.label,
20864
+ placeholder: t("cat.label"),
20865
+ onChange: (event) => setEditing({ key: item.key, label: event.target.value })
20866
+ }
20867
+ ), /* @__PURE__ */ import_react.default.createElement(
20868
+ "button",
20869
+ {
20870
+ className: "t-team-chip",
20871
+ type: "button",
20872
+ disabled: busy,
20873
+ onClick: () => void run(() => remote.updateCategory(item.key, editing.label.trim()))
20874
+ },
20875
+ t("cat.save")
20876
+ ), /* @__PURE__ */ import_react.default.createElement("button", { className: "t-team-chip", type: "button", disabled: busy, onClick: () => setEditing(null) }, t("custom.cancel"))) : /* @__PURE__ */ import_react.default.createElement(import_react.default.Fragment, null, /* @__PURE__ */ import_react.default.createElement(
20877
+ "button",
20878
+ {
20879
+ className: "t-team-chip",
20880
+ type: "button",
20881
+ disabled: busy,
20882
+ onClick: () => setEditing({ key: item.key, label: item.label })
20883
+ },
20884
+ t("cat.rename")
20885
+ ), pendingDelete === item.key ? /* @__PURE__ */ import_react.default.createElement(import_react.default.Fragment, null, /* @__PURE__ */ import_react.default.createElement(
20886
+ "button",
20887
+ {
20888
+ className: "t-team-chip",
20889
+ type: "button",
20890
+ "data-active": "true",
20891
+ disabled: busy,
20892
+ onClick: () => void run(() => remote.deleteCategory(item.key))
20893
+ },
20894
+ t("cat.confirmDelete")
20895
+ ), /* @__PURE__ */ import_react.default.createElement("button", { className: "t-team-chip", type: "button", disabled: busy, onClick: () => setPendingDelete("") }, t("custom.cancel"))) : /* @__PURE__ */ import_react.default.createElement("button", { className: "t-team-chip", type: "button", disabled: busy, onClick: () => setPendingDelete(item.key) }, t("cat.delete")))));
20896
+ return /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-tabbody" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-note" }, t("cat.hint")), /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-bar" }, /* @__PURE__ */ import_react.default.createElement(
20897
+ "input",
20898
+ {
20899
+ className: "t-team-input",
20900
+ placeholder: t("cat.key"),
20901
+ spellCheck: false,
20902
+ value: key,
20903
+ onChange: (event) => setKey(event.target.value)
20904
+ }
20905
+ ), /* @__PURE__ */ import_react.default.createElement(
20906
+ "input",
20907
+ {
20908
+ className: "t-team-input",
20909
+ placeholder: t("cat.label"),
20910
+ value: label,
20911
+ onChange: (event) => setLabel(event.target.value)
20912
+ }
20913
+ ), /* @__PURE__ */ import_react.default.createElement(
20914
+ "button",
20915
+ {
20916
+ className: "t-team-chip",
20917
+ type: "button",
20918
+ "data-active": "true",
20919
+ disabled: busy || key.trim() === "",
20920
+ onClick: () => void run(() => remote.createCategory(key.trim(), label.trim()))
20921
+ },
20922
+ t("cat.create")
20923
+ )), error62 !== "" && /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-error" }, error62), /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-section-head" }, t("cat.mine")), /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-cat-list" }, mine.length === 0 ? /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-cat-empty" }, t("cat.empty")) : mine.map(row)), /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-section-head" }, t("cat.official")), /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-meta" }, t("cat.officialNote")), /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-cat-list", "data-scroll": "true" }, official.map(row)));
20924
+ }
20477
20925
  function SquadsTab({ t, remote, initialData, initialDraft }) {
20478
20926
  const [data, setData] = import_react.default.useState(initialData ?? null);
20479
20927
  const [draft, setDraft] = import_react.default.useState(initialDraft ?? null);