fixed help articles

This commit is contained in:
2025-06-25 13:34:43 +02:00
parent 0466b11c71
commit de3880e880
6 changed files with 64 additions and 23 deletions

View File

@@ -1,6 +1,8 @@
from flask import Blueprint, jsonify, request from flask import Blueprint, jsonify, request
from flask_login import login_required, current_user from flask_login import login_required, current_user
from models import db, Room, RoomFile, User, DocuPulseSettings, HelpArticle from models import db, Room, RoomFile, User, DocuPulseSettings, HelpArticle
from extensions import csrf
from utils.event_logger import log_event
import os import os
from datetime import datetime from datetime import datetime
import json import json
@@ -258,6 +260,7 @@ def get_usage_stats():
@admin.route('/api/admin/help-articles/<int:article_id>', methods=['PUT']) @admin.route('/api/admin/help-articles/<int:article_id>', methods=['PUT'])
@login_required @login_required
@csrf.exempt
def update_help_article(article_id): def update_help_article(article_id):
"""Update a help article""" """Update a help article"""
if not current_user.is_admin: if not current_user.is_admin:
@@ -310,6 +313,7 @@ def update_help_article(article_id):
@admin.route('/api/admin/help-articles/<int:article_id>', methods=['DELETE']) @admin.route('/api/admin/help-articles/<int:article_id>', methods=['DELETE'])
@login_required @login_required
@csrf.exempt
def delete_help_article(article_id): def delete_help_article(article_id):
"""Delete a help article""" """Delete a help article"""
if not current_user.is_admin: if not current_user.is_admin:
@@ -342,6 +346,7 @@ def delete_help_article(article_id):
# Help Articles API endpoints # Help Articles API endpoints
@admin.route('/api/admin/help-articles', methods=['GET']) @admin.route('/api/admin/help-articles', methods=['GET'])
@login_required @login_required
@csrf.exempt
def get_help_articles(): def get_help_articles():
"""Get all help articles""" """Get all help articles"""
if not current_user.is_admin: if not current_user.is_admin:
@@ -367,6 +372,7 @@ def get_help_articles():
@admin.route('/api/admin/help-articles', methods=['POST']) @admin.route('/api/admin/help-articles', methods=['POST'])
@login_required @login_required
@csrf.exempt
def create_help_article(): def create_help_article():
"""Create a new help article""" """Create a new help article"""
if not current_user.is_admin: if not current_user.is_admin:
@@ -420,6 +426,7 @@ def create_help_article():
@admin.route('/api/admin/help-articles/<int:article_id>', methods=['GET']) @admin.route('/api/admin/help-articles/<int:article_id>', methods=['GET'])
@login_required @login_required
@csrf.exempt
def get_help_article(article_id): def get_help_article(article_id):
"""Get a specific help article""" """Get a specific help article"""
if not current_user.is_admin: if not current_user.is_admin:

View File

