- Added formObjectOptions hook for document pages - CSS/JS injected via DOMContentLoaded for proper timing - Toggle between list and tile view (localStorage persisted) - Lightbox for image gallery with keyboard navigation - Fixed hook contexts (productdocuments, invoicedocuments, etc.) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
350 lines
12 KiB
PHP
350 lines
12 KiB
PHP
<?php
|
|
/* Copyright (C) 2026 Eduard Wisch <data@data-it-solution.de>
|
|
* JavaScript for FileArchiv Document Browser
|
|
*/
|
|
|
|
if (!defined('NOTOKENRENEWAL')) {
|
|
define('NOTOKENRENEWAL', '1');
|
|
}
|
|
if (!defined('NOLOGIN')) {
|
|
define('NOLOGIN', '1');
|
|
}
|
|
if (!defined('NOCSRFCHECK')) {
|
|
define('NOCSRFCHECK', '1');
|
|
}
|
|
|
|
// Load Dolibarr environment for translations
|
|
$res = 0;
|
|
if (!$res && file_exists("../../main.inc.php")) {
|
|
$res = @include "../../main.inc.php";
|
|
}
|
|
if (!$res && file_exists("../../../main.inc.php")) {
|
|
$res = @include "../../../main.inc.php";
|
|
}
|
|
if (!$res && file_exists("../../../../main.inc.php")) {
|
|
$res = @include "../../../../main.inc.php";
|
|
}
|
|
|
|
header('Content-Type: application/javascript; charset=UTF-8');
|
|
|
|
// Load translations
|
|
$langs->load('filearchiv@filearchiv');
|
|
|
|
$ajaxUrl = dol_buildpath('/filearchiv/ajax/getdocuments.php', 1);
|
|
$addFileUrl = dol_buildpath('/filearchiv/ajax/addfile.php', 1);
|
|
?>
|
|
/**
|
|
* FileArchiv Document Browser
|
|
* Injects document browser into email presend forms
|
|
*/
|
|
(function() {
|
|
'use strict';
|
|
|
|
// Only run on presend pages
|
|
if (window.location.href.indexOf('action=presend') === -1) {
|
|
return;
|
|
}
|
|
|
|
// Configuration
|
|
var config = {
|
|
ajaxUrl: '<?php echo dol_escape_js($ajaxUrl); ?>',
|
|
addFileUrl: '<?php echo dol_escape_js($addFileUrl); ?>',
|
|
token: '',
|
|
element: '',
|
|
id: 0,
|
|
trackid: '',
|
|
translations: {
|
|
browseDocuments: '<?php echo dol_escape_js($langs->trans("BrowseRelatedDocuments")); ?>',
|
|
selectDocuments: '<?php echo dol_escape_js($langs->trans("SelectDocumentsToAttach")); ?>',
|
|
loading: '<?php echo dol_escape_js($langs->trans("Loading")); ?>',
|
|
noDocuments: '<?php echo dol_escape_js($langs->trans("NoRelatedDocumentsFound")); ?>',
|
|
errorLoading: '<?php echo dol_escape_js($langs->trans("ErrorLoadingDocuments")); ?>',
|
|
filesSelected: '<?php echo dol_escape_js($langs->trans("FilesSelected")); ?>',
|
|
cancel: '<?php echo dol_escape_js($langs->trans("Cancel")); ?>',
|
|
addSelected: '<?php echo dol_escape_js($langs->trans("AddSelectedFiles")); ?>',
|
|
adding: '<?php echo dol_escape_js($langs->trans("Adding")); ?>',
|
|
errorAdding: '<?php echo dol_escape_js($langs->trans("ErrorAddingFiles")); ?>'
|
|
}
|
|
};
|
|
|
|
// Detect element type and ID from URL
|
|
function detectContext() {
|
|
var url = window.location.href;
|
|
var params = new URLSearchParams(window.location.search);
|
|
|
|
config.id = parseInt(params.get('id')) || 0;
|
|
config.token = typeof token !== 'undefined' ? token : '';
|
|
|
|
// Detect element type from URL path
|
|
if (url.indexOf('/comm/propal/') !== -1) {
|
|
config.element = 'propal';
|
|
} else if (url.indexOf('/commande/') !== -1) {
|
|
config.element = 'commande';
|
|
} else if (url.indexOf('/compta/facture/') !== -1) {
|
|
config.element = 'facture';
|
|
} else if (url.indexOf('/expedition/') !== -1) {
|
|
config.element = 'expedition';
|
|
} else if (url.indexOf('/fourn/commande/') !== -1) {
|
|
config.element = 'order_supplier';
|
|
} else if (url.indexOf('/fourn/facture/') !== -1) {
|
|
config.element = 'invoice_supplier';
|
|
}
|
|
|
|
// Get trackid from form
|
|
var trackidInput = document.querySelector('input[name="trackid"]');
|
|
if (trackidInput) {
|
|
config.trackid = trackidInput.value;
|
|
}
|
|
|
|
// Get token from form
|
|
var tokenInput = document.querySelector('input[name="token"]');
|
|
if (tokenInput) {
|
|
config.token = tokenInput.value;
|
|
}
|
|
}
|
|
|
|
// Create and inject the document browser UI
|
|
function injectDocumentBrowser() {
|
|
// Find the mail form
|
|
var mailForm = document.getElementById('mailform');
|
|
if (!mailForm) {
|
|
mailForm = document.querySelector('form[name="mailform"]');
|
|
}
|
|
if (!mailForm) {
|
|
return;
|
|
}
|
|
|
|
// Find attachment section (look for file input or attachment table)
|
|
var attachSection = mailForm.querySelector('input[name="addedfile"]');
|
|
if (!attachSection) {
|
|
attachSection = mailForm.querySelector('.linked-medias');
|
|
}
|
|
if (!attachSection) {
|
|
// Try to find any file-related section
|
|
attachSection = mailForm.querySelector('table.liste');
|
|
}
|
|
|
|
// Create button container
|
|
var buttonHtml = '<div class="filearchiv-docbrowser-section">' +
|
|
'<div class="filearchiv-docbrowser-title">' +
|
|
'<i class="fas fa-folder-open"></i> ' + config.translations.browseDocuments +
|
|
'</div>' +
|
|
'<button type="button" class="filearchiv-btn filearchiv-btn-secondary" onclick="FileArchivDocBrowser.open()">' +
|
|
'<i class="fas fa-search"></i> ' + config.translations.selectDocuments +
|
|
'</button>' +
|
|
'</div>';
|
|
|
|
// Create modal
|
|
var modalHtml = '<div class="filearchiv-modal-overlay" id="filearchiv-docbrowser-modal">' +
|
|
'<div class="filearchiv-modal">' +
|
|
'<div class="filearchiv-modal-header">' +
|
|
'<h3><i class="fas fa-folder-open"></i> ' + config.translations.selectDocuments + '</h3>' +
|
|
'<span class="filearchiv-modal-close" onclick="FileArchivDocBrowser.close()">×</span>' +
|
|
'</div>' +
|
|
'<div class="filearchiv-modal-body" id="filearchiv-docbrowser-content">' +
|
|
'<div class="filearchiv-loading">' +
|
|
'<i class="fas fa-spinner fa-spin"></i>' +
|
|
'<div>' + config.translations.loading + '...</div>' +
|
|
'</div>' +
|
|
'</div>' +
|
|
'<div class="filearchiv-modal-footer">' +
|
|
'<span class="filearchiv-selected-count">' +
|
|
'<span id="filearchiv-selected-num">0</span> ' + config.translations.filesSelected +
|
|
'</span>' +
|
|
'<div>' +
|
|
'<button type="button" class="filearchiv-btn filearchiv-btn-secondary" onclick="FileArchivDocBrowser.close()">' +
|
|
config.translations.cancel +
|
|
'</button> ' +
|
|
'<button type="button" class="filearchiv-btn filearchiv-btn-primary" id="filearchiv-add-btn" onclick="FileArchivDocBrowser.addSelected()" disabled>' +
|
|
'<i class="fas fa-plus"></i> ' + config.translations.addSelected +
|
|
'</button>' +
|
|
'</div>' +
|
|
'</div>' +
|
|
'</div>' +
|
|
'</div>';
|
|
|
|
// Insert button before attachment section or at end of form
|
|
var container = document.createElement('div');
|
|
container.innerHTML = buttonHtml;
|
|
|
|
if (attachSection && attachSection.parentNode) {
|
|
attachSection.parentNode.insertBefore(container.firstChild, attachSection);
|
|
} else {
|
|
// Insert before submit button
|
|
var submitBtn = mailForm.querySelector('input[type="submit"], button[type="submit"]');
|
|
if (submitBtn && submitBtn.parentNode) {
|
|
submitBtn.parentNode.insertBefore(container.firstChild, submitBtn);
|
|
} else {
|
|
mailForm.appendChild(container.firstChild);
|
|
}
|
|
}
|
|
|
|
// Add modal to body
|
|
document.body.insertAdjacentHTML('beforeend', modalHtml);
|
|
}
|
|
|
|
// Document Browser Controller
|
|
window.FileArchivDocBrowser = {
|
|
loaded: false,
|
|
selectedFiles: [],
|
|
|
|
open: function() {
|
|
document.getElementById('filearchiv-docbrowser-modal').classList.add('active');
|
|
document.body.style.overflow = 'hidden';
|
|
if (!this.loaded) {
|
|
this.loadDocuments();
|
|
}
|
|
},
|
|
|
|
close: function() {
|
|
document.getElementById('filearchiv-docbrowser-modal').classList.remove('active');
|
|
document.body.style.overflow = '';
|
|
},
|
|
|
|
loadDocuments: function() {
|
|
var self = this;
|
|
var url = config.ajaxUrl + '?element=' + config.element + '&id=' + config.id + '&token=' + config.token;
|
|
|
|
fetch(url)
|
|
.then(function(response) { return response.json(); })
|
|
.then(function(data) {
|
|
self.loaded = true;
|
|
self.renderDocuments(data);
|
|
})
|
|
.catch(function(error) {
|
|
console.error('Error loading documents:', error);
|
|
document.getElementById('filearchiv-docbrowser-content').innerHTML =
|
|
'<div class="filearchiv-empty"><i class="fas fa-exclamation-triangle"></i> ' + config.translations.errorLoading + '</div>';
|
|
});
|
|
},
|
|
|
|
renderDocuments: function(data) {
|
|
var self = this;
|
|
var container = document.getElementById('filearchiv-docbrowser-content');
|
|
|
|
if (!data.success || !data.categories || data.categories.length === 0) {
|
|
container.innerHTML = '<div class="filearchiv-empty"><i class="fas fa-folder-open"></i><br>' + config.translations.noDocuments + '</div>';
|
|
return;
|
|
}
|
|
|
|
var html = '';
|
|
data.categories.forEach(function(category, catIndex) {
|
|
html += '<div class="filearchiv-category' + (catIndex === 0 ? ' open' : '') + '" id="cat-' + catIndex + '">';
|
|
html += '<div class="filearchiv-category-header" onclick="FileArchivDocBrowser.toggleCategory(' + catIndex + ')">';
|
|
html += '<i class="fas ' + category.icon + '"></i>';
|
|
html += '<span>' + self.escapeHtml(category.title) + '</span>';
|
|
html += '<span class="count">' + category.files.length + '</span>';
|
|
html += '<i class="fas fa-chevron-down toggle-icon"></i>';
|
|
html += '</div>';
|
|
html += '<div class="filearchiv-category-body">';
|
|
|
|
category.files.forEach(function(file, fileIndex) {
|
|
var fileId = 'file-' + catIndex + '-' + fileIndex;
|
|
html += '<div class="filearchiv-file-item">';
|
|
html += '<input type="checkbox" id="' + fileId + '" onchange="FileArchivDocBrowser.toggleFile(this, \'' + self.escapeHtml(file.fullpath) + '\', \'' + self.escapeHtml(file.name) + '\')">';
|
|
html += '<i class="fas ' + file.icon + ' file-icon"></i>';
|
|
html += '<div class="file-info">';
|
|
html += '<div class="file-name">' + self.escapeHtml(file.name) + '</div>';
|
|
html += '<div class="file-meta">' + file.size_formatted + ' - ' + file.date + '</div>';
|
|
if (file.product_ref) {
|
|
html += '<span class="file-source">' + self.escapeHtml(file.product_ref) + ': ' + self.escapeHtml(file.product_label) + '</span>';
|
|
} else if (file.object_ref) {
|
|
html += '<span class="file-source">' + self.escapeHtml(file.object_ref) + '</span>';
|
|
}
|
|
html += '</div>';
|
|
html += '</div>';
|
|
});
|
|
|
|
html += '</div></div>';
|
|
});
|
|
|
|
container.innerHTML = html;
|
|
},
|
|
|
|
escapeHtml: function(text) {
|
|
if (!text) return '';
|
|
var div = document.createElement('div');
|
|
div.textContent = text;
|
|
return div.innerHTML;
|
|
},
|
|
|
|
toggleCategory: function(index) {
|
|
var cat = document.getElementById('cat-' + index);
|
|
cat.classList.toggle('open');
|
|
},
|
|
|
|
toggleFile: function(checkbox, filepath, filename) {
|
|
if (checkbox.checked) {
|
|
this.selectedFiles.push({ path: filepath, name: filename });
|
|
} else {
|
|
this.selectedFiles = this.selectedFiles.filter(function(f) {
|
|
return f.path !== filepath;
|
|
});
|
|
}
|
|
this.updateSelectedCount();
|
|
},
|
|
|
|
updateSelectedCount: function() {
|
|
document.getElementById('filearchiv-selected-num').textContent = this.selectedFiles.length;
|
|
document.getElementById('filearchiv-add-btn').disabled = this.selectedFiles.length === 0;
|
|
},
|
|
|
|
addSelected: function() {
|
|
if (this.selectedFiles.length === 0) return;
|
|
|
|
var self = this;
|
|
var addBtn = document.getElementById('filearchiv-add-btn');
|
|
addBtn.disabled = true;
|
|
addBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> ' + config.translations.adding + '...';
|
|
|
|
var promises = this.selectedFiles.map(function(file) {
|
|
return fetch(config.addFileUrl, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/x-www-form-urlencoded'
|
|
},
|
|
body: 'filepath=' + encodeURIComponent(file.path) +
|
|
'&filename=' + encodeURIComponent(file.name) +
|
|
'&trackid=' + encodeURIComponent(config.trackid) +
|
|
'&token=' + encodeURIComponent(config.token)
|
|
});
|
|
});
|
|
|
|
Promise.all(promises)
|
|
.then(function() {
|
|
window.location.reload();
|
|
})
|
|
.catch(function(error) {
|
|
console.error('Error adding files:', error);
|
|
addBtn.disabled = false;
|
|
addBtn.innerHTML = '<i class="fas fa-plus"></i> ' + config.translations.addSelected;
|
|
alert(config.translations.errorAdding);
|
|
});
|
|
}
|
|
};
|
|
|
|
// Initialize on DOM ready
|
|
if (document.readyState === 'loading') {
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
detectContext();
|
|
if (config.element && config.id) {
|
|
injectDocumentBrowser();
|
|
}
|
|
});
|
|
} else {
|
|
detectContext();
|
|
if (config.element && config.id) {
|
|
injectDocumentBrowser();
|
|
}
|
|
}
|
|
|
|
// Close modal on Escape
|
|
document.addEventListener('keydown', function(e) {
|
|
if (e.key === 'Escape') {
|
|
var modal = document.getElementById('filearchiv-docbrowser-modal');
|
|
if (modal && modal.classList.contains('active')) {
|
|
FileArchivDocBrowser.close();
|
|
}
|
|
}
|
|
});
|
|
})();
|