(useCacheOnly)
| 844 | // ───────────────────────────────────────── |
| 845 | |
| 846 | async function loadClientPortalsList(useCacheOnly) { |
| 847 | const body = document.getElementById('clientPortalsBody'); |
| 848 | const status = document.getElementById('clientPortalsStatus'); |
| 849 | if (!body) return; |
| 850 | |
| 851 | body.textContent = `${t('loading')}…`; |
| 852 | if (status) { |
| 853 | status.textContent = ''; |
| 854 | status.className = 'small text-muted'; |
| 855 | } |
| 856 | |
| 857 | try { |
| 858 | await loadPortalSources(); |
| 859 | const publicAiConfig = await loadPublicAiConfig(); |
| 860 | let portals; |
| 861 | if (useCacheOnly && __portalsCache && Object.keys(__portalsCache).length) { |
| 862 | portals = __portalsCache; |
| 863 | } else { |
| 864 | portals = await fetchAllPortals(); |
| 865 | __portalsCache = portals || {}; |
| 866 | } |
| 867 | |
| 868 | const slugs = Object.keys(__portalsCache).sort((a, b) => a.localeCompare(b)); |
| 869 | if (!slugs.length) { |
| 870 | body.innerHTML = `<p class="muted">No client portals defined yet. Click “Add portal” to create one.</p>`; |
| 871 | return; |
| 872 | } |
| 873 | |
| 874 | let html = ''; |
| 875 | slugs.forEach(slug => { |
| 876 | const origin = window.location.origin || ''; |
| 877 | const portalPath = withBase('/portal/' + encodeURIComponent(slug)); |
| 878 | const portalUrl = origin ? origin + portalPath : portalPath; |
| 879 | |
| 880 | const p = __portalsCache[slug] || {}; |
| 881 | const label = p.label || slug; |
| 882 | const folder = p.folder || ''; |
| 883 | const sourceId = normalizePortalSourceId(p.sourceId) || getPortalSourceFallbackId(); |
| 884 | const sourceOptions = renderPortalSourceOptions(sourceId); |
| 885 | const clientEmail = p.clientEmail || ''; |
| 886 | const aiEnabled = Object.prototype.hasOwnProperty.call(p, 'aiEnabled') ? p.aiEnabled !== false : false; |
| 887 | const uploadOnly = !!p.uploadOnly; |
| 888 | const allowSubfolders = !!p.allowSubfolders; |
| 889 | |
| 890 | // Backwards compat: |
| 891 | // - Old portals only had "uploadOnly": |
| 892 | // uploadOnly = true => upload yes, download no |
| 893 | // uploadOnly = false => upload yes, download yes |
| 894 | // - New portals have explicit allowDownload. |
| 895 | let allowDownload; |
| 896 | if (Object.prototype.hasOwnProperty.call(p, 'allowDownload')) { |
| 897 | allowDownload = p.allowDownload !== false; |
| 898 | } else { |
| 899 | // Legacy: "upload only" meant no download |
| 900 | allowDownload = !uploadOnly; |
| 901 | } |
| 902 | |
| 903 |
no test coverage detected