a1a3bd1b2b
- Fix recursive CTE: add missing RECURSIVE keyword in move_to_trash and restore_from_trash SQL queries (relation 'descendants' does not exist) - Fix folder deletion: delete descendant files before folder to avoid 'duplicate key violates unique constraint idx_files_unique_name_at_root' - Simplify trash model: only mark the folder as trashed, not child files (implicit trash via parent) - Update trash_items view: filter to show only top-level trashed items - Update schema.sql: change files.folder_id FK from ON DELETE SET NULL to ON DELETE CASCADE - Fix trash view icons: folders and files now show correct visual icons (folder-icon, pdf-icon, etc.) in trash view - Frontend refactoring: extract inline CSS/JS from admin.html and profile.html into dedicated external files - Frontend cleanup: replace all inline style attributes with CSS classes - Frontend cleanup: replace style.display JS - Fix recursive CTE: add missing RECURSIVE keyword in move_to_trash and restore_from_trash SQL queries (relation 'descendants' d
64 lines
2.0 KiB
JavaScript
64 lines
2.0 KiB
JavaScript
/**
|
|
* OxiCloud - Shared format and escaping utilities
|
|
* Centralized global helpers for date/size/text formatting and XSS-safe escaping.
|
|
*/
|
|
|
|
function escapeHtml(str) {
|
|
if (typeof str !== 'string') return '';
|
|
return str
|
|
.replace(/&/g, '&')
|
|
.replace(/</g, '<')
|
|
.replace(/>/g, '>')
|
|
.replace(/\"/g, '"')
|
|
.replace(/'/g, ''');
|
|
}
|
|
|
|
function formatFileSize(bytes) {
|
|
if (bytes === 0) return '0 Bytes';
|
|
|
|
const k = 1024;
|
|
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
|
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
|
|
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
|
}
|
|
|
|
function formatDateTime(value) {
|
|
if (!value) return '';
|
|
let dateValue;
|
|
if (value instanceof Date) {
|
|
dateValue = value;
|
|
} else if (typeof value === 'number') {
|
|
dateValue = new Date(value < 1e12 ? value * 1000 : value);
|
|
} else {
|
|
dateValue = new Date(value);
|
|
}
|
|
if (isNaN(dateValue.getTime())) return String(value);
|
|
return dateValue.toLocaleDateString() + ' ' +
|
|
dateValue.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
|
}
|
|
|
|
function formatDateShort(value) {
|
|
if (!value) return 'N/A';
|
|
const dateValue = typeof value === 'number' ? new Date(value * 1000) : new Date(value);
|
|
if (isNaN(dateValue.getTime())) return String(value);
|
|
return dateValue.toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric' });
|
|
}
|
|
|
|
function isTextViewable(mimeType) {
|
|
if (!mimeType) return false;
|
|
if (mimeType.startsWith('text/')) return true;
|
|
const textTypes = [
|
|
'application/json', 'application/xml', 'application/javascript',
|
|
'application/x-sh', 'application/x-yaml', 'application/toml',
|
|
'application/x-toml', 'application/sql',
|
|
];
|
|
return textTypes.includes(mimeType);
|
|
}
|
|
|
|
window.escapeHtml = escapeHtml;
|
|
window.formatFileSize = formatFileSize;
|
|
window.formatDateTime = formatDateTime;
|
|
window.formatDateShort = formatDateShort;
|
|
window.isTextViewable = isTextViewable;
|