1
0
Files
breadcrumb-the-shire/web/js/pages/app-list-utils.js

270 lines
7.3 KiB
JavaScript

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) => ({
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#39;'
}[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 `<span class="badge" data-variant="${badgeVariant}"${badgeTooltip}>${escapeHtml(labelText)}</span>`;
}).join(' ');
return `<div class="badge-list">${badges}</div>`;
};
export const postAction = async (url, csrf, data = {}) => {
const body = new URLSearchParams({ ...data, [csrf.key]: csrf.token });
return fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Accept': 'application/json',
'X-Requested-With': 'fetch'
},
body
});
};
export const badgeHtml = (gridjs, value = '') => {
const safe = value ?? '';
return gridjs.html(`<span class="badge" data-variant="neutral">${safe}</span>`);
};
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);
};