@arcblock/did-connect-service 4.1.12 → 4.1.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. package/dist/_generated/asset-bytes.js +1 -1
  2. package/dist/_generated/asset-manifest.js +4 -4
  3. package/dist/_generated/login-bundle-string.d.ts +1 -1
  4. package/dist/_generated/login-bundle-string.d.ts.map +1 -1
  5. package/dist/_generated/login-bundle-string.js +1 -1
  6. package/dist/_generated/login-bundle-string.js.map +1 -1
  7. package/dist/access/change-role.d.ts +48 -0
  8. package/dist/access/change-role.d.ts.map +1 -0
  9. package/dist/access/change-role.js +52 -0
  10. package/dist/access/change-role.js.map +1 -0
  11. package/dist/assets/{admin-core.cbbdbe6d.js → admin-core.a63bbc69.js} +16 -17
  12. package/dist/assets/{admin-extra.35b91f98.js → admin-extra.5b309e5e.js} +8 -4
  13. package/dist/assets/{admin.c26bb17a.css → admin.dfeedbe4.css} +10 -0
  14. package/dist/assets/login.76c483c2.js +720 -0
  15. package/dist/blocklet-service.d.ts.map +1 -1
  16. package/dist/blocklet-service.js +5 -1
  17. package/dist/blocklet-service.js.map +1 -1
  18. package/dist/handler-errors.d.ts +118 -0
  19. package/dist/handler-errors.d.ts.map +1 -0
  20. package/dist/handler-errors.js +38 -0
  21. package/dist/handler-errors.js.map +1 -0
  22. package/dist/handlers/access-key-connect-handler.d.ts +3 -0
  23. package/dist/handlers/access-key-connect-handler.d.ts.map +1 -1
  24. package/dist/handlers/access-key-connect-handler.js +7 -1
  25. package/dist/handlers/access-key-connect-handler.js.map +1 -1
  26. package/dist/handlers/admin-instance-handler.d.ts.map +1 -1
  27. package/dist/handlers/admin-instance-handler.js +2 -1
  28. package/dist/handlers/admin-instance-handler.js.map +1 -1
  29. package/dist/handlers/auth-handler.d.ts.map +1 -1
  30. package/dist/handlers/auth-handler.js +33 -9
  31. package/dist/handlers/auth-handler.js.map +1 -1
  32. package/dist/handlers/branding-handler.d.ts.map +1 -1
  33. package/dist/handlers/branding-handler.js +12 -9
  34. package/dist/handlers/branding-handler.js.map +1 -1
  35. package/dist/handlers/did-connect-handler.d.ts.map +1 -1
  36. package/dist/handlers/did-connect-handler.js +6 -5
  37. package/dist/handlers/did-connect-handler.js.map +1 -1
  38. package/dist/handlers/email-login-handler.d.ts +1 -1
  39. package/dist/handlers/email-login-handler.d.ts.map +1 -1
  40. package/dist/handlers/email-login-handler.js +63 -12
  41. package/dist/handlers/email-login-handler.js.map +1 -1
  42. package/dist/handlers/federation-admin-handler.d.ts +2 -0
  43. package/dist/handlers/federation-admin-handler.d.ts.map +1 -1
  44. package/dist/handlers/federation-admin-handler.js +105 -51
  45. package/dist/handlers/federation-admin-handler.js.map +1 -1
  46. package/dist/handlers/oauth-handler.d.ts.map +1 -1
  47. package/dist/handlers/oauth-handler.js +23 -12
  48. package/dist/handlers/oauth-handler.js.map +1 -1
  49. package/dist/handlers/passkey-handler.d.ts.map +1 -1
  50. package/dist/handlers/passkey-handler.js +5 -4
  51. package/dist/handlers/passkey-handler.js.map +1 -1
  52. package/dist/handlers/team-handler.d.ts.map +1 -1
  53. package/dist/handlers/team-handler.js +19 -19
  54. package/dist/handlers/team-handler.js.map +1 -1
  55. package/dist/identity/refresh-tokens.d.ts +21 -0
  56. package/dist/identity/refresh-tokens.d.ts.map +1 -0
  57. package/dist/identity/refresh-tokens.js +45 -0
  58. package/dist/identity/refresh-tokens.js.map +1 -0
  59. package/dist/identity/session-context.d.ts +12 -0
  60. package/dist/identity/session-context.d.ts.map +1 -1
  61. package/dist/identity/session-context.js +12 -0
  62. package/dist/identity/session-context.js.map +1 -1
  63. package/dist/identity/session-tokens.d.ts +15 -0
  64. package/dist/identity/session-tokens.d.ts.map +1 -0
  65. package/dist/identity/session-tokens.js +15 -0
  66. package/dist/identity/session-tokens.js.map +1 -0
  67. package/dist/locale.d.ts +1 -1
  68. package/dist/locale.d.ts.map +1 -1
  69. package/dist/og/templates.d.ts.map +1 -1
  70. package/dist/og/templates.js +33 -4
  71. package/dist/og/templates.js.map +1 -1
  72. package/dist/pages/admin/i18n.d.ts.map +1 -1
  73. package/dist/pages/admin/i18n.js +33 -9
  74. package/dist/pages/admin/i18n.js.map +1 -1
  75. package/dist/pages/admin/scripts/api-client.d.ts +1 -1
  76. package/dist/pages/admin/scripts/api-client.d.ts.map +1 -1
  77. package/dist/pages/admin/scripts/api-client.js +4 -4
  78. package/dist/pages/admin/scripts/utils.d.ts +1 -1
  79. package/dist/pages/admin/scripts/utils.d.ts.map +1 -1
  80. package/dist/pages/admin/scripts/utils.js +7 -8
  81. package/dist/pages/admin/scripts/utils.js.map +1 -1
  82. package/dist/pages/admin/styles.d.ts +1 -1
  83. package/dist/pages/admin/styles.d.ts.map +1 -1
  84. package/dist/pages/admin/styles.js +10 -0
  85. package/dist/pages/admin/styles.js.map +1 -1
  86. package/dist/pages/admin/tab-access-keys.d.ts +1 -1
  87. package/dist/pages/admin/tab-access-keys.d.ts.map +1 -1
  88. package/dist/pages/admin/tab-access-keys.js +4 -4
  89. package/dist/pages/admin/tab-invitations.d.ts +1 -1
  90. package/dist/pages/admin/tab-invitations.d.ts.map +1 -1
  91. package/dist/pages/admin/tab-invitations.js +2 -2
  92. package/dist/pages/admin/tab-members.d.ts +1 -1
  93. package/dist/pages/admin/tab-members.d.ts.map +1 -1
  94. package/dist/pages/admin/tab-members.js +7 -3
  95. package/dist/pages/admin/tab-members.js.map +1 -1
  96. package/dist/pages/admin/tab-profile-accounts.js +1 -1
  97. package/dist/pages/admin-instances-page.d.ts +1 -1
  98. package/dist/pages/admin-instances-page.d.ts.map +1 -1
  99. package/dist/pages/admin-instances-page.js +195 -65
  100. package/dist/pages/admin-instances-page.js.map +1 -1
  101. package/dist/pages/gen-access-key-page.d.ts +1 -1
  102. package/dist/pages/gen-access-key-page.d.ts.map +1 -1
  103. package/dist/pages/gen-access-key-page.js +163 -35
  104. package/dist/pages/gen-access-key-page.js.map +1 -1
  105. package/dist/pages/homepage.d.ts.map +1 -1
  106. package/dist/pages/homepage.js +0 -4
  107. package/dist/pages/homepage.js.map +1 -1
  108. package/dist/pages/invite-page.d.ts +1 -1
  109. package/dist/pages/invite-page.d.ts.map +1 -1
  110. package/dist/pages/invite-page.js +91 -21
  111. package/dist/pages/invite-page.js.map +1 -1
  112. package/dist/pages/oauth-callback-page.d.ts.map +1 -1
  113. package/dist/pages/oauth-callback-page.js +7 -3
  114. package/dist/pages/oauth-callback-page.js.map +1 -1
  115. package/dist/store/d1-store.d.ts +55 -1
  116. package/dist/store/d1-store.d.ts.map +1 -1
  117. package/dist/store/d1-store.js +162 -9
  118. package/dist/store/d1-store.js.map +1 -1
  119. package/migrations/0011_refresh_tokens.sql +23 -0
  120. package/package.json +7 -7
  121. package/dist/assets/admin-core.a13c2729.js +0 -1461
  122. package/dist/assets/admin-core.c0b5af61.js +0 -1393
  123. package/dist/assets/admin-core.e83fd379.js +0 -1397
  124. package/dist/assets/admin-extra.7ca9c16b.js +0 -2529
  125. package/dist/assets/admin-extra.841b2bbe.js +0 -2530
  126. package/dist/assets/admin-extra.afcb3651.js +0 -2528
  127. package/dist/assets/login.d3f05790.js +0 -720
