|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TextUtils { |
|
|
|
static truncate(text, maxLength, suffix = '...') { |
|
if (!text || text.length <= maxLength) return text; |
|
return text.substring(0, maxLength - suffix.length) + suffix; |
|
} |
|
|
|
|
|
static wordCount(text) { |
|
if (!text) return 0; |
|
return text.trim().split(/\s+/).filter(word => word.length > 0).length; |
|
} |
|
|
|
|
|
static estimateReadingTime(text) { |
|
const words = this.wordCount(text); |
|
const minutes = Math.ceil(words / 200); |
|
return minutes; |
|
} |
|
|
|
|
|
static normalize(text) { |
|
if (!text) return ''; |
|
return text |
|
.trim() |
|
.replace(/\s+/g, ' ') |
|
.replace(/[^\w\s.,!?;:-]/g, ''); |
|
} |
|
|
|
|
|
static extractHashtags(text) { |
|
const hashtagRegex = /#[\w]+/g; |
|
return text.match(hashtagRegex) || []; |
|
} |
|
|
|
|
|
static extractMentions(text) { |
|
const mentionRegex = /@[\w]+/g; |
|
return text.match(mentionRegex) || []; |
|
} |
|
|
|
|
|
static slugify(text) { |
|
return text |
|
.toLowerCase() |
|
.trim() |
|
.replace(/[^\w\s-]/g, '') |
|
.replace(/[\s_-]+/g, '-') |
|
.replace(/^-+|-+$/g, ''); |
|
} |
|
|
|
|
|
static highlight(text, searchTerm, className = 'highlight') { |
|
if (!searchTerm) return text; |
|
const regex = new RegExp(`(${searchTerm})`, 'gi'); |
|
return text.replace(regex, `<span class="${className}">$1</span>`); |
|
} |
|
|
|
|
|
static stripHtml(html) { |
|
const div = document.createElement('div'); |
|
div.innerHTML = html; |
|
return div.textContent || div.innerText || ''; |
|
} |
|
|
|
|
|
static simpleMarkdown(text) { |
|
return text |
|
.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>') |
|
.replace(/\*(.*?)\*/g, '<em>$1</em>') |
|
.replace(/`(.*?)`/g, '<code>$1</code>') |
|
.replace(/\n/g, '<br>'); |
|
} |
|
} |
|
|
|
|
|
class DateUtils { |
|
|
|
static formatRelative(date) { |
|
const now = new Date(); |
|
const diffInSeconds = Math.floor((now - date) / 1000); |
|
|
|
if (diffInSeconds < 60) return 'just now'; |
|
if (diffInSeconds < 3600) return `${Math.floor(diffInSeconds / 60)} minutes ago`; |
|
if (diffInSeconds < 86400) return `${Math.floor(diffInSeconds / 3600)} hours ago`; |
|
if (diffInSeconds < 604800) return `${Math.floor(diffInSeconds / 86400)} days ago`; |
|
|
|
return date.toLocaleDateString(); |
|
} |
|
|
|
|
|
static formatDisplay(date, options = {}) { |
|
const defaultOptions = { |
|
year: 'numeric', |
|
month: 'short', |
|
day: 'numeric', |
|
hour: '2-digit', |
|
minute: '2-digit' |
|
}; |
|
|
|
return date.toLocaleDateString(undefined, { ...defaultOptions, ...options }); |
|
} |
|
|
|
|
|
static startOfDay(date = new Date()) { |
|
const start = new Date(date); |
|
start.setHours(0, 0, 0, 0); |
|
return start; |
|
} |
|
|
|
|
|
static endOfDay(date = new Date()) { |
|
const end = new Date(date); |
|
end.setHours(23, 59, 59, 999); |
|
return end; |
|
} |
|
|
|
|
|
static isToday(date) { |
|
const today = new Date(); |
|
return this.startOfDay(date).getTime() === this.startOfDay(today).getTime(); |
|
} |
|
|
|
|
|
static isYesterday(date) { |
|
const yesterday = new Date(); |
|
yesterday.setDate(yesterday.getDate() - 1); |
|
return this.startOfDay(date).getTime() === this.startOfDay(yesterday).getTime(); |
|
} |
|
|
|
|
|
static getTimezoneOffset() { |
|
return new Date().getTimezoneOffset(); |
|
} |
|
|
|
|
|
static parseDate(dateString) { |
|
|
|
const formats = [ |
|
/^\d{4}-\d{2}-\d{2}$/, |
|
/^\d{2}\/\d{2}\/\d{4}$/, |
|
/^\d{2}-\d{2}-\d{4}$/, |
|
]; |
|
|
|
for (const format of formats) { |
|
if (format.test(dateString)) { |
|
const date = new Date(dateString); |
|
if (!isNaN(date.getTime())) { |
|
return date; |
|
} |
|
} |
|
} |
|
|
|
return null; |
|
} |
|
} |
|
|
|
|
|
class StorageUtils { |
|
|
|
static safeJsonParse(str, fallback = null) { |
|
try { |
|
return JSON.parse(str); |
|
} catch (error) { |
|
console.warn('Failed to parse JSON:', error); |
|
return fallback; |
|
} |
|
} |
|
|
|
|
|
static safeJsonStringify(obj, fallback = '{}') { |
|
try { |
|
return JSON.stringify(obj); |
|
} catch (error) { |
|
console.warn('Failed to stringify JSON:', error); |
|
return fallback; |
|
} |
|
} |
|
|
|
|
|
static getItem(key, fallback = null) { |
|
try { |
|
const item = localStorage.getItem(key); |
|
return item !== null ? this.safeJsonParse(item, fallback) : fallback; |
|
} catch (error) { |
|
console.warn('Failed to get localStorage item:', error); |
|
return fallback; |
|
} |
|
} |
|
|
|
|
|
static setItem(key, value) { |
|
try { |
|
localStorage.setItem(key, this.safeJsonStringify(value)); |
|
return true; |
|
} catch (error) { |
|
console.warn('Failed to set localStorage item:', error); |
|
return false; |
|
} |
|
} |
|
|
|
|
|
static removeItem(key) { |
|
try { |
|
localStorage.removeItem(key); |
|
return true; |
|
} catch (error) { |
|
console.warn('Failed to remove localStorage item:', error); |
|
return false; |
|
} |
|
} |
|
|
|
|
|
static clear() { |
|
try { |
|
localStorage.clear(); |
|
return true; |
|
} catch (error) { |
|
console.warn('Failed to clear localStorage:', error); |
|
return false; |
|
} |
|
} |
|
|
|
|
|
static getStorageUsage() { |
|
let total = 0; |
|
for (let key in localStorage) { |
|
if (localStorage.hasOwnProperty(key)) { |
|
total += localStorage[key].length + key.length; |
|
} |
|
} |
|
return total; |
|
} |
|
|
|
|
|
static isAvailable() { |
|
try { |
|
const test = '__storage_test__'; |
|
localStorage.setItem(test, test); |
|
localStorage.removeItem(test); |
|
return true; |
|
} catch (error) { |
|
return false; |
|
} |
|
} |
|
} |
|
|
|
|
|
class EventUtils { |
|
|
|
static debounce(func, wait, immediate = false) { |
|
let timeout; |
|
return function executedFunction(...args) { |
|
const later = () => { |
|
timeout = null; |
|
if (!immediate) func.apply(this, args); |
|
}; |
|
const callNow = immediate && !timeout; |
|
clearTimeout(timeout); |
|
timeout = setTimeout(later, wait); |
|
if (callNow) func.apply(this, args); |
|
}; |
|
} |
|
|
|
|
|
static throttle(func, limit) { |
|
let inThrottle; |
|
return function executedFunction(...args) { |
|
if (!inThrottle) { |
|
func.apply(this, args); |
|
inThrottle = true; |
|
setTimeout(() => inThrottle = false, limit); |
|
} |
|
}; |
|
} |
|
|
|
|
|
static addListener(element, event, handler, options = {}) { |
|
element.addEventListener(event, handler, options); |
|
return () => element.removeEventListener(event, handler, options); |
|
} |
|
|
|
|
|
static createEmitter() { |
|
const listeners = new Map(); |
|
|
|
return { |
|
on(event, callback) { |
|
if (!listeners.has(event)) { |
|
listeners.set(event, []); |
|
} |
|
listeners.get(event).push(callback); |
|
}, |
|
|
|
off(event, callback) { |
|
const eventListeners = listeners.get(event); |
|
if (eventListeners) { |
|
const index = eventListeners.indexOf(callback); |
|
if (index > -1) { |
|
eventListeners.splice(index, 1); |
|
} |
|
} |
|
}, |
|
|
|
emit(event, data) { |
|
const eventListeners = listeners.get(event); |
|
if (eventListeners) { |
|
eventListeners.forEach(callback => callback(data)); |
|
} |
|
}, |
|
|
|
once(event, callback) { |
|
const onceWrapper = (data) => { |
|
callback(data); |
|
this.off(event, onceWrapper); |
|
}; |
|
this.on(event, onceWrapper); |
|
} |
|
}; |
|
} |
|
|
|
|
|
static isInViewport(element) { |
|
const rect = element.getBoundingClientRect(); |
|
return ( |
|
rect.top >= 0 && |
|
rect.left >= 0 && |
|
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && |
|
rect.right <= (window.innerWidth || document.documentElement.clientWidth) |
|
); |
|
} |
|
|
|
|
|
static scrollToElement(element, options = {}) { |
|
const defaultOptions = { |
|
behavior: 'smooth', |
|
block: 'start', |
|
inline: 'nearest' |
|
}; |
|
|
|
element.scrollIntoView({ ...defaultOptions, ...options }); |
|
} |
|
} |
|
|
|
|
|
class PerformanceUtils { |
|
|
|
static measureTime(func, ...args) { |
|
const start = performance.now(); |
|
const result = func.apply(this, args); |
|
const end = performance.now(); |
|
|
|
console.log(`Function executed in ${end - start} milliseconds`); |
|
return result; |
|
} |
|
|
|
|
|
static async measureAsyncTime(func, ...args) { |
|
const start = performance.now(); |
|
const result = await func.apply(this, args); |
|
const end = performance.now(); |
|
|
|
console.log(`Async function executed in ${end - start} milliseconds`); |
|
return result; |
|
} |
|
|
|
|
|
static observePerformance(callback) { |
|
if ('PerformanceObserver' in window) { |
|
const observer = new PerformanceObserver(callback); |
|
observer.observe({ entryTypes: ['measure', 'navigation', 'resource'] }); |
|
return observer; |
|
} |
|
return null; |
|
} |
|
|
|
|
|
static getMemoryInfo() { |
|
if (performance.memory) { |
|
return { |
|
used: Math.round(performance.memory.usedJSHeapSize / 1048576), |
|
total: Math.round(performance.memory.totalJSHeapSize / 1048576), |
|
limit: Math.round(performance.memory.jsHeapSizeLimit / 1048576) |
|
}; |
|
} |
|
return null; |
|
} |
|
|
|
|
|
static onIdle(callback, options = {}) { |
|
if ('requestIdleCallback' in window) { |
|
return requestIdleCallback(callback, options); |
|
} else { |
|
return setTimeout(callback, 0); |
|
} |
|
} |
|
|
|
|
|
static cancelIdle(id) { |
|
if ('cancelIdleCallback' in window) { |
|
cancelIdleCallback(id); |
|
} else { |
|
clearTimeout(id); |
|
} |
|
} |
|
} |
|
|
|
|
|
class ValidationUtils { |
|
|
|
static isValidEmail(email) { |
|
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; |
|
return emailRegex.test(email); |
|
} |
|
|
|
|
|
static isValidUrl(url) { |
|
try { |
|
new URL(url); |
|
return true; |
|
} catch { |
|
return false; |
|
} |
|
} |
|
|
|
|
|
static isEmpty(str) { |
|
return !str || str.trim().length === 0; |
|
} |
|
|
|
|
|
static isValidMessage(content) { |
|
if (typeof content !== 'string') return false; |
|
if (this.isEmpty(content)) return false; |
|
if (content.length > 10000) return false; |
|
return true; |
|
} |
|
|
|
|
|
static containsProfanity(text) { |
|
const profanityList = ['spam', 'scam']; |
|
const lowerText = text.toLowerCase(); |
|
return profanityList.some(word => lowerText.includes(word)); |
|
} |
|
|
|
|
|
static isValidJson(str) { |
|
try { |
|
JSON.parse(str); |
|
return true; |
|
} catch { |
|
return false; |
|
} |
|
} |
|
} |
|
|
|
|
|
class RandomUtils { |
|
|
|
static generateId(length = 8) { |
|
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; |
|
let result = ''; |
|
for (let i = 0; i < length; i++) { |
|
result += chars.charAt(Math.floor(Math.random() * chars.length)); |
|
} |
|
return result; |
|
} |
|
|
|
|
|
static generateUuid() { |
|
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { |
|
const r = Math.random() * 16 | 0; |
|
const v = c == 'x' ? r : (r & 0x3 | 0x8); |
|
return v.toString(16); |
|
}); |
|
} |
|
|
|
|
|
static randomElement(array) { |
|
return array[Math.floor(Math.random() * array.length)]; |
|
} |
|
|
|
|
|
static shuffle(array) { |
|
const shuffled = [...array]; |
|
for (let i = shuffled.length - 1; i > 0; i--) { |
|
const j = Math.floor(Math.random() * (i + 1)); |
|
[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]]; |
|
} |
|
return shuffled; |
|
} |
|
|
|
|
|
static randomColor() { |
|
return `#${Math.floor(Math.random()*16777215).toString(16)}`; |
|
} |
|
} |
|
|
|
|
|
const Utils = { |
|
Text: TextUtils, |
|
Date: DateUtils, |
|
Storage: StorageUtils, |
|
Event: EventUtils, |
|
Performance: PerformanceUtils, |
|
Validation: ValidationUtils, |
|
Random: RandomUtils |
|
}; |
|
|
|
|
|
window.Utils = Utils; |
|
|
|
|
|
if (typeof module !== 'undefined' && module.exports) { |
|
module.exports = Utils; |
|
} |