482 lines
15 KiB
JavaScript
482 lines
15 KiB
JavaScript
// Messages functionality
|
|
|
|
let messagesData = [];
|
|
let messageModal;
|
|
let currentEditingId = null;
|
|
|
|
// Initialize messages on page load
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
// Initialize Bootstrap modal
|
|
const modalElement = document.getElementById('messageModal');
|
|
if (modalElement) {
|
|
messageModal = new bootstrap.Modal(modalElement);
|
|
}
|
|
|
|
// Tab switching
|
|
const tabButtons = document.querySelectorAll('.tab-button');
|
|
tabButtons.forEach(button => {
|
|
button.addEventListener('click', () => {
|
|
const targetTab = button.getAttribute('data-tab');
|
|
|
|
// Update tab buttons
|
|
tabButtons.forEach(btn => {
|
|
if (btn.getAttribute('data-tab') === targetTab) {
|
|
btn.classList.add('active');
|
|
} else {
|
|
btn.classList.remove('active');
|
|
}
|
|
});
|
|
|
|
// Update tab content
|
|
const tabContents = document.querySelectorAll('.tab-content');
|
|
tabContents.forEach(content => {
|
|
content.classList.remove('active');
|
|
});
|
|
|
|
const targetContent = document.getElementById(targetTab + '-section');
|
|
if (targetContent) {
|
|
targetContent.classList.add('active');
|
|
}
|
|
|
|
// Load messages when switching to messages tab
|
|
if (targetTab === 'messages') {
|
|
loadMessages();
|
|
}
|
|
});
|
|
});
|
|
|
|
// Add message button
|
|
const addMessageBtn = document.getElementById('add-message-btn');
|
|
if (addMessageBtn) {
|
|
addMessageBtn.addEventListener('click', () => {
|
|
openMessageModal();
|
|
});
|
|
}
|
|
|
|
// Save message button
|
|
const saveMessageBtn = document.getElementById('saveMessageBtn');
|
|
if (saveMessageBtn) {
|
|
saveMessageBtn.addEventListener('click', () => {
|
|
saveMessage();
|
|
});
|
|
}
|
|
|
|
// Insert link button
|
|
const insertLinkBtn = document.getElementById('insertLinkBtn');
|
|
if (insertLinkBtn) {
|
|
insertLinkBtn.addEventListener('click', (e) => {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
toggleFileBrowser();
|
|
});
|
|
}
|
|
|
|
// Event delegation for file browser actions
|
|
const fileBrowserContent = document.getElementById('fileBrowserContent');
|
|
if (fileBrowserContent) {
|
|
fileBrowserContent.addEventListener('click', (e) => {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
|
|
// Check if button was clicked
|
|
const button = e.target.closest('button[data-action="insert-link"]');
|
|
if (button) {
|
|
const path = button.getAttribute('data-path');
|
|
const name = button.getAttribute('data-name');
|
|
const type = button.getAttribute('data-type');
|
|
insertLink(path, name, type);
|
|
return;
|
|
}
|
|
|
|
// Check if list item was clicked for navigation
|
|
const navItem = e.target.closest('li[data-action="navigate"]');
|
|
if (navItem && !e.target.closest('button')) {
|
|
const path = navItem.getAttribute('data-path');
|
|
loadFileBrowser(path);
|
|
return;
|
|
}
|
|
|
|
// Check if list item was clicked for insert (files)
|
|
const insertItem = e.target.closest('li[data-action="insert-link"]');
|
|
if (insertItem && !e.target.closest('button')) {
|
|
const path = insertItem.getAttribute('data-path');
|
|
const name = insertItem.getAttribute('data-name');
|
|
const type = insertItem.getAttribute('data-type');
|
|
insertLink(path, name, type);
|
|
return;
|
|
}
|
|
});
|
|
}
|
|
|
|
// Event delegation for folder links in messages
|
|
const messagesContainer = document.getElementById('messages-container');
|
|
if (messagesContainer) {
|
|
messagesContainer.addEventListener('click', (e) => {
|
|
const folderLink = e.target.closest('button[data-folder-path]');
|
|
if (folderLink) {
|
|
e.preventDefault();
|
|
const folderPath = folderLink.getAttribute('data-folder-path');
|
|
|
|
// Update tab buttons without changing hash
|
|
const tabButtons = document.querySelectorAll('.tab-button');
|
|
tabButtons.forEach(btn => {
|
|
if (btn.getAttribute('data-tab') === 'browse') {
|
|
btn.classList.add('active');
|
|
} else {
|
|
btn.classList.remove('active');
|
|
}
|
|
});
|
|
|
|
// Update tab content
|
|
const tabContents = document.querySelectorAll('.tab-content');
|
|
tabContents.forEach(content => {
|
|
content.classList.remove('active');
|
|
});
|
|
|
|
const browseSection = document.getElementById('browse-section');
|
|
if (browseSection) {
|
|
browseSection.classList.add('active');
|
|
}
|
|
|
|
// Load the directory directly without setTimeout
|
|
if (typeof loadDirectory === 'function') {
|
|
loadDirectory(folderPath);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
});
|
|
|
|
async function loadMessages() {
|
|
try {
|
|
const response = await fetch('/api/messages');
|
|
if (!response.ok) {
|
|
throw new Error('Failed to load messages');
|
|
}
|
|
messagesData = await response.json();
|
|
renderMessages();
|
|
} catch (error) {
|
|
console.error('Error loading messages:', error);
|
|
const container = document.getElementById('messages-container');
|
|
container.innerHTML = '<div class="alert alert-danger">Fehler beim Laden der Nachrichten</div>';
|
|
}
|
|
}
|
|
|
|
function renderMessages() {
|
|
const container = document.getElementById('messages-container');
|
|
|
|
if (messagesData.length === 0) {
|
|
container.innerHTML = '<div class="no-messages">Keine Nachrichten vorhanden</div>';
|
|
return;
|
|
}
|
|
|
|
let html = '';
|
|
messagesData.forEach(message => {
|
|
const datetime = formatDateTime(message.datetime);
|
|
|
|
// Convert folder: protocol to clickable buttons before markdown parsing
|
|
let processedContent = message.content || '';
|
|
processedContent = processedContent.replace(/\[([^\]]+)\]\(folder:([^)]+)\)/g,
|
|
(match, name, path) => `<button class="folder-link-btn" data-folder-path="${escapeHtml(path)}"><i class="bi bi-folder2"></i> ${escapeHtml(name)}</button>`
|
|
);
|
|
|
|
// Configure marked to allow all links
|
|
marked.setOptions({
|
|
breaks: true,
|
|
gfm: true
|
|
});
|
|
const contentHtml = marked.parse(processedContent);
|
|
|
|
html += `
|
|
<div class="message-card" data-message-id="${message.id}">
|
|
<div class="message-header">
|
|
<h2 class="message-title">${escapeHtml(message.title)}</h2>
|
|
<div class="message-datetime">${datetime}</div>
|
|
</div>
|
|
<div class="message-content">
|
|
${contentHtml}
|
|
</div>
|
|
${admin_enabled ? `
|
|
<div class="message-actions">
|
|
<button class="btn-edit" onclick="editMessage(${message.id})" title="Bearbeiten">
|
|
<i class="bi bi-pencil"></i>
|
|
</button>
|
|
<button class="btn-delete" onclick="deleteMessage(${message.id})" title="Löschen">
|
|
<i class="bi bi-trash"></i>
|
|
</button>
|
|
</div>
|
|
` : ''}
|
|
</div>
|
|
`;
|
|
});
|
|
|
|
container.innerHTML = html;
|
|
}
|
|
|
|
function formatDateTime(datetimeStr) {
|
|
try {
|
|
const date = new Date(datetimeStr);
|
|
const options = {
|
|
year: 'numeric',
|
|
month: 'long',
|
|
day: 'numeric',
|
|
hour: '2-digit',
|
|
minute: '2-digit'
|
|
};
|
|
return date.toLocaleDateString('de-DE', options);
|
|
} catch (error) {
|
|
return datetimeStr;
|
|
}
|
|
}
|
|
|
|
function escapeHtml(text) {
|
|
const map = {
|
|
'&': '&',
|
|
'<': '<',
|
|
'>': '>',
|
|
'"': '"',
|
|
"'": '''
|
|
};
|
|
return text.replace(/[&<>"']/g, m => map[m]);
|
|
}
|
|
|
|
function openMessageModal(messageId = null) {
|
|
currentEditingId = messageId;
|
|
|
|
const modalTitle = document.getElementById('messageModalLabel');
|
|
const messageIdInput = document.getElementById('messageId');
|
|
const titleInput = document.getElementById('messageTitle');
|
|
const datetimeInput = document.getElementById('messageDateTime');
|
|
const contentInput = document.getElementById('messageContent');
|
|
|
|
if (messageId) {
|
|
// Edit mode
|
|
const message = messagesData.find(m => m.id === messageId);
|
|
if (message) {
|
|
modalTitle.textContent = 'Nachricht bearbeiten';
|
|
messageIdInput.value = message.id;
|
|
titleInput.value = message.title;
|
|
|
|
// Format datetime for input (convert to local time)
|
|
const date = new Date(message.datetime);
|
|
const year = date.getFullYear();
|
|
const month = String(date.getMonth() + 1).padStart(2, '0');
|
|
const day = String(date.getDate()).padStart(2, '0');
|
|
const hours = String(date.getHours()).padStart(2, '0');
|
|
const minutes = String(date.getMinutes()).padStart(2, '0');
|
|
const formattedDate = `${year}-${month}-${day}T${hours}:${minutes}`;
|
|
datetimeInput.value = formattedDate;
|
|
|
|
contentInput.value = message.content;
|
|
}
|
|
} else {
|
|
// Add mode
|
|
modalTitle.textContent = 'Neue Nachricht';
|
|
messageIdInput.value = '';
|
|
titleInput.value = '';
|
|
|
|
// Set current datetime (use local time)
|
|
const now = new Date();
|
|
const year = now.getFullYear();
|
|
const month = String(now.getMonth() + 1).padStart(2, '0');
|
|
const day = String(now.getDate()).padStart(2, '0');
|
|
const hours = String(now.getHours()).padStart(2, '0');
|
|
const minutes = String(now.getMinutes()).padStart(2, '0');
|
|
const formattedNow = `${year}-${month}-${day}T${hours}:${minutes}`;
|
|
datetimeInput.value = formattedNow;
|
|
|
|
contentInput.value = '';
|
|
}
|
|
|
|
messageModal.show();
|
|
}
|
|
|
|
async function saveMessage() {
|
|
const messageId = document.getElementById('messageId').value;
|
|
const title = document.getElementById('messageTitle').value.trim();
|
|
const datetime = document.getElementById('messageDateTime').value;
|
|
const content = document.getElementById('messageContent').value.trim();
|
|
|
|
if (!title || !datetime || !content) {
|
|
alert('Bitte füllen Sie alle Felder aus.');
|
|
return;
|
|
}
|
|
|
|
// Convert datetime-local to ISO string
|
|
const datetimeISO = new Date(datetime).toISOString();
|
|
|
|
const data = {
|
|
title: title,
|
|
datetime: datetimeISO,
|
|
content: content
|
|
};
|
|
|
|
try {
|
|
let response;
|
|
if (messageId) {
|
|
// Update existing message
|
|
response = await fetch(`/api/messages/${messageId}`, {
|
|
method: 'PUT',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify(data)
|
|
});
|
|
} else {
|
|
// Create new message
|
|
response = await fetch('/api/messages', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify(data)
|
|
});
|
|
}
|
|
|
|
if (!response.ok) {
|
|
throw new Error('Failed to save message');
|
|
}
|
|
|
|
messageModal.hide();
|
|
loadMessages();
|
|
} catch (error) {
|
|
console.error('Error saving message:', error);
|
|
alert('Fehler beim Speichern der Nachricht.');
|
|
}
|
|
}
|
|
|
|
function editMessage(messageId) {
|
|
openMessageModal(messageId);
|
|
}
|
|
|
|
async function deleteMessage(messageId) {
|
|
if (!confirm('Möchten Sie diese Nachricht wirklich löschen?')) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const response = await fetch(`/api/messages/${messageId}`, {
|
|
method: 'DELETE'
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error('Failed to delete message');
|
|
}
|
|
|
|
loadMessages();
|
|
} catch (error) {
|
|
console.error('Error deleting message:', error);
|
|
alert('Fehler beim Löschen der Nachricht.');
|
|
}
|
|
}
|
|
|
|
// File browser functionality
|
|
let currentBrowserPath = '';
|
|
let fileBrowserCollapse = null;
|
|
|
|
function toggleFileBrowser() {
|
|
const collapse = document.getElementById('fileBrowserCollapse');
|
|
|
|
// Initialize collapse on first use
|
|
if (!fileBrowserCollapse) {
|
|
fileBrowserCollapse = new bootstrap.Collapse(collapse, { toggle: false });
|
|
}
|
|
|
|
if (collapse.classList.contains('show')) {
|
|
fileBrowserCollapse.hide();
|
|
} else {
|
|
fileBrowserCollapse.show();
|
|
loadFileBrowser('');
|
|
}
|
|
}
|
|
|
|
async function loadFileBrowser(path) {
|
|
currentBrowserPath = path;
|
|
const container = document.getElementById('fileBrowserContent');
|
|
const pathDisplay = document.getElementById('currentPath');
|
|
|
|
pathDisplay.textContent = path || '/';
|
|
container.innerHTML = '<div class="text-center py-3"><div class="spinner-border spinner-border-sm" role="status"></div></div>';
|
|
|
|
try {
|
|
const response = await fetch(`/api/path/${path}`);
|
|
if (!response.ok) {
|
|
throw new Error('Failed to load directory');
|
|
}
|
|
|
|
const data = await response.json();
|
|
renderFileBrowser(data);
|
|
} catch (error) {
|
|
console.error('Error loading directory:', error);
|
|
container.innerHTML = '<div class="alert alert-danger">Fehler beim Laden des Verzeichnisses</div>';
|
|
}
|
|
}
|
|
|
|
function renderFileBrowser(data) {
|
|
const container = document.getElementById('fileBrowserContent');
|
|
let html = '<ul class="list-group list-group-flush">';
|
|
|
|
// Add parent directory link if not at root
|
|
if (currentBrowserPath) {
|
|
const parentPath = currentBrowserPath.split('/').slice(0, -1).join('/');
|
|
html += `
|
|
<li class="list-group-item list-group-item-action" style="cursor: pointer;" data-action="navigate" data-path="${escapeHtml(parentPath)}">
|
|
<i class="bi bi-arrow-up"></i> ..
|
|
</li>
|
|
`;
|
|
}
|
|
|
|
// Add directories
|
|
data.directories.forEach(dir => {
|
|
html += `
|
|
<li class="list-group-item list-group-item-action d-flex justify-content-between align-items-center" style="cursor: pointer;" data-action="navigate" data-path="${escapeHtml(dir.path)}" data-name="${escapeHtml(dir.name)}">
|
|
<span>
|
|
<i class="bi bi-folder"></i> ${escapeHtml(dir.name)}
|
|
</span>
|
|
<button class="btn btn-sm btn-primary" data-action="insert-link" data-path="${escapeHtml(dir.path)}" data-name="${escapeHtml(dir.name)}" data-type="folder">
|
|
<i class="bi bi-link-45deg"></i>
|
|
</button>
|
|
</li>
|
|
`;
|
|
});
|
|
|
|
// Add files
|
|
data.files.forEach(file => {
|
|
const icon = file.file_type === 'music' ? 'bi-music-note' :
|
|
file.file_type === 'image' ? 'bi-image' : 'bi-file-earmark';
|
|
html += `
|
|
<li class="list-group-item list-group-item-action d-flex justify-content-between align-items-center" style="cursor: pointer;" data-action="insert-link" data-path="${escapeHtml(file.path)}" data-name="${escapeHtml(file.name)}" data-type="file">
|
|
<span>
|
|
<i class="bi ${icon}"></i> ${escapeHtml(file.name)}
|
|
</span>
|
|
<button class="btn btn-sm btn-primary" data-action="insert-link" data-path="${escapeHtml(file.path)}" data-name="${escapeHtml(file.name)}" data-type="file">
|
|
<i class="bi bi-link-45deg"></i>
|
|
</button>
|
|
</li>
|
|
`;
|
|
});
|
|
|
|
html += '</ul>';
|
|
container.innerHTML = html;
|
|
}
|
|
|
|
function insertLink(path, name, type) {
|
|
const contentTextarea = document.getElementById('messageContent');
|
|
// For folders, use folder: protocol; for files use /media/
|
|
const url = type === 'folder' ? `folder:${path}` : `/media/${path}`;
|
|
const linkText = `[${name}](${url})`;
|
|
|
|
// Insert at cursor position
|
|
const start = contentTextarea.selectionStart;
|
|
const end = contentTextarea.selectionEnd;
|
|
const text = contentTextarea.value;
|
|
|
|
contentTextarea.value = text.substring(0, start) + linkText + text.substring(end);
|
|
contentTextarea.selectionStart = contentTextarea.selectionEnd = start + linkText.length;
|
|
contentTextarea.focus();
|
|
|
|
// Optionally close the browser after inserting
|
|
fileBrowserCollapse.hide();
|
|
}
|