import { postForm as postFormRequest } from '../core/app-http.js'; export const buildUrl = (appBase, path) => new URL(path, appBase).toString(); export const getAppBase = () => { const baseEl = document.querySelector('base'); if (baseEl && baseEl.href) { return baseEl.href; } return `${window.location.origin}/`; }; const resolveAppBaseUrl = () => { try { return new URL(getAppBase(), `${window.location.origin}/`); } catch { return new URL(`${window.location.origin}/`); } }; const normalizeBasePathname = (pathname) => { const trimmed = String(pathname ?? '').trim(); if (trimmed === '' || trimmed === '/') { return '/'; } return `/${trimmed.replace(/^\/+|\/+$/g, '')}/`; }; const toAppRelativePath = (pathname, appBasePathname) => { const normalizedPath = `/${String(pathname ?? '').replace(/^\/+/, '')}`; const normalizedBasePath = normalizeBasePathname(appBasePathname); let relativePath = normalizedPath; if (normalizedBasePath !== '/' && relativePath.startsWith(normalizedBasePath)) { relativePath = relativePath.slice(normalizedBasePath.length - 1); } else if (normalizedBasePath !== '/' && relativePath === normalizedBasePath.slice(0, -1)) { relativePath = '/'; } return relativePath.replace(/^\/+/, ''); }; const parseAppUrl = (value, baseUrl = resolveAppBaseUrl()) => { const raw = String(value ?? '').trim(); if (raw === '') { return null; } try { return new URL(raw, baseUrl); } catch { return null; } }; export const normalizeListReturnTarget = (sourceUrl = window.location.href) => { const appBaseUrl = resolveAppBaseUrl(); const parsed = parseAppUrl(sourceUrl, appBaseUrl); if (!parsed) { return ''; } const relativePath = toAppRelativePath(parsed.pathname, appBaseUrl.pathname); if (relativePath === '') { return ''; } const query = new URLSearchParams(parsed.search); query.delete('return'); const queryString = query.toString(); return `${relativePath}${queryString ? `?${queryString}` : ''}`; }; export const withReturnTarget = (targetUrl, returnTarget = '') => { const appBaseUrl = resolveAppBaseUrl(); const parsedTarget = parseAppUrl(targetUrl, appBaseUrl); if (!parsedTarget) { return String(targetUrl ?? ''); } if (parsedTarget.origin !== window.location.origin) { return String(targetUrl ?? ''); } const normalizedReturn = normalizeListReturnTarget(returnTarget); parsedTarget.searchParams.delete('return'); if (normalizedReturn !== '') { parsedTarget.searchParams.set('return', normalizedReturn); } return parsedTarget.toString(); }; export const withCurrentListReturn = (targetUrl) => withReturnTarget(targetUrl, window.location.href); export const escapeHtml = (value) => String(value ?? '').replace(/[&<>"']/g, (char) => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[char])); export const buildBadgeList = (items, options = {}) => { const { variant = 'neutral', primaryVariant = 'info', primaryTooltip = '' } = options; let list = items; let primary = ''; if (items && typeof items === 'object' && !Array.isArray(items)) { list = Array.isArray(items.items) ? items.items : []; primary = String(items.primary ?? ''); } if (!Array.isArray(list) || list.length === 0) {return '';} const tooltipAttr = primaryTooltip ? ` data-tooltip="${escapeHtml(primaryTooltip)}" data-tooltip-pos="top"` : ''; const badges = list.map((label) => { const labelText = String(label ?? ''); const isPrimary = primary !== '' && labelText === primary; const badgeVariant = isPrimary ? primaryVariant : variant; const badgeTooltip = isPrimary && primaryTooltip ? tooltipAttr : ''; return `${escapeHtml(labelText)}`; }).join(' '); return `
${badges}
`; }; export const postAction = async (url, csrf, data = {}) => { const body = new URLSearchParams({ ...data, [csrf.key]: csrf.token }); return postFormRequest(url, body); }; export const badgeHtml = (gridjs, value = '') => { const safe = value ?? ''; return gridjs.html(`${safe}`); }; export const normalizeMultiSelectValue = (value) => { if (Array.isArray(value)) { return value .map((item) => String(item ?? '').trim()) .filter(Boolean); } if (typeof value === 'string') { return value .split(',') .map((item) => item.trim()) .filter(Boolean); } return []; }; export const gridFilterSelect = (input, param, options = {}) => ({ input, param, ...options }); export const gridFilterDate = (input, param, options = {}) => ({ input, param, normalize: (value) => { const date = String(value ?? '').trim(); return /^\d{4}-\d{2}-\d{2}$/.test(date) ? date : ''; }, ...options }); export const gridFilterText = (input, param, options = {}) => ({ input, param, normalize: (value) => String(value ?? '').trim(), ...options }); export const gridFilterMultiCsv = (input, param, options = {}) => ({ input, param, normalize: normalizeMultiSelectValue, ...options }); export const gridFilterNumber = (input, param, options = {}) => ({ input, param, normalize: (value) => { const raw = String(value ?? '').trim(); if (raw === '') { return ''; } const parsed = Number(raw); return Number.isFinite(parsed) ? String(parsed) : ''; }, ...options }); export const gridFilterHidden = (input, param, options = {}) => ({ input, param, normalize: (value) => String(value ?? '').trim(), ...options }); const NORMALIZERS = { trim: (value) => String(value ?? '').trim(), trim_lower: (value) => String(value ?? '').trim().toLowerCase(), all_to_empty: (value) => { const normalized = String(value ?? '').trim(); return normalized === '' || normalized === 'all' ? '' : normalized; }, csv: normalizeMultiSelectValue }; export const gridFiltersFromSchema = (schema) => { if (!Array.isArray(schema)) { return []; } return schema.map((entry) => { if (!entry || typeof entry !== 'object') { return null; } const type = String(entry.type ?? '').trim().toLowerCase(); const input = entry.input ?? ''; const param = String(entry.param ?? '').trim(); if (!input || !param) { return null; } const options = {}; if ('default' in entry) { options.default = entry.default; } if (typeof entry.event === 'string' && entry.event.trim() !== '') { options.event = entry.event.trim(); } if (typeof entry.normalize === 'string' && NORMALIZERS[entry.normalize]) { options.normalize = NORMALIZERS[entry.normalize]; } if (type === 'date') { return gridFilterDate(input, param, options); } if (type === 'select') { return gridFilterSelect(input, param, options); } if (type === 'multi_csv') { return gridFilterMultiCsv(input, param, options); } if (type === 'number') { return gridFilterNumber(input, param, options); } if (type === 'hidden') { return gridFilterHidden(input, param, options); } if (type === 'text') { return gridFilterText(input, param, options); } return null; }).filter(Boolean); };