@@ -90,7 +90,7 @@ function renderInvitationsTable(invitations, paging) {
90
90
  var uses = inv.useCount + " / " + inv.maxUses;
91
91
  if (inv.useCount >= inv.maxUses) uses += ' <span class="badge badge-blocked">' + __t("invitations.closed") + '</span>';
92
92
  var expired = new Date(inv.expireAt) < new Date();
93
- var expireText = expired ? '<span class="badge badge-expired">expired</span>' : '<span class="cell-date">' + escapeHtml(relativeTime(inv.expireAt)) + '</span>';
93
+ var expireText = expired ? '<span class="badge badge-expired">' + __t("common.expired") + '</span>' : '<span class="cell-date">' + escapeHtml(relativeTime(inv.expireAt)) + '</span>';
94
94
  var isActive = !expired && inv.status !== "closed" && inv.useCount < inv.maxUses;
95
95
  var inviter = inv.inviterName ? escapeHtml(inv.inviterName) : '<span class="text-muted">—</span>';
96
96
 
@@ -149,7 +149,7 @@ async function deleteInvitation(id) {
149
149
  function initInviteRoleOptions() {
150
150
  var select = document.getElementById("invite-role");
151
151
  if (__caller.role === "owner") {
152
- select.innerHTML = '<option value="guest">Guest</option><option value="member">Member</option><option value="admin">Admin</option>';
152
+ select.innerHTML = '<option value="guest">' + __t("common.guest") + '</option><option value="member">' + __t("common.member") + '</option><option value="admin">' + __t("common.admin") + '</option>';
153
153
  }
154
154
  }
155
155
 
@@ -3,6 +3,6 @@
3
3
  */
4
4
  type TFn = (key: string, params?: Record<string, string>) => string;
5
5
  export declare function membersTabHtml(t: TFn): string;
6
- export declare const MEMBERS_TAB_SCRIPT = "\nvar membersPage = 1;\nvar membersPageSize = 20;\nvar memberSearchTimeout = null;\nvar currentMembersList = [];\nvar selectedMember = null;\nvar __membersAggregated = false;\n// Sort state: empty string = default (server decides + self-first on client).\n// Three-state click cycle: (none) \u2192 desc \u2192 asc \u2192 (none).\n// Persisted in location.search as sortBy/sortOrder so it survives refresh + share.\nvar membersSortBy = \"\";\nvar membersSortOrder = \"\";\nvar MEMBERS_SORT_VALID = [\"name\", \"role\", \"status\", \"source\", \"joined\"];\n\n/** Populate module sort state from the current URL query string. Idempotent. */\nfunction readMembersSortFromURL() {\n membersSortBy = \"\";\n membersSortOrder = \"\";\n var params = new URLSearchParams(location.search);\n var by = params.get(\"sortBy\");\n var order = params.get(\"sortOrder\");\n if (by && MEMBERS_SORT_VALID.indexOf(by) >= 0) {\n membersSortBy = by;\n membersSortOrder = (order === \"asc\" || order === \"desc\") ? order : \"desc\";\n }\n}\n\n/** Write current sort state back to location.search without reloading. */\nfunction writeMembersSortToURL() {\n var params = new URLSearchParams(location.search);\n if (membersSortBy) {\n params.set(\"sortBy\", membersSortBy);\n params.set(\"sortOrder\", membersSortOrder || \"desc\");\n } else {\n params.delete(\"sortBy\");\n params.delete(\"sortOrder\");\n }\n var qs = params.toString();\n var newUrl = location.pathname + (qs ? \"?\" + qs : \"\") + location.hash;\n history.replaceState(null, \"\", newUrl);\n}\n\n/** Cycle sort state for a column key and reload. */\nfunction toggleSort(key) {\n if (membersSortBy !== key) {\n membersSortBy = key;\n membersSortOrder = \"desc\";\n } else if (membersSortOrder === \"desc\") {\n membersSortOrder = \"asc\";\n } else {\n // Third click \u2014 back to default.\n membersSortBy = \"\";\n membersSortOrder = \"\";\n }\n writeMembersSortToURL();\n loadMembers(1);\n}\n\n/** Render a <th> with click-to-sort behavior. */\nfunction sortableTh(key, label, extraClass) {\n var isActive = membersSortBy === key;\n var arrow;\n if (isActive) {\n arrow = membersSortOrder === \"asc\"\n ? '<span class=\"sort-arrow sort-arrow-active\">\\u2191</span>'\n : '<span class=\"sort-arrow sort-arrow-active\">\\u2193</span>';\n } else {\n arrow = '<span class=\"sort-arrow\">\\u2195</span>';\n }\n var cls = \"sortable\" + (isActive ? \" sort-active\" : \"\") + (extraClass ? \" \" + extraClass : \"\");\n return '<th class=\"' + cls + '\" onclick=\"toggleSort(\\'' + key + '\\')\">' + label + arrow + '</th>';\n}\n\nfunction providerLabel(sp) {\n if (!sp) return \"\\u2014\";\n var key = \"members.providers.\" + sp;\n var val = __t(key);\n return val !== key ? val : sp;\n}\n\nfunction debouncedMemberSearch() {\n clearTimeout(memberSearchTimeout);\n memberSearchTimeout = setTimeout(function() { loadMembers(1); }, 300);\n}\n\n// Load registered instances for the filter dropdown (system admin only)\nvar __instanceNames = {};\nvar __instanceFilterInitialized = false;\nasync function initInstanceFilter() {\n if (__instanceFilterInitialized) return;\n try {\n // Best-effort: this endpoint is system-admin-only. Instance-scoped owners/admins\n // get 403 here, which is expected \u2014 fail silently (no error toast).\n var data = await api(\"GET\", \"/../admin/instances\", undefined, { silent: true });\n if (data.ok && data.instances && data.instances.length > 0) {\n __instanceFilterInitialized = true;\n var sel = document.getElementById(\"members-instance-filter\");\n data.instances.forEach(function(inst) {\n __instanceNames[inst.instanceDid] = inst.appName || inst.instanceDid.substring(0, 16);\n var opt = document.createElement(\"option\");\n opt.value = inst.instanceDid;\n opt.textContent = __instanceNames[inst.instanceDid];\n sel.appendChild(opt);\n });\n sel.style.display = \"\";\n }\n } catch(e) { /* not admin or no instances */ }\n}\n\n// Also support specific instance filter via query param\n// When ?instance=z1xxx, team handler needs to use that instanceDid\n\nasync function loadMembers(page) {\n membersPage = page || 1;\n var instanceFilter = document.getElementById(\"members-instance-filter\").value;\n var qs = \"?page=\" + membersPage + \"&pageSize=\" + membersPageSize;\n // System view (\"\") \u2192 use defaultInstanceDid so we query membership, not global users\n if (instanceFilter === \"\" && __pageData.instanceDid) {\n qs += \"&instance=\" + encodeURIComponent(__pageData.instanceDid);\n } else if (instanceFilter) {\n qs += \"&instance=\" + encodeURIComponent(instanceFilter);\n }\n var search = document.getElementById(\"members-search\").value.trim();\n if (search) qs += \"&search=\" + encodeURIComponent(search);\n var role = document.getElementById(\"members-role-filter\").value;\n if (role) qs += \"&role=\" + encodeURIComponent(role);\n var status = document.getElementById(\"members-status-filter\").value;\n if (status !== \"\") qs += \"&approved=\" + status;\n var source = document.getElementById(\"members-source-filter\").value;\n if (source) qs += \"&sourceProvider=\" + encodeURIComponent(source);\n if (membersSortBy) {\n qs += \"&sortBy=\" + encodeURIComponent(membersSortBy);\n qs += \"&sortOrder=\" + encodeURIComponent(membersSortOrder || \"desc\");\n }\n\n document.getElementById(\"members-table-wrap\").innerHTML = skeletonTable(8, 6);\n document.getElementById(\"members-pagination\").innerHTML = \"\";\n var data = await api(\"GET\", \"/members\" + qs);\n if (!data.ok) {\n document.getElementById(\"members-table-wrap\").innerHTML = '<div class=\"empty-state\"><div class=\"empty-state-icon\"><svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><path d=\"M12 8v4m0 4h.01\"/></svg></div><div class=\"empty-state-title\">' + __t(\"members.loadFailed\") + '</div></div>';\n return;\n }\n __membersAggregated = !!data.aggregated;\n // Only pin the caller to the top when using the default (server) order.\n // Under explicit sort, honor the user's intent without re-shuffling.\n if (membersSortBy) {\n currentMembersList = data.users || [];\n } else {\n currentMembersList = (data.users || []).slice().sort(function(a, b) {\n if (a.did === __caller.did) return -1;\n if (b.did === __caller.did) return 1;\n return (a.createdAt || 0) - (b.createdAt || 0);\n });\n }\n renderMembersTable(currentMembersList, data.paging);\n updateTransferSection(data.users);\n}\n\nfunction renderMembersTable(users, paging) {\n var wrap = document.getElementById(\"members-table-wrap\");\n if (!users || users.length === 0) {\n wrap.innerHTML = '<div class=\"empty-state\"><div class=\"empty-state-icon\"><svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2\"/><circle cx=\"9\" cy=\"7\" r=\"4\"/></svg></div><div class=\"empty-state-title\">' + __t(\"members.noMembers\") + '</div><div class=\"empty-state-desc\">' + __t(\"members.noMembersHint\") + '</div></div>';\n document.getElementById(\"members-pagination\").innerHTML = \"\";\n return;\n }\n\n var instanceSel = document.getElementById(\"members-instance-filter\");\n var instanceVisible = instanceSel && instanceSel.style.display !== 'none';\n var showInstance = instanceVisible && instanceSel.value === \"all\";\n var html = '<table class=\"table\"><thead><tr>';\n html += sortableTh(\"name\", __t(\"members.col.member\"));\n html += sortableTh(\"role\", __t(\"members.col.role\"));\n if (showInstance) html += '<th>' + __t(\"members.col.instance\") + '</th>';\n html += sortableTh(\"source\", __t(\"members.col.source\"), \"col-hide-sm\");\n html += sortableTh(\"status\", __t(\"members.col.status\"), \"col-hide-sm\");\n html += sortableTh(\"joined\", __t(\"members.col.joined\"), \"col-hide-md\");\n // Registered/Last Login From columns removed \u2014 domain info not meaningful in Worker context\n html += '<th></th></tr></thead><tbody>';\n users.forEach(function(u) {\n var userDid = u.did || u.user_did || '';\n var av = renderAvatar(u.avatar, u.fullName, \"sm\");\n var name = u.fullName ? escapeHtml(u.fullName) : '<span class=\"text-muted\">(unnamed)</span>';\n var did = '<did-address did=\"' + escapeHtml(userDid) + '\" compact></did-address>';\n var roleBadge = '<span class=\"badge badge-' + (u.role || \"guest\") + '\">' + __t(\"common.\" + (u.role || \"guest\")) + '</span>';\n var statusBadge = u.approved !== undefined ? (u.approved ? '<span class=\"badge badge-active\">' + __t(\"common.active\") + '</span>' : '<span class=\"badge badge-blocked\">' + __t(\"common.blocked\") + '</span>') : '<span class=\"text-muted\">\u2014</span>';\n var joined = (u.createdAt || u.joined_at) ? '<span class=\"cell-date\">' + escapeHtml(relativeTime(u.createdAt || u.joined_at)) + '</span>' : '<span class=\"text-muted\">\u2014</span>';\n html += '<tr style=\"cursor:pointer\" onclick=\"openMemberDetail(\\'' + escapeHtml(userDid) + '\\')\">';\n html += '<td><div class=\"member-identity\">' + av + '<div><div class=\"member-name\">' + name + '</div><div class=\"member-email\">' + did + '</div></div></div></td>';\n html += '<td>' + roleBadge + '</td>';\n if (showInstance) {\n if (__membersAggregated && u.instance_count > 1) {\n html += '<td><a href=\"javascript:void(0)\" onclick=\"event.stopPropagation();openMemberDetail(\\'' + escapeHtml(userDid) + '\\')\" style=\"color:var(--blue);font-size:14px;text-decoration:none\">' + u.instance_count + ' ' + __t(\"members.instances\") + '</a></td>';\n } else {\n var instDid = (__membersAggregated && u.instances && u.instances.length === 1) ? u.instances[0].instance_did : (u.instance_did || '');\n var instName = __instanceNames[instDid] || '';\n if (!instName) instName = instDid ? __t(\"members.currentInstance\") : '\u2014';\n html += '<td><div>' + escapeHtml(instName);\n if (instDid) html += '<div style=\"font-size:11px;color:var(--text-secondary);margin-top:2px\" onclick=\"event.stopPropagation()\"><did-address did=\"' + escapeHtml(instDid) + '\" compact></did-address></div>';\n html += '</div></td>';\n }\n }\n var sourceBadge = '<span class=\"text-muted\">' + escapeHtml(providerLabel(u.sourceProvider)) + '</span>';\n html += '<td class=\"col-hide-sm\">' + sourceBadge + '</td>';\n html += '<td class=\"col-hide-sm\">' + statusBadge + '</td>';\n html += '<td class=\"col-hide-md\">' + joined + '</td>';\n // Actions: only for current instance members, not cross-instance\n var canOperate = !instanceVisible || (instanceVisible && instanceSel.value === '');\n // In aggregated mode, only allow actions on users whose instance matches current system\n if (__membersAggregated) canOperate = false;\n html += '<td class=\"action-cell\">' + (canOperate ? renderMemberActions(u) : '') + '</td>';\n html += '</tr>';\n });\n html += '</tbody></table>';\n wrap.innerHTML = html;\n\n renderPagination(\"members-pagination\", paging, loadMembers);\n}\n\nfunction renderMemberActions(u) {\n var isOwner = __caller.role === \"owner\";\n var isAdmin = __caller.role === \"admin\";\n var isSelf = u.did === __caller.did;\n var ROLE_LEVEL = { owner: 3, admin: 2, member: 1, guest: 0 };\n var callerLevel = ROLE_LEVEL[__caller.role] || 0;\n var targetLevel = ROLE_LEVEL[u.role] || 0;\n // Can only act on members with lower privilege\n var canManage = !isSelf && targetLevel < callerLevel;\n var items = [];\n\n // Change role: can manage lower-level members\n if (canManage) {\n items.push('<button class=\"action-menu-item\" onclick=\"event.stopPropagation();openChangeRoleForDid(\\'' + escapeHtml(u.did) + '\\')\">' + __t(\"members.changeRole\") + '</button>');\n }\n if (canManage && u.approved) {\n items.push('<button class=\"action-menu-item\" onclick=\"event.stopPropagation();blockMember(\\'' + escapeHtml(u.did) + '\\')\">' + __t(\"members.block\") + '</button>');\n }\n if (canManage && !u.approved) {\n items.push('<button class=\"action-menu-item\" onclick=\"event.stopPropagation();unblockMember(\\'' + escapeHtml(u.did) + '\\')\">' + __t(\"members.unblock\") + '</button>');\n }\n if (items.length > 0 && canManage) {\n items.push('<div class=\"action-menu-sep\"></div>');\n }\n if (canManage) {\n items.push('<button class=\"action-menu-item danger\" onclick=\"event.stopPropagation();removeMember(\\'' + escapeHtml(u.did) + '\\')\">' + __t(\"members.remove\") + '</button>');\n }\n\n if (items.length === 0) return \"\";\n return '<button class=\"action-trigger\" onclick=\"event.stopPropagation();toggleActionMenu(this)\">\u22EF</button><div class=\"action-menu\">' + items.join(\"\") + '</div>';\n}\n\nfunction toggleActionMenu(trigger) {\n var menu = trigger.nextElementSibling;\n var wasOpen = menu.classList.contains(\"open\");\n // Close all menus first\n document.querySelectorAll(\".action-menu.open\").forEach(function(m) { m.classList.remove(\"open\"); });\n if (!wasOpen) menu.classList.toggle(\"open\");\n}\n\nasync function openMemberDetail(did) {\n var u = currentMembersList.find(function(m) { return m.did === did; });\n if (!u) return;\n selectedMember = u;\n\n // Fetch full member info (with connectedAccounts) from API.\n // Falls back to in-memory list data if the request fails.\n var detail = u;\n try {\n var resp = await api(\"GET\", \"/members/\" + encodeURIComponent(did));\n if (resp && resp.ok && resp.user) {\n // Merge: in-memory list has fields the API may omit (instances, passkeyCount in some paths).\n detail = Object.assign({}, u, resp.user);\n selectedMember = detail;\n }\n } catch (e) {}\n\n var body = document.getElementById(\"member-detail-body\");\n var html = '<div class=\"profile-header\">' + renderAvatar(detail.avatar, detail.fullName) + '<div>';\n html += '<div style=\"font-weight:500;font-size:16px;color:var(--text-white)\">' + escapeHtml(detail.fullName || \"(unnamed)\") + '</div>';\n html += '<did-address did=\"' + escapeHtml(detail.did) + '\" compact></did-address>';\n html += '</div></div>';\n\n html += '<div class=\"settings-card\" style=\"margin-top:16px\">';\n html += '<div class=\"settings-row\"><span class=\"settings-label\">' + __t(\"members.detailRole\") + '</span><span class=\"settings-value\"><span class=\"badge badge-' + (detail.role || \"guest\") + '\">' + __t(\"common.\" + (detail.role || \"guest\")) + '</span></span></div>';\n html += '<div class=\"settings-row\"><span class=\"settings-label\">' + __t(\"members.detailStatus\") + '</span><span class=\"settings-value\">' + (detail.approved ? '<span class=\"badge badge-active\">' + __t(\"common.active\") + '</span>' : '<span class=\"badge badge-blocked\">' + __t(\"common.blocked\") + '</span>') + '</span></div>';\n html += '<div class=\"settings-row\"><span class=\"settings-label\">' + __t(\"members.detailSource\") + '</span><span class=\"settings-value\">' + escapeHtml(providerLabel(detail.sourceProvider)) + '</span></div>';\n if (detail.email) html += '<div class=\"settings-row\"><span class=\"settings-label\">' + __t(\"members.detailEmail\") + '</span><span class=\"settings-value\">' + escapeHtml(detail.email) + '</span></div>';\n if (detail.inviterName) html += '<div class=\"settings-row\"><span class=\"settings-label\">' + __t(\"members.detailInvitedBy\") + '</span><span class=\"settings-value\">' + escapeHtml(detail.inviterName) + '</span></div>';\n if (detail.createdAt) html += '<div class=\"settings-row\"><span class=\"settings-label\">' + __t(\"members.detailJoined\") + '</span><span class=\"settings-value\">' + escapeHtml(absoluteTime(detail.createdAt)) + '</span></div>';\n if (detail.lastLoginAt) html += '<div class=\"settings-row\"><span class=\"settings-label\">' + __t(\"members.detailLastLogin\") + '</span><span class=\"settings-value\">' + escapeHtml(absoluteTime(detail.lastLoginAt)) + '</span></div>';\n if (detail.sourceDomain) html += '<div class=\"settings-row\"><span class=\"settings-label\">' + __t(\"members.detailRegisteredFrom\") + '</span><span class=\"settings-value\">' + escapeHtml(detail.sourceDomain) + '</span></div>';\n if (detail.lastLoginDomain) html += '<div class=\"settings-row\"><span class=\"settings-label\">' + __t(\"members.detailLastLoginFrom\") + '</span><span class=\"settings-value\">' + escapeHtml(detail.lastLoginDomain) + '</span></div>';\n if (detail.passkeyCount !== undefined) html += '<div class=\"settings-row\"><span class=\"settings-label\">' + __t(\"members.detailPasskeys\") + '</span><span class=\"settings-value\">' + detail.passkeyCount + '</span></div>';\n html += '</div>';\n\n // Connected Accounts (read-only)\n if (detail.connectedAccounts && detail.connectedAccounts.length > 0) {\n html += '<div class=\"settings-card\" style=\"margin-top:16px\">';\n html += '<div class=\"settings-card-header\"><span>' + __t(\"members.detailConnectedAccounts\") + ' (' + detail.connectedAccounts.length + ')</span></div>';\n html += '<div style=\"max-height:240px;overflow-y:auto\">';\n detail.connectedAccounts.forEach(function(a) {\n var label = providerLabel(a.provider);\n var info = '';\n if (a.userInfo && a.userInfo.name) info = a.userInfo.name;\n else if (a.userInfo && a.userInfo.email) info = a.userInfo.email;\n else if (a.id) info = a.id;\n html += '<div class=\"settings-row\" style=\"gap:12px;align-items:flex-start\">';\n html += '<div style=\"flex:1;min-width:0\">';\n html += '<div style=\"font-size:14px;font-weight:500;color:var(--text)\">' + escapeHtml(label);\n if (a.isMain) html += ' <span class=\"badge badge-active\" style=\"font-size:10px;padding:1px 6px;\">' + __t(\"profile.mainAccount\") + '</span>';\n html += '</div>';\n if (info) html += '<div style=\"font-size:12px;color:var(--text-secondary);margin-top:2px\">' + escapeHtml(info) + '</div>';\n if (a.did) html += '<div style=\"font-size:11px;color:var(--text-secondary);margin-top:2px\"><did-address did=\"' + escapeHtml(a.did) + '\" compact></did-address></div>';\n html += '</div>';\n if (a.lastLoginAt) html += '<div style=\"font-size:12px;color:var(--text-secondary);flex-shrink:0\">' + escapeHtml(relativeTime(a.lastLoginAt)) + '</div>';\n html += '</div>';\n });\n html += '</div></div>';\n }\n\n // Instance memberships (aggregated mode)\n if (detail.instances && detail.instances.length > 0) {\n html += '<div class=\"settings-card\" style=\"margin-top:16px\">';\n html += '<div class=\"settings-card-header\"><span>' + __t(\"members.instanceMemberships\") + ' (' + detail.instances.length + ')</span></div>';\n html += '<div style=\"max-height:240px;overflow-y:auto\">';\n detail.instances.forEach(function(inst) {\n var instName = __instanceNames[inst.instance_did] || '';\n var instDid = inst.instance_did || '';\n html += '<div class=\"settings-row\" style=\"gap:12px\">';\n html += '<div style=\"flex:1;min-width:0\"><div style=\"font-size:14px;font-weight:500;color:var(--text)\">' + escapeHtml(instName || __t(\"members.currentInstance\")) + '</div>';\n if (instDid) html += '<div style=\"font-size:11px;color:var(--text-secondary);margin-top:2px\"><did-address did=\"' + escapeHtml(instDid) + '\" compact></did-address></div>';\n html += '</div>';\n html += '<div style=\"display:flex;align-items:center;gap:8px;flex-shrink:0\"><span class=\"badge badge-' + inst.role + '\">' + __t(\"common.\" + inst.role) + '</span>';\n if (inst.joined_at) html += '<span style=\"font-size:12px;color:var(--text-secondary)\">' + escapeHtml(relativeTime(inst.joined_at)) + '</span>';\n html += '</div></div>';\n });\n html += '</div></div>';\n }\n\n body.innerHTML = html;\n\n // Change role: mirror list's canOperate + renderMemberActions logic exactly.\n // Use post-merge detail so role/did reflect the latest API response.\n var changeRoleBtn = document.getElementById(\"member-detail-change-role\");\n var instanceSel = document.getElementById(\"members-instance-filter\");\n var instanceVisible = instanceSel && instanceSel.style.display !== 'none';\n var canOperate = !instanceVisible || (instanceVisible && instanceSel.value === '');\n if (__membersAggregated) canOperate = false;\n var isSelf = detail.did === __caller.did;\n var ROLE_LEVEL = { owner: 3, admin: 2, member: 1, guest: 0 };\n var callerLevel = ROLE_LEVEL[__caller.role] || 0;\n var targetLevel = ROLE_LEVEL[detail.role] || 0;\n var canChangeRole = canOperate && !isSelf && targetLevel < callerLevel;\n if (canChangeRole) {\n changeRoleBtn.classList.remove(\"hidden\");\n } else {\n changeRoleBtn.classList.add(\"hidden\");\n }\n\n showDialog(\"member-detail-dialog\");\n}\n\nfunction openChangeRoleForDid(did) {\n var u = currentMembersList.find(function(m) { return m.did === did; });\n if (!u) return;\n selectedMember = u;\n openChangeRoleDialog();\n}\n\nfunction openChangeRoleDialog() {\n if (!selectedMember) return;\n document.getElementById(\"change-role-title\").textContent = __t(\"members.changeRole\");\n document.getElementById(\"change-role-subtitle\").textContent = __t(\"members.changeRoleFor\", { name: selectedMember.fullName || truncateDid(selectedMember.did) });\n // Show/hide role options based on caller's level (only owner can assign admin)\n var radios = document.querySelectorAll('input[name=\"new-role\"]');\n radios.forEach(function(r) {\n r.checked = r.value === selectedMember.role;\n var label = r.closest('label');\n if (label) {\n if (r.value === 'admin' && __caller.role !== 'owner') label.style.display = 'none';\n else label.style.display = '';\n }\n });\n closeDialog(\"member-detail-dialog\");\n showDialog(\"change-role-dialog\");\n}\n\nasync function saveRoleChange() {\n if (!selectedMember) return;\n var selected = document.querySelector('input[name=\"new-role\"]:checked');\n if (!selected) return;\n var newRole = selected.value;\n var data = await api(\"PUT\", \"/members/\" + encodeURIComponent(selectedMember.did) + \"/role\", { role: newRole });\n if (data.ok) {\n showToast(__t(\"members.roleUpdated\"), \"success\");\n closeDialog(\"change-role-dialog\");\n loadMembers(membersPage);\n }\n}\n\n// name is looked up (not passed via onclick) so a member's self-set fullName\n// never has to be embedded as a JS-string literal inside an inline onclick=\n// attribute \u2014 HTML entity-escaping alone can't fully neutralize that context,\n// since the browser decodes entities before parsing onclick as script.\nasync function blockMember(did) {\n var u = currentMembersList.find(function(m) { return m.did === did; });\n var name = (u && u.fullName) || did;\n var ok = await confirmDialog({ title: __t(\"members.blockTitle\"), message: __t(\"members.blockMsg\", { name: name }), confirmLabel: __t(\"members.blockBtn\"), danger: true });\n if (!ok) return;\n var data = await api(\"PUT\", \"/members/\" + encodeURIComponent(did) + \"/approval\", { approved: false });\n if (data.ok) { showToast(__t(\"members.blockSuccess\"), \"success\"); loadMembers(membersPage); }\n}\n\nasync function unblockMember(did) {\n var u = currentMembersList.find(function(m) { return m.did === did; });\n var name = (u && u.fullName) || did;\n var ok = await confirmDialog({ title: __t(\"members.unblockTitle\"), message: __t(\"members.unblockMsg\", { name: name }), confirmLabel: __t(\"members.unblock\") });\n if (!ok) return;\n var data = await api(\"PUT\", \"/members/\" + encodeURIComponent(did) + \"/approval\", { approved: true });\n if (data.ok) { showToast(__t(\"members.unblockSuccess\"), \"success\"); loadMembers(membersPage); }\n}\n\nasync function removeMember(did) {\n var u = currentMembersList.find(function(m) { return m.did === did; });\n var name = (u && u.fullName) || did;\n var ok = await confirmDialog({ title: __t(\"members.removeTitle\"), message: __t(\"members.removeMsg\", { name: name }), confirmLabel: __t(\"members.remove\"), danger: true });\n if (!ok) return;\n var data = await api(\"DELETE\", \"/members/\" + encodeURIComponent(did));\n if (data.ok) { showToast(__t(\"members.removeSuccess\"), \"success\"); loadMembers(membersPage); }\n}\n\nfunction updateTransferSection(users) {\n var section = document.getElementById(\"ownership-transfer-section\");\n if (__caller.role !== \"owner\") {\n section.classList.add(\"hidden\");\n return;\n }\n section.classList.remove(\"hidden\");\n var select = document.getElementById(\"transfer-target\");\n var html = '<option value=\"\">' + __t(\"members.transferSelect\") + '</option>';\n users.forEach(function(u) {\n if (u.did !== __caller.did && u.approved && u.role !== \"owner\") {\n html += '<option value=\"' + escapeHtml(u.did) + '\">' + escapeHtml(u.fullName || truncateDid(u.did)) + ' (' + u.role + ')</option>';\n }\n });\n select.innerHTML = html;\n}\n\nasync function handleTransferOwnership() {\n var target = document.getElementById(\"transfer-target\").value;\n if (!target) { showToast(__t(\"members.selectMemberFirst\"), \"error\"); return; }\n var targetUser = currentMembersList.find(function(u) { return u.did === target; });\n var name = targetUser ? (targetUser.fullName || truncateDid(target)) : truncateDid(target);\n var ok = await confirmDialog({\n title: __t(\"members.transferTitle\"),\n message: __t(\"members.transferMsg\", { name: name }),\n confirmLabel: __t(\"members.transferConfirm\"),\n danger: true,\n });\n if (!ok) return;\n var data = await api(\"POST\", \"/transfer-ownership\", { targetDid: target });\n if (data.ok) {\n showToast(__t(\"members.transferSuccess\"), \"success\");\n setTimeout(function() { location.reload(); }, 1000);\n }\n}\n\n// Close action menus on outside click\ndocument.addEventListener(\"click\", function() {\n document.querySelectorAll(\".action-menu.open\").forEach(function(m) { m.classList.remove(\"open\"); });\n});\n\nregisterTab(\"members\", function() {\n initInstanceFilter().then(function() {\n // Auto-select instance from URL query (e.g., ?instance=z1xxx)\n var urlInstance = new URLSearchParams(window.location.search).get(\"instance\");\n if (urlInstance) {\n var sel = document.getElementById(\"members-instance-filter\");\n if (sel) sel.value = urlInstance;\n }\n // Restore sort state from URL (sortBy / sortOrder) on tab enter.\n readMembersSortFromURL();\n loadMembers(1);\n });\n});\n";
6
+ export declare const MEMBERS_TAB_SCRIPT = "\nvar membersPage = 1;\nvar membersPageSize = 20;\nvar memberSearchTimeout = null;\nvar currentMembersList = [];\nvar selectedMember = null;\nvar __membersAggregated = false;\n// Sort state: empty string = default (server decides + self-first on client).\n// Three-state click cycle: (none) \u2192 desc \u2192 asc \u2192 (none).\n// Persisted in location.search as sortBy/sortOrder so it survives refresh + share.\nvar membersSortBy = \"\";\nvar membersSortOrder = \"\";\nvar MEMBERS_SORT_VALID = [\"name\", \"role\", \"status\", \"source\", \"joined\"];\n\n/** Populate module sort state from the current URL query string. Idempotent. */\nfunction readMembersSortFromURL() {\n membersSortBy = \"\";\n membersSortOrder = \"\";\n var params = new URLSearchParams(location.search);\n var by = params.get(\"sortBy\");\n var order = params.get(\"sortOrder\");\n if (by && MEMBERS_SORT_VALID.indexOf(by) >= 0) {\n membersSortBy = by;\n membersSortOrder = (order === \"asc\" || order === \"desc\") ? order : \"desc\";\n }\n}\n\n/** Write current sort state back to location.search without reloading. */\nfunction writeMembersSortToURL() {\n var params = new URLSearchParams(location.search);\n if (membersSortBy) {\n params.set(\"sortBy\", membersSortBy);\n params.set(\"sortOrder\", membersSortOrder || \"desc\");\n } else {\n params.delete(\"sortBy\");\n params.delete(\"sortOrder\");\n }\n var qs = params.toString();\n var newUrl = location.pathname + (qs ? \"?\" + qs : \"\") + location.hash;\n history.replaceState(null, \"\", newUrl);\n}\n\n/** Cycle sort state for a column key and reload. */\nfunction toggleSort(key) {\n if (membersSortBy !== key) {\n membersSortBy = key;\n membersSortOrder = \"desc\";\n } else if (membersSortOrder === \"desc\") {\n membersSortOrder = \"asc\";\n } else {\n // Third click \u2014 back to default.\n membersSortBy = \"\";\n membersSortOrder = \"\";\n }\n writeMembersSortToURL();\n loadMembers(1);\n}\n\n/** Render a <th> with click-to-sort behavior. */\nfunction sortableTh(key, label, extraClass) {\n var isActive = membersSortBy === key;\n var arrow;\n if (isActive) {\n arrow = membersSortOrder === \"asc\"\n ? '<span class=\"sort-arrow sort-arrow-active\">\\u2191</span>'\n : '<span class=\"sort-arrow sort-arrow-active\">\\u2193</span>';\n } else {\n arrow = '<span class=\"sort-arrow\">\\u2195</span>';\n }\n var cls = \"sortable\" + (isActive ? \" sort-active\" : \"\") + (extraClass ? \" \" + extraClass : \"\");\n return '<th class=\"' + cls + '\" onclick=\"toggleSort(\\'' + key + '\\')\">' + label + arrow + '</th>';\n}\n\nfunction providerLabel(sp) {\n if (!sp) return \"\\u2014\";\n var key = \"members.providers.\" + sp;\n var val = __t(key);\n return val !== key ? val : sp;\n}\n\nfunction debouncedMemberSearch() {\n clearTimeout(memberSearchTimeout);\n memberSearchTimeout = setTimeout(function() { loadMembers(1); }, 300);\n}\n\n// Load registered instances for the filter dropdown (system admin only)\nvar __instanceNames = {};\nvar __instanceFilterInitialized = false;\nasync function initInstanceFilter() {\n if (__instanceFilterInitialized) return;\n try {\n // Best-effort: this endpoint is system-admin-only. Instance-scoped owners/admins\n // get 403 here, which is expected \u2014 fail silently (no error toast).\n var data = await api(\"GET\", \"/../admin/instances\", undefined, { silent: true });\n if (data.ok && data.instances && data.instances.length > 0) {\n __instanceFilterInitialized = true;\n var sel = document.getElementById(\"members-instance-filter\");\n data.instances.forEach(function(inst) {\n __instanceNames[inst.instanceDid] = inst.appName || inst.instanceDid.substring(0, 16);\n var opt = document.createElement(\"option\");\n opt.value = inst.instanceDid;\n opt.textContent = __instanceNames[inst.instanceDid];\n sel.appendChild(opt);\n });\n sel.style.display = \"\";\n }\n } catch(e) { /* not admin or no instances */ }\n}\n\n// Also support specific instance filter via query param\n// When ?instance=z1xxx, team handler needs to use that instanceDid\n\nasync function loadMembers(page) {\n membersPage = page || 1;\n var instanceFilter = document.getElementById(\"members-instance-filter\").value;\n var qs = \"?page=\" + membersPage + \"&pageSize=\" + membersPageSize;\n // System view (\"\") \u2192 use defaultInstanceDid so we query membership, not global users\n if (instanceFilter === \"\" && __pageData.instanceDid) {\n qs += \"&instance=\" + encodeURIComponent(__pageData.instanceDid);\n } else if (instanceFilter) {\n qs += \"&instance=\" + encodeURIComponent(instanceFilter);\n }\n var search = document.getElementById(\"members-search\").value.trim();\n if (search) qs += \"&search=\" + encodeURIComponent(search);\n var role = document.getElementById(\"members-role-filter\").value;\n if (role) qs += \"&role=\" + encodeURIComponent(role);\n var status = document.getElementById(\"members-status-filter\").value;\n if (status !== \"\") qs += \"&approved=\" + status;\n var source = document.getElementById(\"members-source-filter\").value;\n if (source) qs += \"&sourceProvider=\" + encodeURIComponent(source);\n if (membersSortBy) {\n qs += \"&sortBy=\" + encodeURIComponent(membersSortBy);\n qs += \"&sortOrder=\" + encodeURIComponent(membersSortOrder || \"desc\");\n }\n\n document.getElementById(\"members-table-wrap\").innerHTML = skeletonTable(8, 6);\n document.getElementById(\"members-pagination\").innerHTML = \"\";\n var data = await api(\"GET\", \"/members\" + qs);\n if (!data.ok) {\n document.getElementById(\"members-table-wrap\").innerHTML = '<div class=\"empty-state\"><div class=\"empty-state-icon\"><svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><path d=\"M12 8v4m0 4h.01\"/></svg></div><div class=\"empty-state-title\">' + __t(\"members.loadFailed\") + '</div></div>';\n return;\n }\n __membersAggregated = !!data.aggregated;\n // Only pin the caller to the top when using the default (server) order.\n // Under explicit sort, honor the user's intent without re-shuffling.\n if (membersSortBy) {\n currentMembersList = data.users || [];\n } else {\n currentMembersList = (data.users || []).slice().sort(function(a, b) {\n if (a.did === __caller.did) return -1;\n if (b.did === __caller.did) return 1;\n return (a.createdAt || 0) - (b.createdAt || 0);\n });\n }\n renderMembersTable(currentMembersList, data.paging);\n updateTransferSection(data.users);\n}\n\nfunction renderMembersTable(users, paging) {\n var wrap = document.getElementById(\"members-table-wrap\");\n // Aggregated (cross-instance \"All Instances\") view can't tell which instance\n // a role change should apply to, so per-row actions are disabled there \u2014 see\n // canOperate below. Surface why, instead of silently hiding the buttons.\n var aggregatedHint = __membersAggregated ? '<p class=\"text-muted\" style=\"font-size:13px;margin-bottom:12px;\">' + __t(\"members.aggregatedHint\") + '</p>' : '';\n if (!users || users.length === 0) {\n wrap.innerHTML = aggregatedHint + '<div class=\"empty-state\"><div class=\"empty-state-icon\"><svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2\"/><circle cx=\"9\" cy=\"7\" r=\"4\"/></svg></div><div class=\"empty-state-title\">' + __t(\"members.noMembers\") + '</div><div class=\"empty-state-desc\">' + __t(\"members.noMembersHint\") + '</div></div>';\n document.getElementById(\"members-pagination\").innerHTML = \"\";\n return;\n }\n\n var instanceSel = document.getElementById(\"members-instance-filter\");\n var instanceVisible = instanceSel && instanceSel.style.display !== 'none';\n var showInstance = instanceVisible && instanceSel.value === \"all\";\n var html = aggregatedHint + '<table class=\"table\"><thead><tr>';\n html += sortableTh(\"name\", __t(\"members.col.member\"));\n html += sortableTh(\"role\", __t(\"members.col.role\"));\n if (showInstance) html += '<th>' + __t(\"members.col.instance\") + '</th>';\n html += sortableTh(\"source\", __t(\"members.col.source\"), \"col-hide-sm\");\n html += sortableTh(\"status\", __t(\"members.col.status\"), \"col-hide-sm\");\n html += sortableTh(\"joined\", __t(\"members.col.joined\"), \"col-hide-md\");\n // Registered/Last Login From columns removed \u2014 domain info not meaningful in Worker context\n html += '<th></th></tr></thead><tbody>';\n users.forEach(function(u) {\n var userDid = u.did || u.user_did || '';\n var av = renderAvatar(u.avatar, u.fullName, \"sm\");\n var name = u.fullName ? escapeHtml(u.fullName) : '<span class=\"text-muted\">(unnamed)</span>';\n var did = '<did-address did=\"' + escapeHtml(userDid) + '\" compact></did-address>';\n var roleBadge = '<span class=\"badge badge-' + (u.role || \"guest\") + '\">' + __t(\"common.\" + (u.role || \"guest\")) + '</span>';\n var statusBadge = u.approved !== undefined ? (u.approved ? '<span class=\"badge badge-active\">' + __t(\"common.active\") + '</span>' : '<span class=\"badge badge-blocked\">' + __t(\"common.blocked\") + '</span>') : '<span class=\"text-muted\">\u2014</span>';\n var joined = (u.createdAt || u.joined_at) ? '<span class=\"cell-date\">' + escapeHtml(relativeTime(u.createdAt || u.joined_at)) + '</span>' : '<span class=\"text-muted\">\u2014</span>';\n html += '<tr style=\"cursor:pointer\" onclick=\"openMemberDetail(\\'' + escapeHtml(userDid) + '\\')\">';\n html += '<td><div class=\"member-identity\">' + av + '<div><div class=\"member-name\">' + name + '</div><div class=\"member-email\">' + did + '</div></div></div></td>';\n html += '<td>' + roleBadge + '</td>';\n if (showInstance) {\n if (__membersAggregated && u.instance_count > 1) {\n html += '<td><a href=\"javascript:void(0)\" onclick=\"event.stopPropagation();openMemberDetail(\\'' + escapeHtml(userDid) + '\\')\" style=\"color:var(--blue);font-size:14px;text-decoration:none\">' + u.instance_count + ' ' + __t(\"members.instances\") + '</a></td>';\n } else {\n var instDid = (__membersAggregated && u.instances && u.instances.length === 1) ? u.instances[0].instance_did : (u.instance_did || '');\n var instName = __instanceNames[instDid] || '';\n if (!instName) instName = instDid ? __t(\"members.currentInstance\") : '\u2014';\n html += '<td><div>' + escapeHtml(instName);\n if (instDid) html += '<div style=\"font-size:11px;color:var(--text-secondary);margin-top:2px\" onclick=\"event.stopPropagation()\"><did-address did=\"' + escapeHtml(instDid) + '\" compact></did-address></div>';\n html += '</div></td>';\n }\n }\n var sourceBadge = '<span class=\"text-muted\">' + escapeHtml(providerLabel(u.sourceProvider)) + '</span>';\n html += '<td class=\"col-hide-sm\">' + sourceBadge + '</td>';\n html += '<td class=\"col-hide-sm\">' + statusBadge + '</td>';\n html += '<td class=\"col-hide-md\">' + joined + '</td>';\n // Actions: only for current instance members, not cross-instance\n var canOperate = !instanceVisible || (instanceVisible && instanceSel.value === '');\n // In aggregated mode, only allow actions on users whose instance matches current system\n if (__membersAggregated) canOperate = false;\n html += '<td class=\"action-cell\">' + (canOperate ? renderMemberActions(u) : '') + '</td>';\n html += '</tr>';\n });\n html += '</tbody></table>';\n wrap.innerHTML = html;\n\n renderPagination(\"members-pagination\", paging, loadMembers);\n}\n\nfunction renderMemberActions(u) {\n var isOwner = __caller.role === \"owner\";\n var isAdmin = __caller.role === \"admin\";\n var isSelf = u.did === __caller.did;\n var ROLE_LEVEL = { owner: 3, admin: 2, member: 1, guest: 0 };\n var callerLevel = ROLE_LEVEL[__caller.role] || 0;\n var targetLevel = ROLE_LEVEL[u.role] || 0;\n // Can only act on members with lower privilege\n var canManage = !isSelf && targetLevel < callerLevel;\n var items = [];\n\n // Change role: can manage lower-level members\n if (canManage) {\n items.push('<button class=\"action-menu-item\" onclick=\"event.stopPropagation();openChangeRoleForDid(\\'' + escapeHtml(u.did) + '\\')\">' + __t(\"members.changeRole\") + '</button>');\n }\n if (canManage && u.approved) {\n items.push('<button class=\"action-menu-item\" onclick=\"event.stopPropagation();blockMember(\\'' + escapeHtml(u.did) + '\\')\">' + __t(\"members.block\") + '</button>');\n }\n if (canManage && !u.approved) {\n items.push('<button class=\"action-menu-item\" onclick=\"event.stopPropagation();unblockMember(\\'' + escapeHtml(u.did) + '\\')\">' + __t(\"members.unblock\") + '</button>');\n }\n if (items.length > 0 && canManage) {\n items.push('<div class=\"action-menu-sep\"></div>');\n }\n if (canManage) {\n items.push('<button class=\"action-menu-item danger\" onclick=\"event.stopPropagation();removeMember(\\'' + escapeHtml(u.did) + '\\')\">' + __t(\"members.remove\") + '</button>');\n }\n\n if (items.length === 0) return \"\";\n return '<button class=\"action-trigger\" onclick=\"event.stopPropagation();toggleActionMenu(this)\">\u22EF</button><div class=\"action-menu\">' + items.join(\"\") + '</div>';\n}\n\nfunction toggleActionMenu(trigger) {\n var menu = trigger.nextElementSibling;\n var wasOpen = menu.classList.contains(\"open\");\n // Close all menus first\n document.querySelectorAll(\".action-menu.open\").forEach(function(m) { m.classList.remove(\"open\"); });\n if (!wasOpen) menu.classList.toggle(\"open\");\n}\n\nasync function openMemberDetail(did) {\n var u = currentMembersList.find(function(m) { return m.did === did; });\n if (!u) return;\n selectedMember = u;\n\n // Fetch full member info (with connectedAccounts) from API.\n // Falls back to in-memory list data if the request fails.\n var detail = u;\n try {\n var resp = await api(\"GET\", \"/members/\" + encodeURIComponent(did));\n if (resp && resp.ok && resp.user) {\n // Merge: in-memory list has fields the API may omit (instances, passkeyCount in some paths).\n detail = Object.assign({}, u, resp.user);\n selectedMember = detail;\n }\n } catch (e) {}\n\n var body = document.getElementById(\"member-detail-body\");\n var html = '<div class=\"profile-header\">' + renderAvatar(detail.avatar, detail.fullName) + '<div>';\n html += '<div style=\"font-weight:500;font-size:16px;color:var(--text-white)\">' + escapeHtml(detail.fullName || \"(unnamed)\") + '</div>';\n html += '<did-address did=\"' + escapeHtml(detail.did) + '\" compact></did-address>';\n html += '</div></div>';\n\n html += '<div class=\"settings-card\" style=\"margin-top:16px\">';\n html += '<div class=\"settings-row\"><span class=\"settings-label\">' + __t(\"members.detailRole\") + '</span><span class=\"settings-value\"><span class=\"badge badge-' + (detail.role || \"guest\") + '\">' + __t(\"common.\" + (detail.role || \"guest\")) + '</span></span></div>';\n html += '<div class=\"settings-row\"><span class=\"settings-label\">' + __t(\"members.detailStatus\") + '</span><span class=\"settings-value\">' + (detail.approved ? '<span class=\"badge badge-active\">' + __t(\"common.active\") + '</span>' : '<span class=\"badge badge-blocked\">' + __t(\"common.blocked\") + '</span>') + '</span></div>';\n html += '<div class=\"settings-row\"><span class=\"settings-label\">' + __t(\"members.detailSource\") + '</span><span class=\"settings-value\">' + escapeHtml(providerLabel(detail.sourceProvider)) + '</span></div>';\n if (detail.email) html += '<div class=\"settings-row\"><span class=\"settings-label\">' + __t(\"members.detailEmail\") + '</span><span class=\"settings-value\">' + escapeHtml(detail.email) + '</span></div>';\n if (detail.inviterName) html += '<div class=\"settings-row\"><span class=\"settings-label\">' + __t(\"members.detailInvitedBy\") + '</span><span class=\"settings-value\">' + escapeHtml(detail.inviterName) + '</span></div>';\n if (detail.createdAt) html += '<div class=\"settings-row\"><span class=\"settings-label\">' + __t(\"members.detailJoined\") + '</span><span class=\"settings-value\">' + escapeHtml(absoluteTime(detail.createdAt)) + '</span></div>';\n if (detail.lastLoginAt) html += '<div class=\"settings-row\"><span class=\"settings-label\">' + __t(\"members.detailLastLogin\") + '</span><span class=\"settings-value\">' + escapeHtml(absoluteTime(detail.lastLoginAt)) + '</span></div>';\n if (detail.sourceDomain) html += '<div class=\"settings-row\"><span class=\"settings-label\">' + __t(\"members.detailRegisteredFrom\") + '</span><span class=\"settings-value\">' + escapeHtml(detail.sourceDomain) + '</span></div>';\n if (detail.lastLoginDomain) html += '<div class=\"settings-row\"><span class=\"settings-label\">' + __t(\"members.detailLastLoginFrom\") + '</span><span class=\"settings-value\">' + escapeHtml(detail.lastLoginDomain) + '</span></div>';\n if (detail.passkeyCount !== undefined) html += '<div class=\"settings-row\"><span class=\"settings-label\">' + __t(\"members.detailPasskeys\") + '</span><span class=\"settings-value\">' + detail.passkeyCount + '</span></div>';\n html += '</div>';\n\n // Connected Accounts (read-only)\n if (detail.connectedAccounts && detail.connectedAccounts.length > 0) {\n html += '<div class=\"settings-card\" style=\"margin-top:16px\">';\n html += '<div class=\"settings-card-header\"><span>' + __t(\"members.detailConnectedAccounts\") + ' (' + detail.connectedAccounts.length + ')</span></div>';\n html += '<div style=\"max-height:240px;overflow-y:auto\">';\n detail.connectedAccounts.forEach(function(a) {\n var label = providerLabel(a.provider);\n var info = '';\n if (a.userInfo && a.userInfo.name) info = a.userInfo.name;\n else if (a.userInfo && a.userInfo.email) info = a.userInfo.email;\n else if (a.id) info = a.id;\n html += '<div class=\"settings-row\" style=\"gap:12px;align-items:flex-start\">';\n html += '<div style=\"flex:1;min-width:0\">';\n html += '<div style=\"font-size:14px;font-weight:500;color:var(--text)\">' + escapeHtml(label);\n if (a.isMain) html += ' <span class=\"badge badge-active\" style=\"font-size:10px;padding:1px 6px;\">' + __t(\"profile.mainAccount\") + '</span>';\n html += '</div>';\n if (info) html += '<div style=\"font-size:12px;color:var(--text-secondary);margin-top:2px\">' + escapeHtml(info) + '</div>';\n if (a.did) html += '<div style=\"font-size:11px;color:var(--text-secondary);margin-top:2px\"><did-address did=\"' + escapeHtml(a.did) + '\" compact></did-address></div>';\n html += '</div>';\n if (a.lastLoginAt) html += '<div style=\"font-size:12px;color:var(--text-secondary);flex-shrink:0\">' + escapeHtml(relativeTime(a.lastLoginAt)) + '</div>';\n html += '</div>';\n });\n html += '</div></div>';\n }\n\n // Instance memberships (aggregated mode)\n if (detail.instances && detail.instances.length > 0) {\n html += '<div class=\"settings-card\" style=\"margin-top:16px\">';\n html += '<div class=\"settings-card-header\"><span>' + __t(\"members.instanceMemberships\") + ' (' + detail.instances.length + ')</span></div>';\n html += '<div style=\"max-height:240px;overflow-y:auto\">';\n detail.instances.forEach(function(inst) {\n var instName = __instanceNames[inst.instance_did] || '';\n var instDid = inst.instance_did || '';\n html += '<div class=\"settings-row\" style=\"gap:12px\">';\n html += '<div style=\"flex:1;min-width:0\"><div style=\"font-size:14px;font-weight:500;color:var(--text)\">' + escapeHtml(instName || __t(\"members.currentInstance\")) + '</div>';\n if (instDid) html += '<div style=\"font-size:11px;color:var(--text-secondary);margin-top:2px\"><did-address did=\"' + escapeHtml(instDid) + '\" compact></did-address></div>';\n html += '</div>';\n html += '<div style=\"display:flex;align-items:center;gap:8px;flex-shrink:0\"><span class=\"badge badge-' + inst.role + '\">' + __t(\"common.\" + inst.role) + '</span>';\n if (inst.joined_at) html += '<span style=\"font-size:12px;color:var(--text-secondary)\">' + escapeHtml(relativeTime(inst.joined_at)) + '</span>';\n html += '</div></div>';\n });\n html += '</div></div>';\n }\n\n body.innerHTML = html;\n\n // Change role: mirror list's canOperate + renderMemberActions logic exactly.\n // Use post-merge detail so role/did reflect the latest API response.\n var changeRoleBtn = document.getElementById(\"member-detail-change-role\");\n var instanceSel = document.getElementById(\"members-instance-filter\");\n var instanceVisible = instanceSel && instanceSel.style.display !== 'none';\n var canOperate = !instanceVisible || (instanceVisible && instanceSel.value === '');\n if (__membersAggregated) canOperate = false;\n var isSelf = detail.did === __caller.did;\n var ROLE_LEVEL = { owner: 3, admin: 2, member: 1, guest: 0 };\n var callerLevel = ROLE_LEVEL[__caller.role] || 0;\n var targetLevel = ROLE_LEVEL[detail.role] || 0;\n var canChangeRole = canOperate && !isSelf && targetLevel < callerLevel;\n if (canChangeRole) {\n changeRoleBtn.classList.remove(\"hidden\");\n } else {\n changeRoleBtn.classList.add(\"hidden\");\n }\n\n showDialog(\"member-detail-dialog\");\n}\n\nfunction openChangeRoleForDid(did) {\n var u = currentMembersList.find(function(m) { return m.did === did; });\n if (!u) return;\n selectedMember = u;\n openChangeRoleDialog();\n}\n\nfunction openChangeRoleDialog() {\n if (!selectedMember) return;\n document.getElementById(\"change-role-title\").textContent = __t(\"members.changeRole\");\n document.getElementById(\"change-role-subtitle\").textContent = __t(\"members.changeRoleFor\", { name: selectedMember.fullName || truncateDid(selectedMember.did) });\n // Show/hide role options based on caller's level (only owner can assign admin)\n var radios = document.querySelectorAll('input[name=\"new-role\"]');\n radios.forEach(function(r) {\n r.checked = r.value === selectedMember.role;\n var label = r.closest('label');\n if (label) {\n if (r.value === 'admin' && __caller.role !== 'owner') label.style.display = 'none';\n else label.style.display = '';\n }\n });\n closeDialog(\"member-detail-dialog\");\n showDialog(\"change-role-dialog\");\n}\n\nasync function saveRoleChange() {\n if (!selectedMember) return;\n var selected = document.querySelector('input[name=\"new-role\"]:checked');\n if (!selected) return;\n var newRole = selected.value;\n var data = await api(\"PUT\", \"/members/\" + encodeURIComponent(selectedMember.did) + \"/role\", { role: newRole });\n if (data.ok) {\n showToast(__t(\"members.roleUpdated\"), \"success\");\n closeDialog(\"change-role-dialog\");\n loadMembers(membersPage);\n }\n}\n\n// name is looked up (not passed via onclick) so a member's self-set fullName\n// never has to be embedded as a JS-string literal inside an inline onclick=\n// attribute \u2014 HTML entity-escaping alone can't fully neutralize that context,\n// since the browser decodes entities before parsing onclick as script.\nasync function blockMember(did) {\n var u = currentMembersList.find(function(m) { return m.did === did; });\n var name = (u && u.fullName) || did;\n var ok = await confirmDialog({ title: __t(\"members.blockTitle\"), message: __t(\"members.blockMsg\", { name: name }), confirmLabel: __t(\"members.blockBtn\"), danger: true });\n if (!ok) return;\n var data = await api(\"PUT\", \"/members/\" + encodeURIComponent(did) + \"/approval\", { approved: false });\n if (data.ok) { showToast(__t(\"members.blockSuccess\"), \"success\"); loadMembers(membersPage); }\n}\n\nasync function unblockMember(did) {\n var u = currentMembersList.find(function(m) { return m.did === did; });\n var name = (u && u.fullName) || did;\n var ok = await confirmDialog({ title: __t(\"members.unblockTitle\"), message: __t(\"members.unblockMsg\", { name: name }), confirmLabel: __t(\"members.unblock\") });\n if (!ok) return;\n var data = await api(\"PUT\", \"/members/\" + encodeURIComponent(did) + \"/approval\", { approved: true });\n if (data.ok) { showToast(__t(\"members.unblockSuccess\"), \"success\"); loadMembers(membersPage); }\n}\n\nasync function removeMember(did) {\n var u = currentMembersList.find(function(m) { return m.did === did; });\n var name = (u && u.fullName) || did;\n var ok = await confirmDialog({ title: __t(\"members.removeTitle\"), message: __t(\"members.removeMsg\", { name: name }), confirmLabel: __t(\"members.remove\"), danger: true });\n if (!ok) return;\n var data = await api(\"DELETE\", \"/members/\" + encodeURIComponent(did));\n if (data.ok) { showToast(__t(\"members.removeSuccess\"), \"success\"); loadMembers(membersPage); }\n}\n\nfunction updateTransferSection(users) {\n var section = document.getElementById(\"ownership-transfer-section\");\n if (__caller.role !== \"owner\") {\n section.classList.add(\"hidden\");\n return;\n }\n section.classList.remove(\"hidden\");\n var select = document.getElementById(\"transfer-target\");\n var html = '<option value=\"\">' + __t(\"members.transferSelect\") + '</option>';\n users.forEach(function(u) {\n if (u.did !== __caller.did && u.approved && u.role !== \"owner\") {\n html += '<option value=\"' + escapeHtml(u.did) + '\">' + escapeHtml(u.fullName || truncateDid(u.did)) + ' (' + u.role + ')</option>';\n }\n });\n select.innerHTML = html;\n}\n\nasync function handleTransferOwnership() {\n var target = document.getElementById(\"transfer-target\").value;\n if (!target) { showToast(__t(\"members.selectMemberFirst\"), \"error\"); return; }\n var targetUser = currentMembersList.find(function(u) { return u.did === target; });\n var name = targetUser ? (targetUser.fullName || truncateDid(target)) : truncateDid(target);\n var ok = await confirmDialog({\n title: __t(\"members.transferTitle\"),\n message: __t(\"members.transferMsg\", { name: name }),\n confirmLabel: __t(\"members.transferConfirm\"),\n danger: true,\n });\n if (!ok) return;\n var data = await api(\"POST\", \"/transfer-ownership\", { targetDid: target });\n if (data.ok) {\n showToast(__t(\"members.transferSuccess\"), \"success\");\n setTimeout(function() { location.reload(); }, 1000);\n }\n}\n\n// Close action menus on outside click\ndocument.addEventListener(\"click\", function() {\n document.querySelectorAll(\".action-menu.open\").forEach(function(m) { m.classList.remove(\"open\"); });\n});\n\nregisterTab(\"members\", function() {\n initInstanceFilter().then(function() {\n // Auto-select instance from URL query (e.g., ?instance=z1xxx)\n var urlInstance = new URLSearchParams(window.location.search).get(\"instance\");\n if (urlInstance) {\n var sel = document.getElementById(\"members-instance-filter\");\n if (sel) sel.value = urlInstance;\n }\n // Restore sort state from URL (sortBy / sortOrder) on tab enter.\n readMembersSortFromURL();\n loadMembers(1);\n });\n});\n";
7
7
  export {};
8
8
  //# sourceMappingURL=tab-members.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"tab-members.d.ts","sourceRoot":"","sources":["../../../src/pages/admin/tab-members.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,KAAK,GAAG,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,MAAM,CAAC;AAEpE,wBAAgB,cAAc,CAAC,CAAC,EAAE,GAAG,GAAG,MAAM,CAiG7C;AAED,eAAO,MAAM,kBAAkB,oy0BAoe9B,CAAC"}
1
+ {"version":3,"file":"tab-members.d.ts","sourceRoot":"","sources":["../../../src/pages/admin/tab-members.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,KAAK,GAAG,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,MAAM,CAAC;AAEpE,wBAAgB,cAAc,CAAC,CAAC,EAAE,GAAG,GAAG,MAAM,CAiG7C;AAED,eAAO,MAAM,kBAAkB,mu1BAwe9B,CAAC"}
@@ -14,7 +14,7 @@ export function membersTabHtml(t) {
14
14
  </div>
15
15
  <select class="select" id="members-instance-filter" onchange="loadMembers(1)" style="display:none">
16
16
  <option value="all">${t("members.allInstances")}</option>
17
- <option value="">${t("members.currentInstance")}</option>
17
+ <option value="" selected>${t("members.currentInstance")}</option>
18
18
  </select>
19
19
  <select class="select" id="members-role-filter" onchange="loadMembers(1)">
20
20
  <option value="">${t("members.allRoles")}</option>
@@ -259,8 +259,12 @@ async function loadMembers(page) {
259
259
 
260
260
  function renderMembersTable(users, paging) {
261
261
  var wrap = document.getElementById("members-table-wrap");
262
+ // Aggregated (cross-instance "All Instances") view can't tell which instance
263
+ // a role change should apply to, so per-row actions are disabled there — see
264
+ // canOperate below. Surface why, instead of silently hiding the buttons.
265
+ var aggregatedHint = __membersAggregated ? '<p class="text-muted" style="font-size:13px;margin-bottom:12px;">' + __t("members.aggregatedHint") + '</p>' : '';
262
266
  if (!users || users.length === 0) {
263
- wrap.innerHTML = '<div class="empty-state"><div class="empty-state-icon"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/></svg></div><div class="empty-state-title">' + __t("members.noMembers") + '</div><div class="empty-state-desc">' + __t("members.noMembersHint") + '</div></div>';
267
+ wrap.innerHTML = aggregatedHint + '<div class="empty-state"><div class="empty-state-icon"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/></svg></div><div class="empty-state-title">' + __t("members.noMembers") + '</div><div class="empty-state-desc">' + __t("members.noMembersHint") + '</div></div>';
264
268
  document.getElementById("members-pagination").innerHTML = "";
265
269
  return;
266
270
  }
@@ -268,7 +272,7 @@ function renderMembersTable(users, paging) {
268
272
  var instanceSel = document.getElementById("members-instance-filter");
269
273
  var instanceVisible = instanceSel && instanceSel.style.display !== 'none';
270
274
  var showInstance = instanceVisible && instanceSel.value === "all";
271
- var html = '<table class="table"><thead><tr>';
275
+ var html = aggregatedHint + '<table class="table"><thead><tr>';
272
276
  html += sortableTh("name", __t("members.col.member"));
273
277
  html += sortableTh("role", __t("members.col.role"));
274
278
  if (showInstance) html += '<th>' + __t("members.col.instance") + '</th>';
@@ -1 +1 @@
1
- {"version":3,"file":"tab-members.js","sourceRoot":"","sources":["../../../src/pages/admin/tab-members.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,MAAM,UAAU,cAAc,CAAC,CAAM;IACnC,OAAO,UAAU,CAAC;;8BAEU,CAAC,CAAC,eAAe,CAAC;4BACpB,CAAC,CAAC,cAAc,CAAC;;;;;+EAKkC,CAAC,CAAC,gBAAgB,CAAC;;;4BAGtE,CAAC,CAAC,sBAAsB,CAAC;yBAC5B,CAAC,CAAC,yBAAyB,CAAC;;;yBAG5B,CAAC,CAAC,kBAAkB,CAAC;8BAChB,CAAC,CAAC,cAAc,CAAC;8BACjB,CAAC,CAAC,cAAc,CAAC;+BAChB,CAAC,CAAC,eAAe,CAAC;8BACnB,CAAC,CAAC,cAAc,CAAC;;;yBAGtB,CAAC,CAAC,mBAAmB,CAAC;0BACrB,CAAC,CAAC,eAAe,CAAC;0BAClB,CAAC,CAAC,gBAAgB,CAAC;;;yBAGpB,CAAC,CAAC,oBAAoB,CAAC;+BACjB,CAAC,CAAC,0BAA0B,CAAC;gCAC5B,CAAC,CAAC,2BAA2B,CAAC;8BAChC,CAAC,CAAC,yBAAyB,CAAC;+BAC3B,CAAC,CAAC,0BAA0B,CAAC;+BAC7B,CAAC,CAAC,0BAA0B,CAAC;8BAC9B,CAAC,CAAC,yBAAyB,CAAC;gCAC1B,CAAC,CAAC,2BAA2B,CAAC;iCAC7B,CAAC,CAAC,4BAA4B,CAAC;8BAClC,CAAC,CAAC,yBAAyB,CAAC;;;;;;;;;;;;8EAYoB,CAAC,CAAC,kBAAkB,CAAC;;;2EAGxB,CAAC,CAAC,sBAAsB,CAAC;;;sFAGd,CAAC,CAAC,qBAAqB,CAAC;;;;;;;;;;UAUpG,CAAC,CAAC,gBAAgB,CAAC;;;;sFAIyD,CAAC,CAAC,cAAc,CAAC;iGACN,CAAC,CAAC,oBAAoB,CAAC;;;;;;;iCAOvF,CAAC,CAAC,oBAAoB,CAAC;;;;qEAIa,CAAC,CAAC,mBAAmB,CAAC;;;sEAGrB,CAAC,CAAC,oBAAoB,CAAC;;;qEAGxB,CAAC,CAAC,mBAAmB,CAAC;;;;;oFAKP,CAAC,CAAC,eAAe,CAAC;qDACjD,CAAC,CAAC,oBAAoB,CAAC;;;CAG3E,CAAC;AACF,CAAC;AAED,MAAM,CAAC,MAAM,kBAAkB,GAAG,QAAQ,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAoe1C,CAAC"}
1
+ {"version":3,"file":"tab-members.js","sourceRoot":"","sources":["../../../src/pages/admin/tab-members.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,MAAM,UAAU,cAAc,CAAC,CAAM;IACnC,OAAO,UAAU,CAAC;;8BAEU,CAAC,CAAC,eAAe,CAAC;4BACpB,CAAC,CAAC,cAAc,CAAC;;;;;+EAKkC,CAAC,CAAC,gBAAgB,CAAC;;;4BAGtE,CAAC,CAAC,sBAAsB,CAAC;kCACnB,CAAC,CAAC,yBAAyB,CAAC;;;yBAGrC,CAAC,CAAC,kBAAkB,CAAC;8BAChB,CAAC,CAAC,cAAc,CAAC;8BACjB,CAAC,CAAC,cAAc,CAAC;+BAChB,CAAC,CAAC,eAAe,CAAC;8BACnB,CAAC,CAAC,cAAc,CAAC;;;yBAGtB,CAAC,CAAC,mBAAmB,CAAC;0BACrB,CAAC,CAAC,eAAe,CAAC;0BAClB,CAAC,CAAC,gBAAgB,CAAC;;;yBAGpB,CAAC,CAAC,oBAAoB,CAAC;+BACjB,CAAC,CAAC,0BAA0B,CAAC;gCAC5B,CAAC,CAAC,2BAA2B,CAAC;8BAChC,CAAC,CAAC,yBAAyB,CAAC;+BAC3B,CAAC,CAAC,0BAA0B,CAAC;+BAC7B,CAAC,CAAC,0BAA0B,CAAC;8BAC9B,CAAC,CAAC,yBAAyB,CAAC;gCAC1B,CAAC,CAAC,2BAA2B,CAAC;iCAC7B,CAAC,CAAC,4BAA4B,CAAC;8BAClC,CAAC,CAAC,yBAAyB,CAAC;;;;;;;;;;;;8EAYoB,CAAC,CAAC,kBAAkB,CAAC;;;2EAGxB,CAAC,CAAC,sBAAsB,CAAC;;;sFAGd,CAAC,CAAC,qBAAqB,CAAC;;;;;;;;;;UAUpG,CAAC,CAAC,gBAAgB,CAAC;;;;sFAIyD,CAAC,CAAC,cAAc,CAAC;iGACN,CAAC,CAAC,oBAAoB,CAAC;;;;;;;iCAOvF,CAAC,CAAC,oBAAoB,CAAC;;;;qEAIa,CAAC,CAAC,mBAAmB,CAAC;;;sEAGrB,CAAC,CAAC,oBAAoB,CAAC;;;qEAGxB,CAAC,CAAC,mBAAmB,CAAC;;;;;oFAKP,CAAC,CAAC,eAAe,CAAC;qDACjD,CAAC,CAAC,oBAAoB,CAAC;;;CAG3E,CAAC;AACF,CAAC;AAED,MAAM,CAAC,MAAM,kBAAkB,GAAG,QAAQ,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAwe1C,CAAC"}
@@ -260,7 +260,7 @@ function connectOAuth(provider) {
260
260
  var left = (screen.width - w) / 2, top = (screen.height - h) / 2;
261
261
  var popup = window.open(url, "oauth_bind", "width=" + w + ",height=" + h + ",left=" + left + ",top=" + top);
262
262
  if (!popup) {
263
- showToast("Popup blocked — please allow popups and try again", "error");
263
+ showToast(__t("common.popupBlocked"), "error");
264
264
  return;
265
265
  }
266
266
 
@@ -4,5 +4,5 @@
4
4
  * Pattern: embedded LoginPage + registration form + registered instance list.
5
5
  * Follows gen-access-key-page.ts architecture.
6
6
  */
7
- export declare function buildAdminInstancesPageHTML(): string;
7
+ export declare function buildAdminInstancesPageHTML(locale?: string): string;
8
8
  //# sourceMappingURL=admin-instances-page.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"admin-instances-page.d.ts","sourceRoot":"","sources":["../../src/pages/admin-instances-page.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAwMH,wBAAgB,2BAA2B,IAAI,MAAM,CAmVpD"}
1
+ {"version":3,"file":"admin-instances-page.d.ts","sourceRoot":"","sources":["../../src/pages/admin-instances-page.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAsTH,wBAAgB,2BAA2B,CAAC,MAAM,SAAO,GAAG,MAAM,CAwWjE"}