Add PayPal webhook handler and marketing styles
- 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.
This commit is contained in:
900
assets/marketing.js
Normal file
900
assets/marketing.js
Normal file
@@ -0,0 +1,900 @@
|
||||
/**
|
||||
* 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();
|
||||
});
|
||||
Reference in New Issue
Block a user