style(ui): request that all types defined

- check in more restrictive mode = request types
- define main types in static/js/core/types.js
This commit is contained in:
Edouard Vanbelle
2026-04-30 02:02:27 +02:00
parent 97ea87efc2
commit 3050556dc0
29 changed files with 560 additions and 345 deletions
+43 -11
View File
@@ -3,11 +3,21 @@
* Centralized global helpers for date/size/text formatting and XSS-safe escaping.
*/
/**
*
* @param {string} str
* @returns {string}
*/
function escapeHtml(str) {
if (typeof str !== 'string') return '';
return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#039;');
}
/**
*
* @param {number} bytes
* @returns {string}
*/
function formatFileSize(bytes) {
if (bytes === 0) return '0 Bytes';
@@ -19,11 +29,21 @@ function formatFileSize(bytes) {
}
/// Formats a byte count for quota display. When bytes is 0, returns "∞" (unlimited).
/**
*
* @param {number} bytes
* @returns {string}
*/
function formatQuotaSize(bytes) {
if (bytes === 0) return '∞';
return formatFileSize(bytes);
}
/**
*
* @param {Date | number| null} value
* @returns {string}
*/
function formatDateTime(value) {
if (!value) return '';
let dateValue;
@@ -38,6 +58,11 @@ function formatDateTime(value) {
return `${dateValue.toLocaleDateString()} ${dateValue.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}`;
}
/**
*
* @param {Date | number| null} value
* @returns {string}
*/
function formatDateShort(value) {
if (!value) return 'N/A';
const dateValue = typeof value === 'number' ? new Date(value * 1000) : new Date(value);
@@ -49,20 +74,27 @@ function formatDateShort(value) {
});
}
const TEXT_TYPES = [
'application/json',
'application/xml',
'application/javascript',
'application/x-sh',
'application/x-yaml',
'application/toml',
'application/x-toml',
'application/sql'
];
// FIXME: move is to another file
/**
*
* @param {string} mimeType
* @returns {boolean}
*/
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);
return TEXT_TYPES.includes(mimeType);
}
export { escapeHtml, formatDateShort, formatDateTime, formatFileSize, formatQuotaSize, isTextViewable };
+1
View File
@@ -18,6 +18,7 @@ if (!supportedLocales.includes(currentLocale)) {
}
// Cache for translations
/** @type {Record<string, Object>} */
const translations = {};
/**
+1 -1
View File
@@ -450,7 +450,7 @@ function replaceIconsInElement(container) {
if (!container) container = document.body;
const icons = container.querySelectorAll('i[class*="fa-"]');
for (let i = 0; i < icons.length; i++) {
const el = icons[i];
const el = /** @type {HTMLElement} */ (icons[i]);
const classes = el.className.split(/\s+/);
// Find the icon name (fa-xxx)
+2 -2
View File
@@ -96,7 +96,7 @@ function createLanguageSelector(containerId = 'language-selector') {
option.className = `language-option${lang.code === currentLocale ? ' active' : ''}`;
option.setAttribute('role', 'option');
option.setAttribute('data-lang', lang.code);
option.setAttribute('aria-selected', lang.code === currentLocale);
option.setAttribute('aria-selected', String(lang.code === currentLocale));
option.innerHTML = `
<span class="lang-flag">${lang.flag}</span>
<span class="lang-name">${lang.name}</span>
@@ -134,7 +134,7 @@ function createLanguageSelector(containerId = 'language-selector') {
// Close dropdown when clicking outside
document.addEventListener('click', (e) => {
if (!container.contains(e.target)) {
if (!container.contains(/** @type {Node | null } */ (e.target))) {
closeDropdown(container);
}
});
+4 -4
View File
@@ -53,9 +53,9 @@ const Modal = {
this.closeBtn = document.getElementById('modal-close-btn');
// Event listeners
this.cancelBtn.addEventListener('click', () => this.close(false));
this.closeBtn.addEventListener('click', () => this.close(false));
this.confirmBtn.addEventListener('click', () => this.confirm());
this.cancelBtn?.addEventListener('click', () => this.close(false));
this.closeBtn?.addEventListener('click', () => this.close(false));
this.confirmBtn?.addEventListener('click', () => this.confirm());
// Close on overlay click
this.overlay.addEventListener('click', (e) => {
@@ -65,7 +65,7 @@ const Modal = {
});
// Handle Enter and Escape keys
this.input.addEventListener('keydown', (e) => {
this.input?.addEventListener('keydown', (e) => {
if (e.key === 'Enter') {
e.preventDefault();
this.confirm();
+5 -5
View File
@@ -34,7 +34,7 @@ const notifications = (() => {
bellBtn.addEventListener('click', (e) => {
e.stopPropagation();
const open = wrapper.classList.toggle('open');
const open = wrapper?.classList.toggle('open');
bellBtn.classList.toggle('active', open);
// Close user-menu if it's open
@@ -46,7 +46,7 @@ const notifications = (() => {
// Close on outside click
document.addEventListener('click', (e) => {
if (!wrapper.contains(e.target)) {
if (!wrapper?.contains(/** @type {Node | null} */ (e.target))) {
close();
}
});
@@ -63,8 +63,8 @@ const notifications = (() => {
function close() {
const bellBtn = $('notif-bell-btn');
const wrapper = $('notif-wrapper');
wrapper.classList.remove('open');
bellBtn.classList.remove('active');
wrapper?.classList.remove('open');
bellBtn?.classList.remove('active');
}
/* ── badge helpers ──────────────────────────────────────── */
@@ -82,7 +82,7 @@ const notifications = (() => {
if (!badge) return;
if (_badgeCount > 0) {
badge.classList.remove('hidden');
badge.textContent = _badgeCount > 99 ? '99+' : _badgeCount;
badge.textContent = _badgeCount > 99 ? '99+' : String(_badgeCount);
} else {
badge.classList.add('hidden');
}
+55
View File
@@ -0,0 +1,55 @@
/**
* @typedef {Object} FolderInfo
* @property {string} category
* @property {number} created_at - timestamp
* @property {string} icon_class
* @property {string} icon_special_class
* @property {string} id the uniq id of the folder
* @property {boolean} is_root
* @property {number} modified_at
* @property {string} name
* @property {string} owner_id
* @property {string|null} parent_id the folder parent (null if is_root)
* @property {string} path the full path
*/
/**
* @typedef {Object} FileInfo
* @property {string} category
* @property {number} created_at - timestamp
* @property {string} icon_class
* @property {string} icon_special_class
* @property {string} id the uniq id of the folder
* @property {string} mime_type
* @property {number} modified_at - timestamp
* @property {string} name
* @property {string} owner_id
* @property {string} folder_id the folder parent
* @property {string} path the full path
* @property {number} size
* @property {string} size_formatted
* @property {number} sort_date
*/
/**
* @typedef {Object} SharePermissions
* @property {boolean} read
* @property {boolean} reshare
* @property {boolean} write
*/
/**
* @typedef {Object} Share
* @property {number} access_count
* @property {number} created_at - timestamp
* @property {String} created_by
* @property {number} expires_at - timestamp
* @property {boolean} has_password
* @property {string} id
* @property {string} item_id
* @property {string} item_name
* @property {string} item_type
* @property {SharePermissions} permissions
* @property {string | null} token
* @property {string} url
*/