- Implemented PayPal webhook for handling payment notifications, including signature verification and transaction updates. - Created invoice generation and license management for software upgrades upon successful payment. - Added comprehensive logging for debugging purposes. - Introduced new CSS styles for the marketing file management system, including layout, toolbar, breadcrumb navigation, search filters, and file management UI components.
900 lines
31 KiB
JavaScript
900 lines
31 KiB
JavaScript
/**
|
|
* Marketing File Management System
|
|
* Professional drag-and-drop upload with folder management and tagging
|
|
*/
|
|
|
|
class MarketingFileManager {
|
|
constructor() {
|
|
this.currentFolder = '';
|
|
this.selectedFiles = [];
|
|
this.uploadQueue = [];
|
|
this.viewMode = 'grid';
|
|
this.filters = {
|
|
search: '',
|
|
tag: '',
|
|
fileTypes: []
|
|
};
|
|
this.folders = []; // Store folders data
|
|
this.loadRequestId = 0; // Track the latest load request
|
|
|
|
this.init();
|
|
}
|
|
|
|
init() {
|
|
this.bindEvents();
|
|
this.loadFolders();
|
|
this.loadTags();
|
|
this.loadFiles();
|
|
this.setupDragAndDrop();
|
|
}
|
|
|
|
bindEvents() {
|
|
// Upload modal
|
|
document.getElementById('uploadBtn')?.addEventListener('click', () => {
|
|
this.showUploadModal();
|
|
});
|
|
|
|
// Create folder modal
|
|
document.getElementById('createFolderBtn')?.addEventListener('click', () => {
|
|
this.showFolderModal();
|
|
});
|
|
|
|
// View mode toggle
|
|
document.getElementById('gridViewBtn')?.addEventListener('click', () => {
|
|
this.setViewMode('grid');
|
|
});
|
|
|
|
document.getElementById('listViewBtn')?.addEventListener('click', () => {
|
|
this.setViewMode('list');
|
|
});
|
|
|
|
// Search
|
|
document.getElementById('searchInput')?.addEventListener('input', (e) => {
|
|
this.filters.search = e.target.value;
|
|
this.debounce(this.loadFiles.bind(this), 300)();
|
|
});
|
|
|
|
// Tag filter
|
|
document.getElementById('tagFilter')?.addEventListener('change', (e) => {
|
|
this.filters.tag = e.target.value;
|
|
this.loadFiles();
|
|
});
|
|
|
|
// File type filters
|
|
document.querySelectorAll('.file-type-filters input[type="checkbox"]').forEach(checkbox => {
|
|
checkbox.addEventListener('change', () => {
|
|
this.updateFileTypeFilters();
|
|
});
|
|
});
|
|
|
|
// Modal events
|
|
this.bindModalEvents();
|
|
|
|
// Upload events
|
|
this.bindUploadEvents();
|
|
}
|
|
|
|
bindModalEvents() {
|
|
// Close modals
|
|
document.querySelectorAll('.modal-close, .modal-cancel').forEach(btn => {
|
|
btn.addEventListener('click', (e) => {
|
|
this.closeModal(e.target.closest('.modal'));
|
|
});
|
|
});
|
|
|
|
// Create folder
|
|
document.getElementById('createFolder')?.addEventListener('click', () => {
|
|
this.createFolder();
|
|
});
|
|
|
|
// Download file
|
|
document.getElementById('downloadFile')?.addEventListener('click', () => {
|
|
if (this.selectedFile) {
|
|
this.downloadFile(this.selectedFile);
|
|
}
|
|
});
|
|
|
|
// Delete file
|
|
document.getElementById('deleteFile')?.addEventListener('click', () => {
|
|
if (this.selectedFile) {
|
|
this.deleteFile(this.selectedFile);
|
|
}
|
|
});
|
|
}
|
|
|
|
bindUploadEvents() {
|
|
const fileInput = document.getElementById('fileInput');
|
|
const browseBtn = document.getElementById('browseBtn');
|
|
const startUpload = document.getElementById('startUpload');
|
|
|
|
browseBtn?.addEventListener('click', () => {
|
|
fileInput.click();
|
|
});
|
|
|
|
fileInput?.addEventListener('change', (e) => {
|
|
this.handleFileSelect(e.target.files);
|
|
});
|
|
|
|
startUpload?.addEventListener('click', () => {
|
|
this.startUpload();
|
|
});
|
|
}
|
|
|
|
setupDragAndDrop() {
|
|
const uploadArea = document.getElementById('uploadArea');
|
|
const filesContainer = document.getElementById('filesContainer');
|
|
|
|
if (uploadArea) {
|
|
uploadArea.addEventListener('dragover', this.handleDragOver);
|
|
uploadArea.addEventListener('drop', (e) => this.handleDrop(e));
|
|
}
|
|
|
|
if (filesContainer) {
|
|
filesContainer.addEventListener('dragover', this.handleDragOver);
|
|
filesContainer.addEventListener('drop', (e) => this.handleDrop(e));
|
|
}
|
|
}
|
|
|
|
handleDragOver(e) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
e.currentTarget.classList.add('drag-over');
|
|
}
|
|
|
|
handleDrop(e) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
e.currentTarget.classList.remove('drag-over');
|
|
|
|
const files = e.dataTransfer.files;
|
|
if (files.length > 0) {
|
|
this.showUploadModal();
|
|
this.handleFileSelect(files);
|
|
}
|
|
}
|
|
|
|
async loadFolders() {
|
|
try {
|
|
const response = await fetch('index.php?page=marketing&action=marketing_folders&tree=true', { cache: 'no-store' });
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
}
|
|
|
|
const text = await response.text();
|
|
if (!text || text.trim() === '') {
|
|
console.warn('Empty response from folders API');
|
|
this.folders = [];
|
|
this.renderFolderTree([]);
|
|
this.populateFolderSelects([]);
|
|
return;
|
|
}
|
|
|
|
const data = JSON.parse(text);
|
|
|
|
this.folders = data || []; // Store the folders data
|
|
// Always render the folder tree (at minimum shows Root)
|
|
this.renderFolderTree(this.folders);
|
|
this.populateFolderSelects(this.folders);
|
|
} catch (error) {
|
|
console.error('Error loading folders:', error);
|
|
this.folders = [];
|
|
// Show at least root folder on error
|
|
this.renderFolderTree([]);
|
|
this.populateFolderSelects([]);
|
|
}
|
|
}
|
|
|
|
async loadTags() {
|
|
try {
|
|
const response = await fetch('index.php?page=marketing&action=marketing_tags&used_only=true', { cache: 'no-store' });
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
}
|
|
|
|
const text = await response.text();
|
|
if (!text || text.trim() === '') {
|
|
console.warn('Empty response from tags API');
|
|
this.populateTagFilter([]);
|
|
return;
|
|
}
|
|
|
|
const data = JSON.parse(text);
|
|
|
|
// Always populate tag filter (at minimum shows "All Tags")
|
|
this.populateTagFilter(data || []);
|
|
} catch (error) {
|
|
console.error('Error loading tags:', error);
|
|
// Show empty tag filter on error
|
|
this.populateTagFilter([]);
|
|
}
|
|
}
|
|
|
|
async loadFiles() {
|
|
const container = document.getElementById('filesContainer');
|
|
const loading = document.getElementById('loadingIndicator');
|
|
const emptyState = document.getElementById('emptyState');
|
|
|
|
// Increment request ID to invalidate previous requests
|
|
const requestId = ++this.loadRequestId;
|
|
|
|
// Clear container FIRST to prevent showing old files
|
|
container.innerHTML = '';
|
|
loading.style.display = 'block';
|
|
emptyState.style.display = 'none';
|
|
|
|
try {
|
|
// Use proper folder ID (null for root, or the folder ID)
|
|
const folderId = this.currentFolder ? this.currentFolder : 'null';
|
|
// Add cache busting to prevent browser caching
|
|
let url = `index.php?page=marketing&action=marketing_files&folder_id=${folderId}&limit=50&_t=${Date.now()}`;
|
|
|
|
if (this.filters.search) {
|
|
url += `&search=${encodeURIComponent(this.filters.search)}`;
|
|
}
|
|
|
|
if (this.filters.tag) {
|
|
url += `&tag=${encodeURIComponent(this.filters.tag)}`;
|
|
}
|
|
|
|
if (this.filters.fileTypes.length > 0) {
|
|
// API expects individual file_type parameter, so we'll filter client-side for now
|
|
}
|
|
|
|
const response = await fetch(url, { cache: 'no-store' });
|
|
|
|
// Ignore response if a newer request was made
|
|
if (requestId !== this.loadRequestId) {
|
|
return;
|
|
}
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
}
|
|
|
|
const text = await response.text();
|
|
|
|
if (!text || text.trim() === '') {
|
|
console.warn('Empty response from files API');
|
|
emptyState.style.display = 'block';
|
|
return;
|
|
}
|
|
|
|
const data = JSON.parse(text);
|
|
|
|
if (data && data.length > 0) {
|
|
let files = data;
|
|
|
|
// Client-side file type filtering
|
|
if (this.filters.fileTypes.length > 0) {
|
|
files = files.filter(file =>
|
|
this.filters.fileTypes.includes(file.file_type.toLowerCase())
|
|
);
|
|
}
|
|
|
|
if (files.length === 0) {
|
|
emptyState.style.display = 'block';
|
|
} else {
|
|
this.renderFiles(files);
|
|
}
|
|
} else {
|
|
emptyState.style.display = 'block';
|
|
}
|
|
} catch (error) {
|
|
console.error('Error loading files:', error);
|
|
this.showToast('Error loading files', 'error');
|
|
} finally {
|
|
loading.style.display = 'none';
|
|
}
|
|
}
|
|
|
|
renderFolderTree(folders, container = null, level = 0) {
|
|
if (!container) {
|
|
container = document.getElementById('folderTree');
|
|
container.innerHTML = '<div class="folder-item root" data-folder=""><i class="fa fa-home"></i> Root</div>';
|
|
|
|
// Add click listener to root folder
|
|
const rootFolder = container.querySelector('.folder-item.root');
|
|
if (rootFolder) {
|
|
rootFolder.addEventListener('click', () => {
|
|
this.selectFolder('');
|
|
});
|
|
}
|
|
}
|
|
|
|
folders.forEach(folder => {
|
|
const folderItem = document.createElement('div');
|
|
folderItem.className = 'folder-item';
|
|
folderItem.setAttribute('data-folder', folder.id);
|
|
folderItem.style.marginLeft = `${level * 20}px`;
|
|
|
|
const hasChildren = folder.children && folder.children.length > 0;
|
|
const expandIcon = hasChildren ? '<i class="fa fa-chevron-right expand-icon"></i>' : '';
|
|
|
|
folderItem.innerHTML = `
|
|
${expandIcon}
|
|
<i class="fa fa-folder"></i>
|
|
<span class="folder-name">${this.escapeHtml(folder.folder_name)}</span>
|
|
<span class="file-count">(${folder.file_count})</span>
|
|
`;
|
|
|
|
folderItem.addEventListener('click', () => {
|
|
this.selectFolder(folder.id);
|
|
});
|
|
|
|
container.appendChild(folderItem);
|
|
|
|
if (hasChildren) {
|
|
this.renderFolderTree(folder.children, container, level + 1);
|
|
}
|
|
});
|
|
}
|
|
|
|
renderFiles(files) {
|
|
const container = document.getElementById('filesContainer');
|
|
container.innerHTML = '';
|
|
|
|
files.forEach(file => {
|
|
const fileElement = this.createFileElement(file);
|
|
container.appendChild(fileElement);
|
|
});
|
|
}
|
|
|
|
createFileElement(file) {
|
|
const fileElement = document.createElement('div');
|
|
fileElement.className = `file-item ${this.viewMode}-item`;
|
|
fileElement.setAttribute('data-file-id', file.id);
|
|
|
|
const thumbnail = this.getThumbnail(file);
|
|
const tags = file.tags.map(tag => `<span class="tag">${this.escapeHtml(tag)}</span>`).join('');
|
|
|
|
fileElement.innerHTML = `
|
|
<div class="file-thumbnail">
|
|
${thumbnail}
|
|
<div class="file-overlay">
|
|
<button class="preview-btn" title="Preview">
|
|
<i class="fa fa-eye"></i>
|
|
</button>
|
|
<button class="download-btn" title="Download">
|
|
<i class="fa fa-download"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="file-info">
|
|
<div class="file-name" title="${this.escapeHtml(file.original_filename)}">
|
|
${this.escapeHtml(file.title || file.original_filename)}
|
|
</div>
|
|
<div class="file-meta">
|
|
<span class="file-size">${file.file_size_formatted}</span>
|
|
<span class="file-type">.${file.file_type.toUpperCase()}</span>
|
|
<span class="file-date">${this.formatDate(file.created)}</span>
|
|
</div>
|
|
<div class="file-tags">
|
|
${tags}
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
// Bind events
|
|
fileElement.querySelector('.preview-btn').addEventListener('click', () => {
|
|
this.previewFile(file);
|
|
});
|
|
|
|
fileElement.querySelector('.download-btn').addEventListener('click', () => {
|
|
this.downloadFile(file);
|
|
});
|
|
|
|
fileElement.addEventListener('dblclick', () => {
|
|
this.previewFile(file);
|
|
});
|
|
|
|
return fileElement;
|
|
}
|
|
|
|
getThumbnail(file) {
|
|
const isImage = ['jpg', 'jpeg', 'png', 'gif', 'webp'].includes(file.file_type.toLowerCase());
|
|
|
|
if (isImage && file.thumbnail_path) {
|
|
return `<img src="${file.thumbnail_path}" alt="${this.escapeHtml(file.title)}" class="thumbnail-img">`;
|
|
}
|
|
|
|
// File type icons
|
|
const iconMap = {
|
|
pdf: 'fa-file-pdf',
|
|
doc: 'fa-file-word',
|
|
docx: 'fa-file-word',
|
|
xls: 'fa-file-excel',
|
|
xlsx: 'fa-file-excel',
|
|
mp4: 'fa-file-video',
|
|
mov: 'fa-file-video',
|
|
avi: 'fa-file-video'
|
|
};
|
|
|
|
const iconClass = iconMap[file.file_type.toLowerCase()] || 'fa-file';
|
|
|
|
return `<div class="file-icon"><i class="fa ${iconClass}"></i></div>`;
|
|
}
|
|
|
|
showUploadModal() {
|
|
const modal = document.getElementById('uploadModal');
|
|
this.showModal(modal);
|
|
this.populateUploadFolders(this.folders); // Use stored folders data
|
|
}
|
|
|
|
showFolderModal() {
|
|
const modal = document.getElementById('folderModal');
|
|
this.showModal(modal);
|
|
this.populateParentFolders(this.folders); // Use stored folders data
|
|
}
|
|
|
|
showModal(modal) {
|
|
modal.style.display = 'flex';
|
|
modal.classList.add('show');
|
|
document.body.classList.add('modal-open');
|
|
}
|
|
|
|
closeModal(modal) {
|
|
modal.classList.remove('show');
|
|
setTimeout(() => {
|
|
modal.style.display = 'none';
|
|
document.body.classList.remove('modal-open');
|
|
}, 300);
|
|
}
|
|
|
|
handleFileSelect(files) {
|
|
this.uploadQueue = [];
|
|
|
|
Array.from(files).forEach(file => {
|
|
this.uploadQueue.push({
|
|
file: file,
|
|
progress: 0,
|
|
status: 'pending'
|
|
});
|
|
});
|
|
|
|
this.renderUploadQueue();
|
|
document.getElementById('startUpload').disabled = this.uploadQueue.length === 0;
|
|
}
|
|
|
|
renderUploadQueue() {
|
|
const container = document.getElementById('uploadQueue');
|
|
container.innerHTML = '';
|
|
|
|
this.uploadQueue.forEach((item, index) => {
|
|
const queueItem = document.createElement('div');
|
|
queueItem.className = 'upload-item';
|
|
queueItem.innerHTML = `
|
|
<div class="upload-info">
|
|
<div class="file-name">${this.escapeHtml(item.file.name)}</div>
|
|
<div class="file-size">${this.formatFileSize(item.file.size)}</div>
|
|
</div>
|
|
<div class="upload-progress">
|
|
<div class="progress-bar">
|
|
<div class="progress-fill" style="width: ${item.progress}%"></div>
|
|
</div>
|
|
<div class="upload-status">${item.status}</div>
|
|
</div>
|
|
<button class="remove-btn" data-index="${index}">
|
|
<i class="fa fa-times"></i>
|
|
</button>
|
|
`;
|
|
|
|
queueItem.querySelector('.remove-btn').addEventListener('click', () => {
|
|
this.removeFromQueue(index);
|
|
});
|
|
|
|
container.appendChild(queueItem);
|
|
});
|
|
}
|
|
|
|
async startUpload() {
|
|
const folder = document.getElementById('uploadFolder').value;
|
|
const tags = document.getElementById('uploadTags').value
|
|
.split(',')
|
|
.map(tag => tag.trim())
|
|
.filter(tag => tag.length > 0);
|
|
|
|
for (let i = 0; i < this.uploadQueue.length; i++) {
|
|
const item = this.uploadQueue[i];
|
|
await this.uploadFile(item, folder, tags, i);
|
|
}
|
|
|
|
// Switch to the uploaded folder if different from current
|
|
if (folder && folder !== this.currentFolder) {
|
|
this.currentFolder = folder;
|
|
}
|
|
|
|
this.loadFiles();
|
|
this.closeModal(document.getElementById('uploadModal'));
|
|
this.showToast('Files uploaded successfully!', 'success');
|
|
}
|
|
|
|
async uploadFile(item, folderId, tags, index) {
|
|
const formData = new FormData();
|
|
formData.append('file', item.file);
|
|
formData.append('folder_id', folderId);
|
|
formData.append('tags', JSON.stringify(tags));
|
|
formData.append('title', item.file.name.replace(/\.[^/.]+$/, ""));
|
|
|
|
item.status = 'uploading';
|
|
this.updateQueueItem(index, item);
|
|
|
|
try {
|
|
const response = await fetch('index.php?page=marketing&action=marketing_upload', {
|
|
method: 'POST',
|
|
body: formData,
|
|
onUploadProgress: (progressEvent) => {
|
|
item.progress = Math.round((progressEvent.loaded * 100) / progressEvent.total);
|
|
this.updateQueueItem(index, item);
|
|
}
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
}
|
|
|
|
const text = await response.text();
|
|
if (!text || text.trim() === '') {
|
|
throw new Error('Empty response from upload server');
|
|
}
|
|
|
|
const result = JSON.parse(text);
|
|
|
|
if (result.success) {
|
|
item.status = 'completed';
|
|
item.progress = 100;
|
|
} else {
|
|
throw new Error(result.error || 'Upload failed');
|
|
}
|
|
} catch (error) {
|
|
item.status = 'error';
|
|
item.error = error.message;
|
|
this.showToast(error.message, 'error');
|
|
}
|
|
|
|
this.updateQueueItem(index, item);
|
|
}
|
|
|
|
async createFolder() {
|
|
const folderName = document.getElementById('folderName').value.trim();
|
|
const parentId = document.getElementById('parentFolder').value;
|
|
const description = document.getElementById('folderDescription').value.trim();
|
|
|
|
if (!folderName) {
|
|
this.showToast('Folder name is required', 'error');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const formData = new FormData();
|
|
formData.append('folder_name', folderName);
|
|
formData.append('parent_id', parentId || '');
|
|
formData.append('description', description);
|
|
|
|
const response = await fetch('index.php?page=marketing&action=marketing_folders', {
|
|
method: 'POST',
|
|
body: formData
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
}
|
|
|
|
const text = await response.text();
|
|
if (!text || text.trim() === '') {
|
|
throw new Error('Empty response from server');
|
|
}
|
|
|
|
const data = JSON.parse(text);
|
|
|
|
if (data && (data.success || data.rowID)) {
|
|
this.closeModal(document.getElementById('folderModal'));
|
|
this.loadFolders();
|
|
this.showToast('Folder created successfully!', 'success');
|
|
} else if (data.error) {
|
|
throw new Error(data.error);
|
|
} else {
|
|
throw new Error('Unexpected response format');
|
|
}
|
|
} catch (error) {
|
|
console.error('Create folder error:', error);
|
|
this.showToast(error.message || 'Error creating folder', 'error');
|
|
}
|
|
}
|
|
|
|
async deleteFile(file) {
|
|
if (!confirm(`Are you sure you want to delete "${file.title || file.original_filename}"?`)) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const formData = new FormData();
|
|
formData.append('file_id', file.id);
|
|
|
|
const response = await fetch('index.php?page=marketing&action=marketing_delete', {
|
|
method: 'POST',
|
|
body: formData
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
}
|
|
|
|
const text = await response.text();
|
|
if (!text || text.trim() === '') {
|
|
throw new Error('Empty response from delete server');
|
|
}
|
|
|
|
const data = JSON.parse(text);
|
|
|
|
if (data && (data.success || !data.error)) {
|
|
this.closeModal(document.getElementById('previewModal'));
|
|
this.loadFiles();
|
|
this.showToast('File deleted successfully!', 'success');
|
|
} else if (data.error) {
|
|
throw new Error(data.error);
|
|
} else {
|
|
throw new Error('Unexpected response format');
|
|
}
|
|
} catch (error) {
|
|
this.showToast(error.message || 'Error deleting file', 'error');
|
|
}
|
|
}
|
|
|
|
previewFile(file) {
|
|
this.selectedFile = file;
|
|
const modal = document.getElementById('previewModal');
|
|
const title = document.getElementById('previewTitle');
|
|
const content = document.getElementById('previewContent');
|
|
|
|
title.textContent = file.title || file.original_filename;
|
|
|
|
// Generate preview content based on file type
|
|
if (['jpg', 'jpeg', 'png', 'gif', 'webp'].includes(file.file_type.toLowerCase())) {
|
|
content.innerHTML = `<img src="${file.file_path}" alt="${this.escapeHtml(file.title)}" style="max-width: 100%; max-height: 500px;">`;
|
|
} else if (file.file_type.toLowerCase() === 'mp4') {
|
|
content.innerHTML = `<video controls style="max-width: 100%; max-height: 500px;"><source src="${file.file_path}" type="video/mp4"></video>`;
|
|
} else {
|
|
content.innerHTML = `
|
|
<div class="file-preview-info">
|
|
<i class="fa ${this.getFileIcon(file.file_type)} fa-4x"></i>
|
|
<h4>${this.escapeHtml(file.title || file.original_filename)}</h4>
|
|
<p>File Type: ${file.file_type.toUpperCase()}</p>
|
|
<p>Size: ${file.file_size_formatted}</p>
|
|
<p>Created: ${this.formatDate(file.created)}</p>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
this.showModal(modal);
|
|
}
|
|
|
|
downloadFile(file) {
|
|
const link = document.createElement('a');
|
|
link.href = file.file_path;
|
|
link.download = file.original_filename;
|
|
document.body.appendChild(link);
|
|
link.click();
|
|
document.body.removeChild(link);
|
|
}
|
|
|
|
// Utility methods
|
|
async apiCall(endpoint, params = {}, method = 'GET') {
|
|
const url = new URL(`/api.php${endpoint}`, window.location.origin);
|
|
|
|
let options = {
|
|
method: method,
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
}
|
|
};
|
|
|
|
if (method === 'GET') {
|
|
Object.keys(params).forEach(key => url.searchParams.append(key, params[key]));
|
|
} else {
|
|
options.body = JSON.stringify(params);
|
|
}
|
|
|
|
const response = await fetch(url, options);
|
|
return await response.json();
|
|
}
|
|
|
|
escapeHtml(text) {
|
|
const map = {
|
|
'&': '&',
|
|
'<': '<',
|
|
'>': '>',
|
|
'"': '"',
|
|
"'": '''
|
|
};
|
|
return text ? text.replace(/[&<>"']/g, m => map[m]) : '';
|
|
}
|
|
|
|
formatDate(dateString) {
|
|
return new Date(dateString).toLocaleDateString();
|
|
}
|
|
|
|
formatFileSize(bytes) {
|
|
const sizes = ['B', 'KB', 'MB', 'GB'];
|
|
const i = Math.floor(Math.log(bytes) / Math.log(1024));
|
|
return Math.round(bytes / Math.pow(1024, i) * 100) / 100 + ' ' + sizes[i];
|
|
}
|
|
|
|
debounce(func, wait) {
|
|
let timeout;
|
|
return function executedFunction(...args) {
|
|
const later = () => {
|
|
clearTimeout(timeout);
|
|
func(...args);
|
|
};
|
|
clearTimeout(timeout);
|
|
timeout = setTimeout(later, wait);
|
|
};
|
|
}
|
|
|
|
showToast(message, type = 'info') {
|
|
// Simple toast implementation
|
|
const toast = document.createElement('div');
|
|
toast.className = `toast toast-${type}`;
|
|
toast.textContent = message;
|
|
|
|
document.body.appendChild(toast);
|
|
|
|
setTimeout(() => {
|
|
toast.classList.add('show');
|
|
}, 100);
|
|
|
|
setTimeout(() => {
|
|
toast.classList.remove('show');
|
|
setTimeout(() => document.body.removeChild(toast), 300);
|
|
}, 3000);
|
|
}
|
|
|
|
setViewMode(mode) {
|
|
this.viewMode = mode;
|
|
const container = document.getElementById('filesContainer');
|
|
|
|
// Update view mode classes
|
|
container.className = `files-container ${mode}-view`;
|
|
|
|
// Update button states
|
|
document.querySelectorAll('.view-btn').forEach(btn => btn.classList.remove('active'));
|
|
document.getElementById(`${mode}ViewBtn`).classList.add('active');
|
|
|
|
// Re-render files with new view mode
|
|
this.loadFiles();
|
|
}
|
|
|
|
selectFolder(folderId) {
|
|
// Clear current folder selection and files BEFORE setting new folder
|
|
const container = document.getElementById('filesContainer');
|
|
container.innerHTML = '';
|
|
|
|
// Set new current folder
|
|
this.currentFolder = folderId;
|
|
|
|
// Update UI
|
|
this.updateBreadcrumb();
|
|
|
|
// Update active folder in tree
|
|
document.querySelectorAll('.folder-item').forEach(item => {
|
|
item.classList.remove('active');
|
|
});
|
|
const selectedFolder = document.querySelector(`[data-folder="${folderId}"]`);
|
|
if (selectedFolder) {
|
|
selectedFolder.classList.add('active');
|
|
}
|
|
|
|
// Load files for the new folder
|
|
this.loadFiles();
|
|
}
|
|
|
|
updateBreadcrumb() {
|
|
// Implement breadcrumb navigation
|
|
const nav = document.getElementById('breadcrumbNav');
|
|
// This would build breadcrumb based on current folder path
|
|
}
|
|
|
|
updateFileTypeFilters() {
|
|
this.filters.fileTypes = [];
|
|
|
|
document.querySelectorAll('.file-type-filters input[type="checkbox"]:checked').forEach(checkbox => {
|
|
const types = checkbox.value.split(',');
|
|
this.filters.fileTypes.push(...types);
|
|
});
|
|
|
|
this.loadFiles();
|
|
}
|
|
|
|
populateTagFilter(tags) {
|
|
const select = document.getElementById('tagFilter');
|
|
select.innerHTML = '<option value="">All Tags</option>';
|
|
|
|
tags.forEach(tag => {
|
|
const option = document.createElement('option');
|
|
option.value = tag.tag_name;
|
|
option.textContent = `${tag.tag_name} (${tag.usage_count})`;
|
|
select.appendChild(option);
|
|
});
|
|
}
|
|
|
|
populateFolderSelects(folders) {
|
|
this.populateUploadFolders(folders);
|
|
this.populateParentFolders(folders);
|
|
}
|
|
|
|
addFolderOptions(select, folders, level = 0) {
|
|
folders.forEach(folder => {
|
|
const option = document.createElement('option');
|
|
option.value = folder.id;
|
|
option.textContent = ' '.repeat(level) + folder.folder_name;
|
|
select.appendChild(option);
|
|
|
|
if (folder.children && folder.children.length > 0) {
|
|
this.addFolderOptions(select, folder.children, level + 1);
|
|
}
|
|
});
|
|
}
|
|
|
|
populateUploadFolders(folders = []) {
|
|
// Populate upload folder select
|
|
const select = document.getElementById('uploadFolder');
|
|
if (select) {
|
|
select.innerHTML = '<option value="">Root Folder</option>';
|
|
this.addFolderOptions(select, folders);
|
|
}
|
|
}
|
|
|
|
populateParentFolders(folders = []) {
|
|
// Populate parent folder select
|
|
const select = document.getElementById('parentFolder');
|
|
if (select) {
|
|
select.innerHTML = '<option value="">Root Folder</option>';
|
|
this.addFolderOptions(select, folders);
|
|
}
|
|
}
|
|
|
|
getFileIcon(fileType) {
|
|
const iconMap = {
|
|
pdf: 'fa-file-pdf',
|
|
doc: 'fa-file-word',
|
|
docx: 'fa-file-word',
|
|
xls: 'fa-file-excel',
|
|
xlsx: 'fa-file-excel',
|
|
mp4: 'fa-file-video',
|
|
mov: 'fa-file-video',
|
|
avi: 'fa-file-video'
|
|
};
|
|
|
|
return iconMap[fileType.toLowerCase()] || 'fa-file';
|
|
}
|
|
|
|
updateQueueItem(index, item) {
|
|
const queueItems = document.querySelectorAll('.upload-item');
|
|
if (queueItems[index]) {
|
|
const progressFill = queueItems[index].querySelector('.progress-fill');
|
|
const status = queueItems[index].querySelector('.upload-status');
|
|
|
|
progressFill.style.width = `${item.progress}%`;
|
|
status.textContent = item.status;
|
|
|
|
if (item.status === 'error') {
|
|
queueItems[index].classList.add('error');
|
|
} else if (item.status === 'completed') {
|
|
queueItems[index].classList.add('completed');
|
|
}
|
|
}
|
|
}
|
|
|
|
removeFromQueue(index) {
|
|
this.uploadQueue.splice(index, 1);
|
|
this.renderUploadQueue();
|
|
document.getElementById('startUpload').disabled = this.uploadQueue.length === 0;
|
|
}
|
|
}
|
|
|
|
// Initialize when DOM is ready
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
window.marketingManager = new MarketingFileManager();
|
|
}); |