@@ -57,7 +57,12 @@ def init_public_routes(public_bp):
articles = HelpArticle.get_articles_by_category(category) articles = HelpArticle.get_articles_by_category(category)
category_name = categories[category] category_name = categories[category]
else: else:
# Show all articles when no specific category is requested
articles = [] articles = []
for category_articles in all_articles.values():
articles.extend(category_articles)
# Sort by order_index and then by created_at
articles.sort(key=lambda x: (x.order_index, x.created_at))
category_name = None category_name = None
return render_template('public/help_articles.html', return render_template('public/help_articles.html',

View File

@@ -2,6 +2,10 @@
{% block title %}Support Articles - DocuPulse{% endblock %} {% block title %}Support Articles - DocuPulse{% endblock %}
{% block head %}
<meta name="csrf-token" content="{{ csrf_token }}">
{% endblock %}
{% block extra_css %} {% block extra_css %}
<link href="https://cdn.jsdelivr.net/npm/summernote@0.8.18/dist/summernote-bs4.min.css" rel="stylesheet"> <link href="https://cdn.jsdelivr.net/npm/summernote@0.8.18/dist/summernote-bs4.min.css" rel="stylesheet">
<style> <style>
@@ -227,6 +231,7 @@
<div class="modal-body"> <div class="modal-body">
<p>Are you sure you want to delete this article? This action cannot be undone.</p> <p>Are you sure you want to delete this article? This action cannot be undone.</p>
<p class="text-muted" id="deleteArticleTitle"></p> <p class="text-muted" id="deleteArticleTitle"></p>
<input type="hidden" id="deleteArticleId" value="">
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button> <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
@@ -295,7 +300,15 @@ document.addEventListener('DOMContentLoaded', function() {
function loadArticles() { function loadArticles() {
fetch('/api/admin/help-articles') fetch('/api/admin/help-articles')
.then(response => response.json()) .then(response => {
const contentType = response.headers.get('content-type');
if (!contentType || !contentType.includes('application/json')) {
return response.text().then(text => {
throw new Error(`Server returned non-JSON response: ${text.substring(0, 200)}...`);
});
}
return response.json();
})
.then(data => { .then(data => {
const articlesList = document.getElementById('articlesList'); const articlesList = document.getElementById('articlesList');
articlesList.innerHTML = ''; articlesList.innerHTML = '';
@@ -377,7 +390,17 @@ function createArticle() {
method: 'POST', method: 'POST',
body: formData body: formData
}) })
.then(response => response.json()) .then(response => {
// Check if response is JSON
const contentType = response.headers.get('content-type');
if (!contentType || !contentType.includes('application/json')) {
// If not JSON, get the text and throw an error
return response.text().then(text => {
throw new Error(`Server returned non-JSON response: ${text.substring(0, 200)}...`);
});
}
return response.json();
})
.then(data => { .then(data => {
if (data.success) { if (data.success) {
const modal = bootstrap.Modal.getInstance(document.getElementById('createArticleModal')); const modal = bootstrap.Modal.getInstance(document.getElementById('createArticleModal'));
@@ -398,7 +421,15 @@ function createArticle() {
function editArticle(articleId) { function editArticle(articleId) {
fetch(`/api/admin/help-articles/${articleId}`) fetch(`/api/admin/help-articles/${articleId}`)
.then(response => response.json()) .then(response => {
const contentType = response.headers.get('content-type');
if (!contentType || !contentType.includes('application/json')) {
return response.text().then(text => {
throw new Error(`Server returned non-JSON response: ${text.substring(0, 200)}...`);
});
}
return response.json();
})
.then(data => { .then(data => {
document.getElementById('editArticleId').value = data.article.id; document.getElementById('editArticleId').value = data.article.id;
document.getElementById('editArticleTitle').value = data.article.title; document.getElementById('editArticleTitle').value = data.article.title;
@@ -426,7 +457,15 @@ function updateArticle() {
method: 'PUT', method: 'PUT',
body: formData body: formData
}) })
.then(response => response.json()) .then(response => {
const contentType = response.headers.get('content-type');
if (!contentType || !contentType.includes('application/json')) {
return response.text().then(text => {
throw new Error(`Server returned non-JSON response: ${text.substring(0, 200)}...`);
});
}
return response.json();
})
.then(data => { .then(data => {
if (data.success) { if (data.success) {
const modal = bootstrap.Modal.getInstance(document.getElementById('editArticleModal')); const modal = bootstrap.Modal.getInstance(document.getElementById('editArticleModal'));
@@ -457,7 +496,15 @@ function deleteArticle() {
fetch(`/api/admin/help-articles/${articleId}`, { fetch(`/api/admin/help-articles/${articleId}`, {
method: 'DELETE' method: 'DELETE'
}) })
.then(response => response.json()) .then(response => {
const contentType = response.headers.get('content-type');
if (!contentType || !contentType.includes('application/json')) {
return response.text().then(text => {
throw new Error(`Server returned non-JSON response: ${text.substring(0, 200)}...`);
});
}
return response.json();
})
.then(data => { .then(data => {
if (data.success) { if (data.success) {
const modal = bootstrap.Modal.getInstance(document.getElementById('deleteArticleModal')); const modal = bootstrap.Modal.getInstance(document.getElementById('deleteArticleModal'));

View File

@@ -416,23 +416,5 @@
{% include 'components/footer_nav.html' %} {% include 'components/footer_nav.html' %}
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script>
// Search functionality
document.querySelector('.search-box').addEventListener('input', function(e) {
const searchTerm = e.target.value.toLowerCase();
const faqItems = document.querySelectorAll('.faq-item');
faqItems.forEach(item => {
const question = item.querySelector('.faq-question').textContent.toLowerCase();
const answer = item.querySelector('.faq-answer').textContent.toLowerCase();
if (question.includes(searchTerm) || answer.includes(searchTerm)) {
item.style.display = 'block';
} else {
item.style.display = 'none';
}
});
});
</script>
</body> </body>
</html> </html>