if (!crypto.randomUUID) {
crypto.randomUUID = function() {
return ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, c =>
(c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
);
};
}
function addHTMXListeners() {
document.body.addEventListener("htmx:sendError", function (e) {
let errorMessage = e.detail.error;
errorMessage = "Failed to send request to server - " + errorMessage;
let htmlFragment = '
' + errorMessage.replace(/';
let bodyElement = document.querySelector('body');
bodyElement.insertAdjacentHTML('afterbegin', htmlFragment);
});
document.body.addEventListener("htmx:responseError", function (e) {
let errorMessage = e.detail.xhr.response;
alert(errorMessage);
let htmlFragment = '' + errorMessage.replace(/';
let bodyElement = document.querySelector('body');
bodyElement.insertAdjacentHTML('afterbegin', htmlFragment);
});
}
async function getFile(fileEntry) {
return new Promise((resolve, reject) => {
fileEntry.file(resolve, reject);
});
}
async function processDirectory(entry, path = '', files = []) {
path = path ? path + "/" + entry.name : entry.name;
let dirReader = entry.createReader();
const readEntries = async () => {
return new Promise((resolve, reject) => {
dirReader.readEntries(resolve, reject);
});
};
let entries;
try {
entries = await readEntries();
} catch (error) {
console.error("Error reading directory entries", error);
return;
}
for (let entry of entries) {
if (entry.isDirectory) {
await processDirectory(entry, path, files);
} else {
let fileEntry = await getFile(entry);
fileEntry._relativefilePath = path + "/" + entry.name;
files.push(fileEntry);
}
}
}
class ValidationController {
constructor(formId, translations) {
this.translations = translations;
this.formId = formId;
this.form = document.getElementById(formId);
if (!this.form) {
console.warn('ValidationController: form not found:', formId);
return;
}
this.fields = this.form.querySelectorAll('input, select, textarea');
this.submitButtons = this.form.querySelectorAll(
'button[type="submit"][data-validation-submit]'
);
this.setupEventListeners();
this.validateForm();
}
translate(s) {
return this.translations[s] || s;
}
setupEventListeners() {
this.form.addEventListener('submit', (e) => this.handleSubmit(e));
this.fields.forEach(field => {
if (this.shouldValidateField(field)) {
field.addEventListener('blur', () => this.validateForm());
field.addEventListener('input', () => this.validateForm());
field.addEventListener('change', () => this.validateForm()); // For select and date inputs
}
});
}
handleSubmit(e) {
e.preventDefault();
if (this.validateForm()) {
console.log('Form is valid, submit it!');
// You can submit the form here if needed
// this.form.submit();
}
}
shouldValidateField(field) {
return field.hasAttribute('required') || field.hasAttribute('data-validation-type');
}
async validateField(field) {
if (!this.shouldValidateField(field)) {
return true; // Field doesn't need validation
}
const isRequired = field.hasAttribute('required');
const minLength = parseInt(field.getAttribute('data-min-length')) || 0;
const maxLength = parseInt(field.getAttribute('data-max-length')) || Infinity;
let isValid = true;
let errorMessage = '';
if (isRequired) {
if (field.tagName.toLowerCase() === 'select') {
if (!field.value || field.value === "") {
isValid = false;
errorMessage = 'Please select an option.';
}
} else if (field.type === 'date') {
if (!field.value) {
isValid = false;
errorMessage = 'Please enter a valid date.';
}
} else if (!field.value.trim()) {
isValid = false;
errorMessage = 'Dies ist ein Pflichtfeld';
}
}
if (isValid && field.value.trim()) {
if (field.type !== 'date' && field.tagName.toLowerCase() !== 'select') {
if (field.value.trim().length < minLength) {
isValid = false;
errorMessage = `Minimum length is ${minLength} characters.`;
} else if (field.value.trim().length > maxLength) {
isValid = false;
errorMessage = `Maximum length is ${maxLength} characters.`;
}
}
}
// Additional date validation
if (isValid && field.type === 'date') {
const minDate = field.getAttribute('min');
const maxDate = field.getAttribute('max');
const selectedDate = new Date(field.value);
if (minDate && selectedDate < new Date(minDate)) {
isValid = false;
errorMessage = `Date must be on or after ${minDate}.`;
} else if (maxDate && selectedDate > new Date(maxDate)) {
isValid = false;
errorMessage = `Date must be on or before ${maxDate}.`;
}
}
// Money validation
if (field.getAttribute('data-validation-type') === 'money') {
//console.log(field);
const fieldValue = field.value.trim();
const isValidNumber = /^-?\d+(\.\d{1,2})?$/.test(fieldValue);
if (!isValidNumber) {
isValid = false;
errorMessage = 'Please enter a valid amount (e.g., 123.45).';
} else {
const fieldLength = fieldValue.length;
if (typeof minLength === 'number' && fieldLength < minLength) {
isValid = false;
errorMessage = `The value must be at least ${minLength} characters long.`;
} else if (typeof maxLength === 'number' && fieldLength > maxLength) {
isValid = false;
errorMessage = `The value must be no more than ${maxLength} characters long.`;
}
}
}
// Year validation
if (field.getAttribute('data-validation-type') === 'year') {
const fieldValue = field.value.trim();
const isValidYear = /^\d{4}$/.test(fieldValue);
if (!isValidYear) {
isValid = false;
errorMessage = 'Please enter a valid amount (e.g., 123.45).';
} else {
const year = parseInt(fieldValue, 10);
const currentYear = new Date().getFullYear();
const minYear = 1900;
const maxYear = currentYear + 10;
if (year < minYear || year > maxYear) {
isValid = false;
errorMessage = `The year must be between ${minYear} and ${maxYear}.`;
}
}
}
// Email validation
if (field.getAttribute('data-validation-type') === 'email') {
const fieldValue = field.value.trim();
const isValidEmail = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(fieldValue);
if (!isValidEmail) {
isValid = false;
errorMessage = 'Bitte geben Sie eine gültige E-Mail-Adresse ein (z. B. example@example.com).';
}
}
// Iban validation
if (field.getAttribute('data-validation-type') === 'iban') {
const fieldValue = field.value.trim();
const iban = fieldValue.replace(/\s+/g, '').toUpperCase();
// Basic IBAN structure check
if (iban && !/^[A-Z0-9]{15,34}$/.test(iban)) {
isValid = false;
errorMessage = 'IBAN must be between 15 and 34 characters long';
this.updateFieldStatus(field, isValid, errorMessage);
return isValid;
}
// Move the first four characters to the end of the string
const rearrangedIban = iban.slice(4) + iban.slice(0, 4);
// Replace each letter in the string with two digits, expanding the string as needed
const expandedIban = rearrangedIban.replace(/[A-Z]/g, function(match) {
return match.charCodeAt(0) - 55;
});
// Perform Modulo 97 operation on the expanded IBAN
const ibanAsNumber = BigInt(expandedIban);
isValid = !ibanAsNumber || ibanAsNumber % 97n === 1n;
//const isValidIban = /^[A-Z0-9]{15,34}$/.test(iban);
if (!isValid) {
errorMessage = 'Please enter a valid IBAN (e.g., CH6330000004400005700).';
}
}
this.updateFieldStatus(field, isValid, errorMessage);
return isValid;
}
async validateForm() {
let isValid = true;
for (const field of this.fields) {
if (this.shouldValidateField(field) && !(await this.validateField(field))) {
console.log('invalid field', field);
isValid = false;
}
};
this.formValidated();
console.log('form isValid', isValid);
this.updateSubmitButtons(isValid);
return isValid;
}
formValidated() {}
updateFieldStatus(field, isValid, errorMessage) {
if (!field) {
console.warn('updateFieldStatus: Kein Feld übergeben.');
return;
}
const feedbackElement = field.nextElementSibling;
if (isValid) {
field.classList.remove('is-invalid');
field.classList.add('is-valid');
if (feedbackElement && feedbackElement.classList.contains('invalid-feedback')) {
feedbackElement.textContent = '';
} else if (!feedbackElement) {
console.warn(`Kein Feedback-Element für Feld "${field.id}" gefunden.`);
}
} else {
field.classList.remove('is-valid');
field.classList.add('is-invalid');
const translatedMessage = this.translate(errorMessage || 'Fehlerhafte Eingabe');
if (feedbackElement && feedbackElement.classList.contains('invalid-feedback')) {
feedbackElement.textContent = translatedMessage;
} else if (!feedbackElement) {
console.warn(`Kein Feedback-Element für Feld "${field.id}" gefunden.`);
}
}
}
updateSubmitButtons(isValid) {
this.submitButtons.forEach(button => {
button.disabled = !isValid;
if (isValid) {
button.classList.remove('disabled');
} else {
button.classList.add('disabled');
}
});
}
}
class DirectUploadHandler {
constructor(config) {
this.filesToUpload = [];
this.ticket = config.ticket;
this.elem = config.elem;
this.parent = config.parent;
this.file_properties = config.file_properties;
this.uploadURL = config.uploadURL;
this.callbackActions = config.callbackActions;
this.dragOverTimeout = null;
this.chunkSize = config.chunkSize ? config.chunkSize : 3 * 1024 * 1024;
this.progress = config.progress;
this.elem.removeEventListener('dragover', this.dragOverHandler.bind(this));
this.elem.addEventListener('dragover', this.dragOverHandler.bind(this));
this.elem.removeEventListener('drop', this.dropHandler.bind(this));
this.elem.addEventListener('drop', this.dropHandler.bind(this));
this.originalBackgroundColor = this.elem.style.backgroundColor;
}
async dropHandler(ev) {
ev.preventDefault();
console.log('drop event!!!');
if (ev.dataTransfer.items) {
this.filesToUpload = [];
let items = ev.dataTransfer.items;
for (let i = 0; i < items.length; i++) {
let item = items[i];
if (item.webkitGetAsEntry) {
let entry = item.webkitGetAsEntry();
if (entry.isDirectory) {
await processDirectory(entry, item.name, this.filesToUpload);
} else if (entry.isFile) {
this.filesToUpload.push(item.getAsFile());
}
} else if (item.kind === 'file') {
this.filesToUpload.push(item.getAsFile());
}
}
} else {
this.filesToUpload = ev.dataTransfer.files;
}
await this.upload();
}
dragOverHandler(ev) {
ev.preventDefault();
this.elem.style.backgroundColor = 'lightblue';
clearTimeout(this.dragOverTimeout);
this.dragOverTimeout = setTimeout(() => {
this.elem.style.backgroundColor = this.originalBackgroundColor;
}, 300);
}
onprogress(event) {
let percent = (event.loaded / event.total) * 100;
//console.log('upload progress:', percent);
let elem = this.progress;
if (elem) {
elem.innerText = 'uploading... ' + Math.round(percent).toString() + " %";
}
}
async callback(response) {
console.log('uploadResponse:', response, response['status']);
response = JSON.parse(response);
let elem = document.getElementById("progress");
if (elem) {
elem.innerText = 'uploaded';
}
if (this.callbackActions) {
this.callbackActions['values'] = {
action: 'uploaded',
obj_id: response['obj_id'],
obj_content_hash: response['obj_content_hash']
};
}
console.log('incallback', this, this.callbackActions);
htmx.ajax('GET', this.callbackActions.url, this.callbackActions);
}
async doUploadFile(file, obj_text, add_to_clipboard) {
console.log('doUploadFile:', file);
let dateObj = new Date(file.lastModified); // Create a Date object
let isoDate = dateObj.toISOString(); // Convert to ISO date string
const projnr = document.getElementById('cq_projnr');
const projname = document.getElementById('cq_projname');
let properties = {
"_obj_profile": "cq_file",
"_obj_name": file.name,
"_obj_title": file.name,
"_obj_date": isoDate,
"_obj_text": obj_text,
"_obj_source": 'webui',
"_cq_doctype": "document",
"_obj_type": "document",
"_obj_lifecycle": "document",
"_obj_lifecycle_state": "00 uploaded",
"_add_to_clipboard": add_to_clipboard
};
if (projnr) { properties['cq_projnr'] = projnr.value()};
if (projname) { properties['cq_projname'] = projname.value()};
//Object.assign(properties, this.file_properties);
let incomingProps = this.file_properties;
if (typeof incomingProps === 'string') {
try {
incomingProps = JSON.parse(incomingProps);
} catch (e) {
incomingProps = {};
}
}
Object.assign(properties, incomingProps);
properties['_relative_path'] = file._relativefilePath ? file._relativefilePath : (file.webkitRelativePath ? file.webkitRelativePath : "");
console.log(properties);
console.log('uploadURL:', this.uploadURL);
let uploadUrl = this.uploadURL ? this.uploadURL : "/dms/upload?type=file&profile=cq_file";
if (this.parent) {
uploadUrl = uploadUrl + "&parent=" + this.parent;
}
uploadUrl = uploadUrl + "&ticket=" + this.ticket;
try {
const response = await uploadFile(uploadUrl, file, this.onprogress.bind(this), properties, {}, this.chunkSize);
await this.callback(response);
} catch (error) {
console.error('Upload failed:', error);
}
}
async upload() {
console.log('do upload:', this.filesToUpload);
if (this.filesToUpload) {
for (let file of this.filesToUpload) {
await this.doUploadFile(file, '');
}
}
}
}
function dropHandler(ev, folderId) {
ev.preventDefault();
if (ev.dataTransfer.items) {
let files = [];
let items = ev.dataTransfer.items;
for (let i = 0; i < items.length; i++) {
let item = items[i];
if (item.webkitGetAsEntry) {
let entry = item.webkitGetAsEntry();
if (entry.isDirectory) {
processDirectory(entry, item.name, files);
} else if (entry.isFile) {
files.push(item.getAsFile());
}
} else if (item.kind === 'file') {
files.push(item.getAsFile());
}
}
document._filesToUpload = files;
htmx.ajax('GET', `upload.html?id=${folderId}`, {target: '#body'});
} else {
document._filesToUpload = ev.dataTransfer.files;
htmx.ajax('GET', `upload.html?id=${folderId}`, {target: '#body'});
}
}
function dragOverHandler(ev) {
ev.preventDefault();
}
function submitOnEnter(event, id) {
if (event.shiftKey && event.key == 'Enter') {
event.preventDefault();
event.stopPropagation();
htmx.trigger(id, 'submit', {});
}
}
function submitOnEnter2(event, id) {
if (event.shiftKey && event.key == 'Enter') {
event.preventDefault();
event.stopPropagation();
document.getElementById(id).click();
}
}
function showDialog(url, cssSelector) {
htmx.ajax('GET', url, {target: '#dialog', swap: 'innerHTML'}).then(() => {
$(cssSelector).modal('show');
});
}
function getVAFilterFields(fields) {
const data = {};
fields.forEach(id => {
const element = document.getElementById(id);
if (element) {
const name = element.name;
const value = element.value;
if (name) {
data[name] = value;
}
}
});
const jsonString = JSON.stringify(data);
return encodeURIComponent(jsonString);
}
function showDrawer(url, cssSelector) {
htmx.ajax('GET', url, {target: '#dialog', swap: 'innerHTML'}).then(() => {
$(cssSelector).modal('show');
});
}
function closeDialog(cssSelector) {
$(cssSelector).modal('hide');
}
function closeDialog2(cssSelector) {
let dialog = document.querySelector(cssSelector);
dialog.style.display = 'none';
let body = document.querySelector('body');
body.style = '';
body.className = '';
let backdrop = document.querySelector('.modal-backdrop');
if (backdrop) {
backdrop.remove();
}
}
function _showPopup(pageX, pageY) {
const popup = document.querySelector('#popup');
window.addEventListener('click', function (event) {
if (!popup.contains(event.target) && event.target !== popup) {
closePopup();
}
}, {once: true});
const menuWidth = popup.offsetWidth;
const menuHeight = popup.offsetHeight;
const viewportWidth = window.innerWidth;
const viewportHeight = window.innerHeight;
let top = pageY;
let left = pageX;
if (left + menuWidth + 5 > viewportWidth) {
left -= menuWidth;
}
if (top + menuHeight + 5 > viewportHeight) {
top -= menuHeight;
}
if (left < 5) left = 5;
if (top < 5) top = 5;
popup.style.left = left + 'px';
popup.style.top = top + 'px';
popup.style.display = 'block';
}
function showPopup(e, url) {
htmx.ajax('GET', url, {target: '#popup', swap: 'innerHTML'}).then(() => {
_showPopup(e.pageX, e.pageY);
});
}
function closePopup() {
const popup = document.querySelector('#popup');
popup.style.display = 'none';
}
function splitPanel(container) {
let children = container.children;
let leftPanel = children[0];
let divider = children[1];
let isResizing = false;
const stopResize = (e) => {
isResizing = false;
document.removeEventListener('pointerup', stopResize, false);
document.removeEventListener('pointermove', doResize, false);
}
const doResize = (e) => {
let containerRect = container.getBoundingClientRect();
let leftWidth = e.clientX - containerRect.left;
leftPanel.style.flexBasis = `${leftWidth}px`;
}
divider.addEventListener('pointerdown', (e) => {
isResizing = true;
document.addEventListener('pointerup', stopResize);
document.addEventListener('pointermove', doResize);
});
}
function toggle_full_screen() {
if (!document.fullscreenElement) {
if (document.documentElement.requestFullscreen) {
document.documentElement.requestFullscreen();
} else if (document.documentElement.mozRequestFullScreen) {
document.documentElement.mozRequestFullScreen();
} else if (document.documentElement.webkitRequestFullscreen) {
document.documentElement.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT);
} else if (document.documentElement.msRequestFullscreen) {
document.documentElement.msRequestFullscreen();
}
} else {
if (document.exitFullscreen) {
document.exitFullscreen();
} else if (document.mozCancelFullScreen) {
document.mozCancelFullScreen();
} else if (document.webkitCancelFullScreen) {
document.webkitCancelFullScreen();
} else if (document.msExitFullscreen) {
document.msExitFullscreen();
}
}
}
function copyToClipboard(copyText) {
navigator.clipboard.writeText(copyText);
}
/*
Updated initTagify to prevent double initialization and avoid undefined whitelist errors
*/
function initTagify(querySelector, whiteList = [], enforceWhiteList = false) {
// Find the input element
const inputElement = document.querySelector(querySelector);
if (!inputElement) return;
// Prevent double initialization: reuse existing instance if present
if (inputElement._tagify) {
// Optionally update the whitelist settings
inputElement._tagify.settings.whitelist = Array.isArray(whiteList) ? whiteList : [];
inputElement._tagify.settings.enforceWhitelist = enforceWhiteList;
return inputElement._tagify;
}
// Remove any leftover container
if (inputElement.previousSibling && inputElement.previousSibling.tagName === 'TAGS') {
inputElement.previousSibling.remove();
}
// Initialize Tagify with a safe default whitelist
const tagify = new Tagify(inputElement, {
whitelist: Array.isArray(whiteList) ? whiteList : [],
enforceWhitelist: enforceWhiteList
});
// Store the instance to avoid re-init
inputElement._tagify = tagify;
// Setup drag-and-drop reordering
function onDragEnd() {
tagify.updateValueByDOMTags();
}
new DragSort(tagify.DOM.scope, {
selector: '.' + tagify.settings.classNames.tag,
callbacks: { dragEnd: onDragEnd }
});
return tagify;
}
function initTypeahead(selector, dataUrl) {
// Initialize Bloodhound suggestion engine
var bloodhound = new Bloodhound({
/*
datumTokenizer: function(datum) {
return Bloodhound.tokenizers.whitespace(datum.value + ' ' + datum.display);
},
queryTokenizer: Bloodhound.tokenizers.whitespace,
*/
datumTokenizer: function(datum) {
return [datum.value]; // Return full value as single token
},
queryTokenizer: function(query) {
return [query]; // Return full query as single token
},
remote: {
url: dataUrl,
wildcard: '%QUERY',
rateLimitBy: 'throttle',
rateLimitWait: 300
}
});
// Initialize typeahead
$(selector).typeahead({
minLength: 2,
highlight: true,
hint: true
},
{
name: 'suggestions',
display: 'value',
source: bloodhound,
limit: 10,
templates: {
empty: [
'',
'No matches found',
'
'
].join('\n'),
suggestion: function(data) {
return '' + data.display + '
';
}
}
});
// Event handlers
$(selector)
.on('typeahead:select', function(ev, suggestion) {
$(this).typeahead('val', suggestion.value);
console.log('Selected value:', suggestion.value);
console.log('Selected display:', suggestion.display);
ev.target.dispatchEvent(new Event('input'));
})
.on('typeahead:asyncrequest', function() {
console.log('Loading suggestions...');
})
.on('typeahead:asyncreceive', function() {
console.log('Finished loading suggestions');
})
.on('typeahead:asynccancel', function() {
console.log('Loading cancelled');
});
}
var tableSelection = {};
function toggleAllCheckboxes(source) {
const checkboxes = document.querySelectorAll('.row-checkbox');
checkboxes.forEach(checkbox => {
checkbox.checked = source.checked;
updateTableSelection(checkbox.id, source.checked);
});
}
function checkboxChanged(objId, evt) {
const isChecked = evt.target.checked;
updateTableSelection(objId, isChecked);
}
function showHideDropdownMenu(hasSelections) {
const targetDiv = document.querySelector('#header_dropdown_menu > span > div');
if (targetDiv) {
if (hasSelections) {
targetDiv.classList.add('inline-block');
targetDiv.classList.remove('d-none');
} else {
targetDiv.classList.remove('inline-block');
targetDiv.classList.add('d-none');
}
}
}
function updateTableSelection(objId, isChecked) {
if (isChecked) {
tableSelection[objId] = true;
} else {
delete tableSelection[objId];
}
console.log('Current tableSelection:', tableSelection);
const hasSelections = Object.keys(tableSelection).length > 0;
showHideDropdownMenu(hasSelections);
}
async function addToFavorites(overview_url) {
let selection = Object.keys(tableSelection)
.map(key => key.replace(/^chk_/, '')); // Remove 'chk_' prefix
for (let id of selection) {
try {
await fetch(`{overview_url}?action=add_favorite&obj_link={details_url}?id=${id}&obj_id=${id}&obj_name=${id}`);
} catch (error) {
console.error(`Error adding id ${id} to favorites:`, error);
}
}
htmx.ajax('GET', overview_url, {target: "#main", swap: "outerHTML", select: "#main"});
}
async function deleteEntries(overview_url) {
let selection = Object.keys(tableSelection)
.map(key => key.replace(/^chk_/, '')); // Remove 'chk_' prefix
let amount = selection.length; // Get the number of selected entries
let confirmDeletion = window.confirm(`Are you sure that you want to delete the selected ${amount} entries?`);
if (!confirmDeletion) {
return;
}
for (let id of selection) {
try {
await fetch(`${overview_url}?action=set_delete&oid=${id}`);
} catch (error) {
console.error(`Error deleting entry with id ${id}:`, error);
}
}
// After all deletions, refresh the page
htmx.ajax('GET', overview_url, {target: "#main", swap: "outerHTML", select: "#main"});
}
async function undeleteEntries(overview_url) {
let selection = Object.keys(tableSelection)
.map(key => key.replace(/^chk_/, '')); // Remove 'chk_' prefix
let amount = selection.length; // Get the number of selected entries
let confirmDeletion = window.confirm(`Are you sure that you want to undelete the selected ${amount} entries?`);
if (!confirmDeletion) {
return;
}
for (let id of selection) {
try {
await fetch(`${overview_url}?action=set_undelete&oid=${id}`);
} catch (error) {
console.error(`Error undeleting entry with id ${id}:`, error);
}
}
// After all deletions, refresh the page
htmx.ajax('GET', overview_url, {target: "#main", swap: "outerHTML", select: "#main"});
}
async function mergeEntries(overview_url) {
// Gather selected IDs (removing any 'chk_' prefix)
const selection = Object.keys(tableSelection)
.map(key => key.replace(/^chk_/, ''));
const amount = selection.length;
// Confirm the merge action
const confirmMerge = window.confirm(`Are you sure that you want to merge the selected ${amount} entries?`);
if (!confirmMerge) {
// If the user cancels, exit early
return;
}
try {
await fetch(`${overview_url}?action=merge_pdf&selection=` + selection);
} catch (error) {
console.error(`Error merging entry with selection ${selection}:`, error);
}
}
function sendResetPassword(id) {
try {
let url = `/webui/user?id=${id}&action=send_reset_password`;
fetch(url);
alert('E-Mail versendet');
} catch (error) {
console.error(`Error deleting entry with id ${id}:`, error);
}
}
async function changeProfile() {
try {
let profile = document.getElementById('obj_profile').value;
const params = new URLSearchParams(window.location.search);
const id = params.get("id");
let url = `/webui/archive?id=${id}&action=change_profile&profile=${profile}`;
const response = await fetch(url);
if (response.ok) {
window.location.reload();
} else {
console.error("Server returned error:", response.status);
}
} catch (error) {
console.error(`Error changing profile for entry with id ${id}:`, error);
}
}
async function deleteTableEntries(overview_url, aspect = null) {
// Auswahl extrahieren
let selection = Object.keys(tableSelection)
.filter(key => key.startsWith('chk_'))
.map(key => key.replace(/^chk_/, ''));
let amount = selection.length;
// Wenn keine Checkbox ausgewählt wurde
if (amount === 0) {
alert("Bitte wähle mindestens einen Eintrag aus, den du löschen möchtest.");
return;
}
// Bestätigungsdialog
let confirmDeletion = window.confirm(
`Are you sure that you want to delete the selected ${amount} entries?`
);
if (!confirmDeletion) return;
// Löschen
for (let id of selection) {
try {
let url = `${overview_url}?id=${id}&action=delete`;
if (aspect) {
url += `&aspect=${encodeURIComponent(aspect)}`;
}
await fetch(url);
} catch (error) {
console.error(`Error deleting entry with id ${id}:`, error);
}
}
// Ansicht aktualisieren
htmx.ajax('GET', overview_url, {
target: "#main",
swap: "outerHTML",
select: "#main"
});
}
/*
function safeName(s){ return (s||'').replace(/[\\/:*?"<>|]+/g,'_').trim(); }
await fetch(`${overview_url}?action=ai&id=${id}&selection=${selection}`);
htmx.ajax('GET', overview_url, {target: "#main", select: "#main", swap: "outerHTML"});
}
*/
async function downloadAsZip() {
const zip = new JSZip();
const ids = Object.keys(tableSelection).map(k => k.replace(/^chk_/, ''));
if (!ids.length) { alert('No items selected!'); return; }
// Optional: Titel aus der Tabelle holen (data-title), sonst ID
const titleById = new Map(
Array.from(document.querySelectorAll('a[data-id][data-title]'))
.map(a => [a.dataset.id, a.dataset.title])
); // data-id/data-title sind in den Zellen vorhanden :contentReference[oaicite:5]{index=5}
for (const id of ids) {
try {
const url = `/dms/content?id=${encodeURIComponent(id)}`; // echter Download :contentReference[oaicite:6]{index=6}
const res = await fetch(url, { credentials: 'same-origin' });
if (!res.ok) { console.error('Download failed', id, res.status); continue; }
const blob = await res.blob();
const titleRaw = titleById.get(id) || id;
const title = String(titleRaw).replace(/[\\/:*?"<>|]+/g, '_').trim();
const ct = (res.headers.get('content-type') || '').toLowerCase();
const ext =
ct.includes('pdf') ? 'pdf' :
ct.includes('jpeg') ? 'jpg' :
ct.includes('png') ? 'png' :
'bin';
zip.file(`${title}.${ext}`, blob);
} catch (e) {
console.error('Error', id, e);
}
}
const stamp = new Date().toISOString().replace(/[-:T]/g, '_').split('.')[0];
const content = await zip.generateAsync({ type: 'blob' });
const a = document.createElement('a');
a.href = URL.createObjectURL(content);
a.download = `selected_${stamp}.zip`;
a.click();
URL.revokeObjectURL(a.href);
}
async function createProjectEntries(overview_url) {
let selection = Object.keys(tableSelection)
.map(key => key.replace(/^chk_/, '')); // Remove 'chk_' prefix
let amount = selection.length; // Get the number of selected entries
if (selection.length === 0) {
alert('Bitte wählen Sie zuerst mindestens einen Eintrag aus');
return;
}
let confirmCreation = window.confirm('Möchten Sie die Einträge wirklich erstellen??');
if (!confirmCreation) {
return;
}
for (let id of selection) {
try {
await fetch(overview_url +`?action=create_new_project_entries&id=${id}&references=projects`);
} catch (error) {
console.error(`was not able to create project with id ${id}:`, error);
}
}
// After all deletions, refresh the page
htmx.ajax('GET', overview_url + '?id=projects', {target: "#main", swap: "outerHTML", select: "#main"});
}
async function changeDefaultTenant(overview_url) {
let tenant = document.getElementById('tenant');
if (!tenant) {
return;
}
try {
const tenantValue = tenant.value;
const url = overview_url + (overview_url.includes("?")
? `&action=change_default_tenant&tenant=${tenantValue}`
: `?action=change_default_tenant&tenant=${tenantValue}`
);
const url1 = url //.replace('http:','https:');
const response = await fetch(url1);
if (response.ok) {
// Seite neu laden nach Erfolg
window.location.reload();
} else {
console.error("Server returned error:", response.status);
}
} catch (error) {
console.error("Error changing client accounting:", error);
}
}
class WebSocketClient {
constructor(config = {}) {
// Default configuration
this.config = {
host: window.location.host,
protocol: window.location.protocol === 'https:' ? 'wss:' : 'ws:',
path: '/ws/jsonrpc',
reconnectMaxAttempts: 5,
reconnectBaseDelay: 1000,
maxDelay: 30000,
debug: false,
...config
};
this.socket = null;
this.reconnectAttempt = 0;
this.reconnectTimeout = null;
this.messageHandler = null;
this.subscriptions = [];
}
connect() {
const wsUrl = `${this.config.protocol}//${this.config.host}${this.config.path}`;
this.socket = new WebSocket(wsUrl);
this.attachEventListeners();
}
setMessageHandler(handler) {
this.messageHandler = handler;
}
addSubscription(topic) {
this.subscriptions.push(topic);
if (this.isConnected()) {
this.sendSubscription(topic);
}
}
sendSubscription(topic) {
const data = {
'id': crypto.randomUUID(),
'method': 'server/subscribe',
'params': { 'topic': topic }
};
this.send(data);
this.log(`Sent subscription for topic: ${topic}`);
}
// Send message
send(data) {
if (this.isConnected()) {
this.socket.send(JSON.stringify(data));
} else {
this.log('Cannot send message - socket is not connected', 'error');
}
}
// Check if socket is connected
isConnected() {
return this.socket && this.socket.readyState === WebSocket.OPEN;
}
// Attach WebSocket event listeners
attachEventListeners() {
this.socket.addEventListener('open', () => {
this.log('Connected to WebSocket server');
this.reconnectAttempt = 0;
// Send all subscriptions
this.subscriptions.forEach(topic => {
this.sendSubscription(topic);
});
});
this.socket.addEventListener('message', (event) => {
this.log('Message received:', 'info', event.data);
try {
const data = JSON.parse(event.data);
if (this.messageHandler) {
this.messageHandler(data);
}
} catch (error) {
console.log(error);
this.log('Error processing message:', 'error', error);
}
});
this.socket.addEventListener('close', () => {
this.log('Disconnected from WebSocket server');
this.handleReconnection();
});
this.socket.addEventListener('error', (error) => {
this.log('WebSocket error:', 'error', error);
});
}
// Handle reconnection logic
handleReconnection() {
if (this.reconnectTimeout) {
clearTimeout(this.reconnectTimeout);
}
if (this.reconnectAttempt < this.config.reconnectMaxAttempts) {
const delay = Math.min(
this.config.reconnectBaseDelay * Math.pow(2, this.reconnectAttempt),
this.config.maxDelay
);
this.log(
`Attempting to reconnect in ${delay}ms (attempt ${this.reconnectAttempt + 1}/${this.config.reconnectMaxAttempts})`
);
this.reconnectTimeout = setTimeout(() => {
this.reconnectAttempt++;
this.connect();
}, delay);
} else {
this.log('Maximum reconnection attempts reached', 'error');
if (this.config.onMaxReconnectAttemptsReached) {
this.config.onMaxReconnectAttemptsReached();
}
}
}
// Logging utility
log(message, level = 'info', ...args) {
if (this.config.debug) {
const timestamp = new Date().toISOString();
const prefix = `[WebSocketClient ${timestamp}]`;
console.log('logg', prefix, message, args);
switch (level) {
case 'error':
console.error(prefix, message, ...args);
break;
case 'warn':
console.warn(prefix, message, ...args);
break;
default:
console.log(prefix, message, ...args);
}
}
}
// Cleanup and close connection
disconnect() {
if (this.reconnectTimeout) {
clearTimeout(this.reconnectTimeout);
}
if (this.socket) {
this.socket.close();
}
}
}
async function addFavorite(linkEl) {
const id = linkEl.dataset.id;
const parent = linkEl.dataset.parent;
const oid = linkEl.dataset.oid;
const objLink = linkEl.dataset.objLink;
const objName = linkEl.dataset.objName || "";
const objDescription = linkEl.dataset.objDescription || "";
const objCategory = linkEl.dataset.objCategory || "";
const params = new URLSearchParams({
id: id,
parent: parent,
action: "add_favorite",
oid: oid,
obj_link: objLink,
obj_name: objName,
obj_description: objDescription,
obj_category: objCategory,
});
const url = "/webui/archive?" + params.toString();
try {
const response = await fetch(url, {
method: "POST",
// Wenn du CSRF brauchst, hier ergänzen:
// headers: { "X-CSRFToken": "..."},
});
if (!response.ok) {
throw new Error("Server-Fehler: " + response.status);
}
// Verhalten wie hx-on="htmx:afterRequest: window.location.reload()"
window.location.reload();
} catch (err) {
console.error("Fehler beim Hinzufügen zu den Favoriten:", err);
alert("Der Favorit konnte nicht gespeichert werden.");
}
}