flask-vim-docs/app/templates/document_view.html
2025-04-17 11:27:48 +02:00

335 lines
No EOL
10 KiB
HTML

{% extends "base.html" %}
{% block title %}{{ document.title }} - Vim Docs{% endblock %}
{% block header_title %}{{ document.title }}{% endblock %}
{% block header_actions %}
<a href="{{ url_for('main.edit_document', doc_id=document.id) }}" class="inline-flex items-center px-4 py-2 bg-primary text-black rounded-md hover:bg-primary-dark transition-colors">
<i class="mdi mdi-pencil mr-2"></i> Edit
</a>
<a href="{{ url_for('main.export_document', doc_id=document.id) }}" class="inline-flex items-center px-4 py-2 bg-gray-700 text-white rounded-md hover:bg-gray-600 transition-colors ml-2">
<i class="mdi mdi-download mr-2"></i> Export
</a>
{% endblock %}
{% block extra_css %}
<!-- GitHub Markdown CSS -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/5.2.0/github-markdown-dark.min.css">
<!-- Highlight.js for syntax highlighting -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/styles/github-dark.min.css">
<style>
.document-container {
display: flex;
flex-direction: column;
min-height: calc(100vh - 64px);
background-color: #0d1117;
}
.document-header {
padding: 20px 30px;
border-bottom: 1px solid #30363d;
display: flex;
flex-wrap: wrap;
gap: 10px;
align-items: center;
}
.document-metadata {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 15px;
margin-right: auto;
}
.metadata-item {
display: flex;
align-items: center;
font-size: 0.875rem;
color: #8b949e;
}
.metadata-item i {
margin-right: 5px;
color: #6e7681;
}
.document-tags {
display: flex;
flex-wrap: wrap;
gap: 5px;
}
.document-tag {
background-color: rgba(80, 250, 123, 0.1);
color: #50fa7b;
padding: 2px 8px;
border-radius: 12px;
font-size: 0.75rem;
transition: all 0.2s ease;
}
.document-tag:hover {
background-color: rgba(80, 250, 123, 0.2);
transform: translateY(-1px);
}
.document-body {
flex: 1;
overflow: auto;
padding: 0;
}
.markdown-body {
box-sizing: border-box;
min-width: 200px;
max-width: 980px;
margin: 0 auto;
padding: 45px;
}
/* GitHub-style admonitions/alerts */
.markdown-body .admonition {
padding: 1rem;
border-left: 4px solid;
margin: 1em 0;
border-radius: 6px;
background-color: rgba(175, 184, 193, 0.2);
}
.markdown-body .admonition-title {
font-weight: 600;
margin-top: 0;
}
.markdown-body .admonition-note {
border-color: #2b6eff;
background-color: rgba(43, 110, 255, 0.1);
}
.markdown-body .admonition-note .admonition-title {
color: #2b6eff;
}
.markdown-body .admonition-tip {
border-color: #3fb950;
background-color: rgba(63, 185, 80, 0.1);
}
.markdown-body .admonition-tip .admonition-title {
color: #3fb950;
}
.markdown-body .admonition-important {
border-color: #a371f7;
background-color: rgba(163, 113, 247, 0.1);
}
.markdown-body .admonition-important .admonition-title {
color: #a371f7;
}
.markdown-body .admonition-warning {
border-color: #d29922;
background-color: rgba(210, 153, 34, 0.1);
}
.markdown-body .admonition-warning .admonition-title {
color: #d29922;
}
.markdown-body .admonition-caution {
border-color: #f85149;
background-color: rgba(248, 81, 73, 0.1);
}
.markdown-body .admonition-caution .admonition-title {
color: #f85149;
}
.markdown-body .admonition-danger {
border-color: #cf222e;
background-color: rgba(207, 34, 46, 0.1);
}
.markdown-body .admonition-danger .admonition-title {
color: #cf222e;
}
/* Animation for fade-in */
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
.fade-in {
animation: fadeIn 0.5s ease-in;
}
/* Mobile adjustments */
@media (max-width: 768px) {
.document-header {
padding: 15px;
}
.markdown-body {
padding: 25px 15px;
}
}
</style>
{% endblock %}
{% block content %}
<div class="document-container">
<div class="document-header">
<div class="document-metadata">
<div class="metadata-item">
<i class="mdi mdi-clock-outline"></i>
Updated {{ document.updated_date.strftime('%b %d, %Y') }}
</div>
{% if document.category %}
<div class="metadata-item">
<i class="mdi {{ document.category.icon }}"></i>
<a href="{{ url_for('main.view_category', category_id=document.category.id) }}" class="hover:text-primary transition-colors">
{{ document.category.name }}
</a>
</div>
{% endif %}
</div>
{% if document.tags %}
<div class="document-tags">
{% for tag in document.tags %}
<span class="document-tag">{{ tag.name }}</span>
{% endfor %}
</div>
{% endif %}
</div>
<div class="document-body">
<div id="document-content" class="markdown-body fade-in">
<!-- Content will be rendered here -->
</div>
</div>
</div>
{% endblock %}
{% block extra_js %}
<!-- Marked.js for Markdown parsing -->
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<!-- Highlight.js for syntax highlighting -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/highlight.min.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
// Markdown rendering configuration
marked.setOptions({
renderer: new marked.Renderer(),
gfm: true,
tables: true,
breaks: false,
pedantic: false,
sanitize: false,
smartLists: true,
smartypants: 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 GitHub-style alert blocks
const renderer = new marked.Renderer();
// Fix the link renderer
const originalLink = renderer.link;
renderer.link = function(href, title, text) {
const html = originalLink.call(this, href, title, text);
return html.replace(/^<a /, '<a target="_blank" rel="noopener noreferrer" ');
};
// Fix the blockquote renderer for GitHub-style alert blocks
const originalBlockquote = renderer.blockquote;
renderer.blockquote = function(quote) {
// Extract text without p tags
const text = quote.replace(/<\/?p>/g, '');
// Pattern for GitHub-style alerts: > [!NOTE], > [!WARNING], etc.
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>`;
}
// Fall back to the original blockquote renderer
return originalBlockquote.call(this, quote);
};
// Apply custom renderer
marked.use({ renderer: renderer });
// Render markdown content
const documentContent = document.getElementById('document-content');
const markdownContent = `{{ document.content|replace("\n", "\\n")|replace("'", "\\'")|safe }}`;
// Render directly without JSON parsing
documentContent.innerHTML = marked.parse(markdownContent);
// Add linkification for dynamic links
document.querySelectorAll('#document-content a[href^="#doc:"]').forEach(link => {
const docId = link.getAttribute('href').replace('#doc:', '');
link.setAttribute('href', `/document/${docId}`);
// Don't open internal doc links in new tab
link.removeAttribute('target');
link.removeAttribute('rel');
});
// Highlight active document in sidebar
highlightActiveDocument();
// Add keyboard shortcuts
document.addEventListener('keydown', function(e) {
// 'e' to edit the current document
if (e.key === 'e' && !e.ctrlKey && !e.metaKey && !e.altKey &&
!(e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA')) {
e.preventDefault();
window.location.href = "{{ url_for('main.edit_document', doc_id=document.id) }}";
}
});
});
function highlightActiveDocument() {
// Find the current document in the sidebar and highlight it
const currentDocId = "{{ document.id }}";
const sidebarItems = document.querySelectorAll('.document-item a');
sidebarItems.forEach(item => {
const href = item.getAttribute('href');
if (href && href.includes(`/document/${currentDocId}`)) {
item.classList.add('text-primary');
// Expand parent category if collapsed
const parentContainer = item.closest('.category-children');
if (parentContainer && parentContainer.style.display === 'none') {
const toggleBtn = parentContainer.parentElement.querySelector('.toggle-btn');
if (toggleBtn) {
toggleBtn.click();
}
}
}
});
}
</script>
{% endblock %}