wip
This commit is contained in:
parent
17885b005c
commit
f5c8e9ee23
4 changed files with 756 additions and 44 deletions
|
@ -51,6 +51,104 @@
|
|||
-webkit-line-clamp: 3;
|
||||
-webkit-box-orient: vertical;
|
||||
text-overflow: ellipsis;
|
||||
transition: max-height 0.3s ease;
|
||||
}
|
||||
|
||||
.document-preview.expanded {
|
||||
max-height: 20rem;
|
||||
-webkit-line-clamp: unset;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.document-preview.fully-expanded {
|
||||
max-height: 60rem;
|
||||
-webkit-line-clamp: unset;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.preview-fold-btn {
|
||||
display: block;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
padding: 0.25rem;
|
||||
color: #58a6ff;
|
||||
background-color: rgba(88, 166, 255, 0.05);
|
||||
border-radius: 0.25rem;
|
||||
cursor: pointer;
|
||||
font-size: 0.75rem;
|
||||
margin-top: 0.5rem;
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
.preview-fold-btn:hover {
|
||||
background-color: rgba(88, 166, 255, 0.1);
|
||||
}
|
||||
|
||||
.document-preview .markdown-content {
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.document-preview .markdown-content h1 {
|
||||
font-size: 1.2rem;
|
||||
margin-top: 0.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.document-preview .markdown-content h2 {
|
||||
font-size: 1.1rem;
|
||||
margin-top: 0.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.document-preview .markdown-content h3,
|
||||
.document-preview .markdown-content h4,
|
||||
.document-preview .markdown-content h5,
|
||||
.document-preview .markdown-content h6 {
|
||||
font-size: 1rem;
|
||||
margin-top: 0.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.document-preview .markdown-content p {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.document-preview .markdown-content ul,
|
||||
.document-preview .markdown-content ol {
|
||||
padding-left: 1.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.document-preview .markdown-content pre {
|
||||
background-color: #1e1e2e;
|
||||
padding: 0.5rem;
|
||||
border-radius: 0.25rem;
|
||||
overflow-x: auto;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.document-preview .markdown-content blockquote {
|
||||
border-left: 3px solid #30363d;
|
||||
padding-left: 0.5rem;
|
||||
color: #8b949e;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.document-preview .markdown-content img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.document-preview .markdown-content table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.document-preview .markdown-content th,
|
||||
.document-preview .markdown-content td {
|
||||
border: 1px solid #30363d;
|
||||
padding: 0.25rem 0.5rem;
|
||||
}
|
||||
|
||||
.document-tag {
|
||||
|
@ -91,6 +189,49 @@
|
|||
background-color: rgba(80, 250, 123, 0.2);
|
||||
color: #50fa7b;
|
||||
}
|
||||
|
||||
.document-actions {
|
||||
display: flex;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
padding: 0.25rem;
|
||||
border-radius: 0.25rem;
|
||||
color: #8b949e;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.action-btn:hover {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.action-btn.delete:hover {
|
||||
color: #f85149;
|
||||
background-color: rgba(248, 81, 73, 0.1);
|
||||
}
|
||||
|
||||
.confirm-delete-modal {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 50;
|
||||
}
|
||||
|
||||
.confirm-delete-content {
|
||||
background-color: #2d333b;
|
||||
border-radius: 0.5rem;
|
||||
width: 90%;
|
||||
max-width: 28rem;
|
||||
padding: 1.5rem;
|
||||
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
|
@ -108,7 +249,7 @@
|
|||
|
||||
<!-- View toggle -->
|
||||
<div class="view-toggle">
|
||||
<button id="grid-view-btn" class="view-toggle-btn active">
|
||||
<button id="grid-view-btn" class="view-toggle-btn active-view">
|
||||
<i class="mdi mdi-view-grid"></i> Grid
|
||||
</button>
|
||||
<button id="list-view-btn" class="view-toggle-btn">
|
||||
|
@ -144,6 +285,9 @@
|
|||
<a href="{{ url_for('main.export_document', doc_id=doc.id) }}" class="block px-4 py-2 text-gray-300 hover:bg-gray-600 hover:text-white">
|
||||
<i class="mdi mdi-download-outline mr-2"></i> Export
|
||||
</a>
|
||||
<button class="delete-document-btn block w-full text-left px-4 py-2 text-red-400 hover:bg-gray-600 hover:text-red-300" data-doc-id="{{ doc.id }}" data-doc-title="{{ doc.title }}">
|
||||
<i class="mdi mdi-delete-outline mr-2"></i> Delete
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -158,9 +302,13 @@
|
|||
{% endif %}
|
||||
|
||||
<div class="document-preview text-gray-400 text-sm mb-3">
|
||||
{{ doc.content[:200] }}{% if doc.content|length > 200 %}...{% endif %}
|
||||
<div class="markdown-content" data-content="{{ doc.content }}">
|
||||
<!-- Markdown will be rendered here -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button class="preview-fold-btn" data-doc-id="{{ doc.id }}">Show more</button>
|
||||
|
||||
<div class="flex items-center justify-between mt-3 text-xs text-gray-500">
|
||||
<div>
|
||||
<i class="mdi mdi-clock-outline mr-1"></i>
|
||||
|
@ -220,20 +368,29 @@
|
|||
</div>
|
||||
|
||||
<div class="document-preview text-gray-400 text-sm">
|
||||
{{ doc.content[:200] }}{% if doc.content|length > 200 %}...{% endif %}
|
||||
<div class="markdown-content" data-content="{{ doc.content }}">
|
||||
<!-- Markdown will be rendered here -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button class="preview-fold-btn" data-doc-id="{{ doc.id }}-list">Show more</button>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center mt-2 sm:mt-0">
|
||||
<a href="{{ url_for('main.view_document', doc_id=doc.id) }}" class="p-2 text-gray-400 hover:text-primary rounded" title="View">
|
||||
<i class="mdi mdi-eye-outline"></i>
|
||||
</a>
|
||||
<a href="{{ url_for('main.edit_document', doc_id=doc.id) }}" class="p-2 text-gray-400 hover:text-primary rounded" title="Edit">
|
||||
<i class="mdi mdi-pencil-outline"></i>
|
||||
</a>
|
||||
<a href="{{ url_for('main.export_document', doc_id=doc.id) }}" class="p-2 text-gray-400 hover:text-primary rounded" title="Export">
|
||||
<i class="mdi mdi-download-outline"></i>
|
||||
</a>
|
||||
<div class="document-actions">
|
||||
<a href="{{ url_for('main.view_document', doc_id=doc.id) }}" class="action-btn" title="View">
|
||||
<i class="mdi mdi-eye-outline"></i>
|
||||
</a>
|
||||
<a href="{{ url_for('main.edit_document', doc_id=doc.id) }}" class="action-btn" title="Edit">
|
||||
<i class="mdi mdi-pencil-outline"></i>
|
||||
</a>
|
||||
<a href="{{ url_for('main.export_document', doc_id=doc.id) }}" class="action-btn" title="Export">
|
||||
<i class="mdi mdi-download-outline"></i>
|
||||
</a>
|
||||
<button class="action-btn delete delete-document-btn" title="Delete" data-doc-id="{{ doc.id }}" data-doc-title="{{ doc.title }}">
|
||||
<i class="mdi mdi-delete-outline"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -259,11 +416,130 @@
|
|||
<p class="text-gray-500">Try a different search term</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Delete confirmation modal -->
|
||||
<div id="delete-document-modal" class="confirm-delete-modal hidden">
|
||||
<div class="confirm-delete-content">
|
||||
<h3 class="text-lg font-medium text-white mb-4">Delete Document</h3>
|
||||
<p class="text-gray-300 mb-6">Are you sure you want to delete "<span id="document-delete-name"></span>"? This action cannot be undone.</p>
|
||||
<div class="flex justify-end space-x-3">
|
||||
<button id="cancel-delete-doc" class="px-4 py-2 bg-gray-700 text-white rounded-md hover:bg-gray-600 transition-colors">
|
||||
Cancel
|
||||
</button>
|
||||
<button id="confirm-delete-doc" class="px-4 py-2 bg-red-600 text-white rounded-md hover:bg-red-500 transition-colors">
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_js %}
|
||||
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/highlight.min.js"></script>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Set up Marked.js for markdown rendering
|
||||
marked.setOptions({
|
||||
gfm: true,
|
||||
breaks: true,
|
||||
sanitize: false,
|
||||
highlight: function(code, lang) {
|
||||
if (lang && hljs.getLanguage(lang)) {
|
||||
return hljs.highlight(code, { language: lang }).value;
|
||||
}
|
||||
return hljs.highlightAuto(code).value;
|
||||
}
|
||||
});
|
||||
|
||||
// Custom renderer for alert blocks
|
||||
const renderer = new marked.Renderer();
|
||||
|
||||
// Fix blockquote renderer for GitHub-style alerts
|
||||
const originalBlockquote = renderer.blockquote;
|
||||
renderer.blockquote = function(quote) {
|
||||
const text = quote.replace(/<\/?p>/g, '');
|
||||
const admonitionRegex = /^\s*\[!(NOTE|TIP|IMPORTANT|WARNING|CAUTION|DANGER)\]\s*([\s\S]*)/i;
|
||||
const match = text.match(admonitionRegex);
|
||||
|
||||
if (match) {
|
||||
const type = match[1].toLowerCase();
|
||||
const title = match[1].charAt(0).toUpperCase() + match[1].slice(1).toLowerCase();
|
||||
const content = match[2] ? match[2].trim() : '';
|
||||
|
||||
return `<div class="admonition admonition-${type}">
|
||||
<p class="admonition-title">${title}</p>
|
||||
<p>${content}</p>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
return originalBlockquote.call(this, quote);
|
||||
};
|
||||
|
||||
marked.use({ renderer });
|
||||
|
||||
// Render markdown content in document previews
|
||||
document.querySelectorAll('.markdown-content').forEach(container => {
|
||||
const content = container.getAttribute('data-content');
|
||||
if (content) {
|
||||
// Limit the content length for initial display
|
||||
const previewLength = 300;
|
||||
const shortenedContent = content.length > previewLength
|
||||
? content.substring(0, previewLength) + "..."
|
||||
: content;
|
||||
|
||||
container.innerHTML = marked.parse(shortenedContent);
|
||||
}
|
||||
});
|
||||
|
||||
// Handle preview fold/unfold
|
||||
const previewBtns = document.querySelectorAll('.preview-fold-btn');
|
||||
previewBtns.forEach(btn => {
|
||||
btn.addEventListener('click', function() {
|
||||
const docId = this.getAttribute('data-doc-id');
|
||||
const previewEl = this.previousElementSibling;
|
||||
const markdownContainer = previewEl.querySelector('.markdown-content');
|
||||
const content = markdownContainer.getAttribute('data-content');
|
||||
|
||||
if (previewEl.classList.contains('expanded')) {
|
||||
if (previewEl.classList.contains('fully-expanded')) {
|
||||
// Collapse to normal preview
|
||||
previewEl.classList.remove('fully-expanded');
|
||||
previewEl.classList.remove('expanded');
|
||||
|
||||
const previewLength = 300;
|
||||
const shortenedContent = content.length > previewLength
|
||||
? content.substring(0, previewLength) + "..."
|
||||
: content;
|
||||
|
||||
markdownContainer.innerHTML = marked.parse(shortenedContent);
|
||||
this.textContent = 'Show more';
|
||||
} else {
|
||||
// Expand to full view
|
||||
previewEl.classList.add('fully-expanded');
|
||||
markdownContainer.innerHTML = marked.parse(content);
|
||||
this.textContent = 'Show less';
|
||||
}
|
||||
} else {
|
||||
// Expand to first level
|
||||
previewEl.classList.add('expanded');
|
||||
|
||||
const previewLength = 1000;
|
||||
const shortenedContent = content.length > previewLength
|
||||
? content.substring(0, previewLength) + "..."
|
||||
: content;
|
||||
|
||||
markdownContainer.innerHTML = marked.parse(shortenedContent);
|
||||
this.textContent = content.length > previewLength ? 'Show even more' : 'Show less';
|
||||
}
|
||||
|
||||
// Apply syntax highlighting
|
||||
previewEl.querySelectorAll('pre code').forEach(block => {
|
||||
hljs.highlightElement(block);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// View toggle functionality
|
||||
const gridViewBtn = document.getElementById('grid-view-btn');
|
||||
const listViewBtn = document.getElementById('list-view-btn');
|
||||
|
@ -355,6 +631,103 @@
|
|||
});
|
||||
});
|
||||
|
||||
// Delete document functionality
|
||||
const deleteModal = document.getElementById('delete-document-modal');
|
||||
const documentNameSpan = document.getElementById('document-delete-name');
|
||||
const cancelDeleteBtn = document.getElementById('cancel-delete-doc');
|
||||
const confirmDeleteBtn = document.getElementById('confirm-delete-doc');
|
||||
let currentDocId = null;
|
||||
|
||||
// Show delete confirmation modal
|
||||
document.querySelectorAll('.delete-document-btn').forEach(btn => {
|
||||
btn.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
const docId = this.getAttribute('data-doc-id');
|
||||
const docTitle = this.getAttribute('data-doc-title');
|
||||
|
||||
documentNameSpan.textContent = docTitle;
|
||||
currentDocId = docId;
|
||||
deleteModal.classList.remove('hidden');
|
||||
});
|
||||
});
|
||||
|
||||
// Cancel document deletion
|
||||
cancelDeleteBtn.addEventListener('click', function() {
|
||||
deleteModal.classList.add('hidden');
|
||||
currentDocId = null;
|
||||
});
|
||||
|
||||
// Confirm document deletion
|
||||
confirmDeleteBtn.addEventListener('click', function() {
|
||||
if (currentDocId) {
|
||||
// Send delete request to server
|
||||
fetch(`/api/document/${currentDocId}`, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') || ''
|
||||
}
|
||||
})
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to delete document');
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
// Remove the document from the DOM
|
||||
document.querySelectorAll(`[data-doc-id="${currentDocId}"]`).forEach(el => {
|
||||
const card = el.closest('.document-card, .document-item');
|
||||
if (card) {
|
||||
card.remove();
|
||||
}
|
||||
});
|
||||
|
||||
// Close the modal
|
||||
deleteModal.classList.add('hidden');
|
||||
|
||||
// Show a success message
|
||||
showNotification('Document deleted successfully');
|
||||
|
||||
// Update count in the heading
|
||||
const countEl = document.querySelector('h2');
|
||||
if (countEl) {
|
||||
const count = document.querySelectorAll('.document-card').length;
|
||||
countEl.textContent = `All Documents (${count})`;
|
||||
}
|
||||
|
||||
// Show "no results" if there are no documents left
|
||||
if (document.querySelectorAll('.document-card').length === 0) {
|
||||
noResults.classList.remove('hidden');
|
||||
gridView.classList.add('hidden');
|
||||
listView.classList.add('hidden');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
showNotification('Error deleting document', 'error');
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Helper function to show notifications
|
||||
function showNotification(message, type = 'success') {
|
||||
const notification = document.createElement('div');
|
||||
const bgColor = type === 'success' ? 'bg-green-600' : 'bg-red-600';
|
||||
|
||||
notification.className = `fixed bottom-4 right-4 ${bgColor} text-white px-4 py-2 rounded-md shadow-lg transform translate-y-0 opacity-100 transition-all duration-300`;
|
||||
notification.textContent = message;
|
||||
|
||||
document.body.appendChild(notification);
|
||||
|
||||
setTimeout(() => {
|
||||
notification.classList.add('translate-y-16', 'opacity-0');
|
||||
setTimeout(() => notification.remove(), 300);
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
// Highlight current category in sidebar
|
||||
const sidebarCategories = document.querySelectorAll('.category-item');
|
||||
sidebarCategories.forEach(item => {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue