feat(documents): add document viewer and configuration for loading markdown files
This commit is contained in:
@@ -13,6 +13,9 @@
|
||||
<!-- Mermaid -->
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/mermaid/10.9.1/mermaid.min.js"></script>
|
||||
|
||||
<!-- Marked.js for Markdown parsing -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
||||
|
||||
<!-- Custom Tailwind Config -->
|
||||
<script>
|
||||
tailwind.config = {
|
||||
@@ -90,6 +93,170 @@
|
||||
#diagram-container:active {
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
||||
/* Modal styles */
|
||||
.modal-overlay {
|
||||
backdrop-filter: blur(4px);
|
||||
animation: fadeIn 0.2s ease-out;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
animation: slideUp 0.3s ease-out;
|
||||
}
|
||||
|
||||
@keyframes slideUp {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
/* Markdown styling */
|
||||
.markdown-content {
|
||||
line-height: 1.7;
|
||||
color: #374151;
|
||||
}
|
||||
|
||||
.markdown-content h1 {
|
||||
font-size: 2em;
|
||||
font-weight: 700;
|
||||
margin-top: 1.5em;
|
||||
margin-bottom: 0.5em;
|
||||
padding-bottom: 0.3em;
|
||||
border-bottom: 2px solid #e5e7eb;
|
||||
color: #111827;
|
||||
}
|
||||
|
||||
.markdown-content h1:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.markdown-content h2 {
|
||||
font-size: 1.5em;
|
||||
font-weight: 600;
|
||||
margin-top: 1.5em;
|
||||
margin-bottom: 0.5em;
|
||||
padding-bottom: 0.2em;
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
color: #111827;
|
||||
}
|
||||
|
||||
.markdown-content h3 {
|
||||
font-size: 1.25em;
|
||||
font-weight: 600;
|
||||
margin-top: 1.2em;
|
||||
margin-bottom: 0.5em;
|
||||
color: #111827;
|
||||
}
|
||||
|
||||
.markdown-content h4 {
|
||||
font-size: 1.1em;
|
||||
font-weight: 600;
|
||||
margin-top: 1em;
|
||||
margin-bottom: 0.5em;
|
||||
color: #111827;
|
||||
}
|
||||
|
||||
.markdown-content p {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.markdown-content ul, .markdown-content ol {
|
||||
margin-bottom: 1em;
|
||||
padding-left: 2em;
|
||||
}
|
||||
|
||||
.markdown-content ul {
|
||||
list-style-type: disc;
|
||||
}
|
||||
|
||||
.markdown-content ol {
|
||||
list-style-type: decimal;
|
||||
}
|
||||
|
||||
.markdown-content li {
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
.markdown-content code {
|
||||
background-color: #f3f4f6;
|
||||
padding: 0.2em 0.4em;
|
||||
border-radius: 0.25em;
|
||||
font-size: 0.9em;
|
||||
font-family: 'Courier New', monospace;
|
||||
color: #dc2626;
|
||||
}
|
||||
|
||||
.markdown-content pre {
|
||||
background-color: #1f2937;
|
||||
color: #f9fafb;
|
||||
padding: 1em;
|
||||
border-radius: 0.5em;
|
||||
overflow-x: auto;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.markdown-content pre code {
|
||||
background-color: transparent;
|
||||
padding: 0;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.markdown-content blockquote {
|
||||
border-left: 4px solid #3b82f6;
|
||||
padding-left: 1em;
|
||||
margin: 1em 0;
|
||||
color: #6b7280;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.markdown-content a {
|
||||
color: #3b82f6;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.markdown-content a:hover {
|
||||
color: #2563eb;
|
||||
}
|
||||
|
||||
.markdown-content table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.markdown-content th, .markdown-content td {
|
||||
border: 1px solid #e5e7eb;
|
||||
padding: 0.5em;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.markdown-content th {
|
||||
background-color: #f9fafb;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.markdown-content img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
border-radius: 0.5em;
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
.markdown-content hr {
|
||||
border: none;
|
||||
border-top: 2px solid #e5e7eb;
|
||||
margin: 2em 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
@@ -123,6 +290,9 @@
|
||||
|
||||
<!-- Dynamic diagrams section - ALL diagrams loaded here -->
|
||||
<div id="dynamic-diagrams-section"></div>
|
||||
|
||||
<!-- Documentation section -->
|
||||
<div id="documentation-section"></div>
|
||||
</nav>
|
||||
|
||||
<!-- Footer -->
|
||||
@@ -350,6 +520,17 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Document Viewer -->
|
||||
<div id="document-viewer" class="hidden h-full flex flex-col p-8">
|
||||
<div class="mb-6">
|
||||
<h3 id="document-title" class="text-2xl font-bold text-gray-900"></h3>
|
||||
</div>
|
||||
<div id="document-container"
|
||||
class="flex-1 bg-white rounded-2xl shadow-xl border border-gray-200 overflow-y-auto p-8 markdown-content">
|
||||
<!-- Document content will be loaded here -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</main>
|
||||
</div>
|
||||
|
||||
@@ -358,10 +539,14 @@
|
||||
|
||||
<script>
|
||||
let allDiagrams = [];
|
||||
let allDocuments = [];
|
||||
const homeView = document.getElementById('home-view');
|
||||
const diagramViewer = document.getElementById('diagram-viewer');
|
||||
const diagramContainer = document.getElementById('diagram-container');
|
||||
const diagramTitle = document.getElementById('diagram-title');
|
||||
const documentViewer = document.getElementById('document-viewer');
|
||||
const documentContainer = document.getElementById('document-container');
|
||||
const documentTitle = document.getElementById('document-title');
|
||||
const zoomInBtn = document.getElementById('zoomInBtn');
|
||||
const zoomOutBtn = document.getElementById('zoomOutBtn');
|
||||
const resetBtn = document.getElementById('resetBtn');
|
||||
@@ -415,6 +600,41 @@
|
||||
return hierarchy;
|
||||
}
|
||||
|
||||
// Build hierarchical structure from paths (for documents)
|
||||
function buildDocumentHierarchy(documents) {
|
||||
const hierarchy = {};
|
||||
|
||||
documents.forEach(doc => {
|
||||
const parts = doc.path.split('/');
|
||||
const relevantParts = parts.slice(2, -1); // Remove 'assets/documents/' and filename
|
||||
|
||||
let current = hierarchy;
|
||||
relevantParts.forEach(part => {
|
||||
if (!current[part]) {
|
||||
current[part] = { _items: [], _children: {} };
|
||||
}
|
||||
current = current[part]._children;
|
||||
});
|
||||
|
||||
// Add the item to appropriate level
|
||||
if (relevantParts.length > 0) {
|
||||
let parent = hierarchy[relevantParts[0]];
|
||||
for (let i = 1; i < relevantParts.length; i++) {
|
||||
parent = parent._children[relevantParts[i]];
|
||||
}
|
||||
parent._items.push(doc);
|
||||
} else {
|
||||
// Root level documents
|
||||
if (!hierarchy._root) {
|
||||
hierarchy._root = { _items: [], _children: {} };
|
||||
}
|
||||
hierarchy._root._items.push(doc);
|
||||
}
|
||||
});
|
||||
|
||||
return hierarchy;
|
||||
}
|
||||
|
||||
// Create navigation from hierarchy
|
||||
function createNavigation(hierarchy, parentElement, level = 0) {
|
||||
// First, show root level items if any
|
||||
@@ -454,6 +674,64 @@
|
||||
});
|
||||
}
|
||||
|
||||
// Create document navigation from hierarchy
|
||||
function createDocumentNavigation(hierarchy, parentElement, level = 0) {
|
||||
// First, show root level items if any
|
||||
if (hierarchy._root && hierarchy._root._items.length > 0) {
|
||||
const mainHeading = document.createElement('div');
|
||||
mainHeading.className = 'px-4 text-xs font-semibold text-gray-500 uppercase tracking-wider mt-6 mb-3';
|
||||
mainHeading.textContent = 'Documentation';
|
||||
parentElement.appendChild(mainHeading);
|
||||
|
||||
hierarchy._root._items.forEach(doc => {
|
||||
createDocumentLink(doc, parentElement, 0);
|
||||
});
|
||||
}
|
||||
|
||||
// Then process nested categories
|
||||
Object.keys(hierarchy).forEach(key => {
|
||||
if (key === '_items' || key === '_children' || key === '_root') return;
|
||||
|
||||
const section = hierarchy[key];
|
||||
const heading = document.createElement('div');
|
||||
heading.className = 'px-4 text-xs font-semibold text-gray-500 uppercase tracking-wider ' +
|
||||
(level === 0 ? 'mt-6 mb-3' : 'mt-4 mb-2 pl-8');
|
||||
heading.textContent = key.split('-').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' ');
|
||||
parentElement.appendChild(heading);
|
||||
|
||||
// Add items in this section
|
||||
if (section._items && section._items.length > 0) {
|
||||
section._items.forEach(doc => {
|
||||
createDocumentLink(doc, parentElement, level);
|
||||
});
|
||||
}
|
||||
|
||||
// Recursively add children
|
||||
if (section._children && Object.keys(section._children).length > 0) {
|
||||
createDocumentNavigation(section._children, parentElement, level + 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Helper function to create a document link
|
||||
function createDocumentLink(doc, parentElement, level) {
|
||||
const link = document.createElement('a');
|
||||
link.href = '#';
|
||||
link.className = 'nav-item flex items-center space-x-3 px-4 py-3 rounded-xl text-gray-700 hover:bg-gray-50 mb-1' +
|
||||
(level > 0 ? ' pl-8' : '');
|
||||
link.onclick = (e) => {
|
||||
e.preventDefault();
|
||||
showView('document', link, doc.path, doc.title);
|
||||
};
|
||||
|
||||
const iconSvg = `<svg class="w-5 h-5 text-gray-400 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
|
||||
</svg>`;
|
||||
|
||||
link.innerHTML = `${iconSvg}<span class="text-sm">${doc.title}</span>`;
|
||||
parentElement.appendChild(link);
|
||||
}
|
||||
|
||||
// Helper function to create a diagram link
|
||||
function createDiagramLink(diagram, parentElement, level) {
|
||||
const link = document.createElement('a');
|
||||
@@ -477,7 +755,7 @@
|
||||
</svg>`;
|
||||
}
|
||||
|
||||
link.innerHTML = `${iconSvg}<span class="text-sm">${diagram.title}</span>`;
|
||||
link.innerHTML = iconSvg + '<span class="text-sm">' + diagram.title + '</span>';
|
||||
parentElement.appendChild(link);
|
||||
}
|
||||
|
||||
@@ -501,7 +779,6 @@
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading diagrams configuration:', error);
|
||||
// Show a helpful message in the UI
|
||||
const errorDiv = document.createElement('div');
|
||||
errorDiv.className = 'px-4 py-3 mx-2 mt-4 text-xs text-amber-600 bg-amber-50 rounded-lg border border-amber-200';
|
||||
errorDiv.innerHTML = `
|
||||
@@ -512,9 +789,40 @@
|
||||
dynamicSection.appendChild(errorDiv);
|
||||
}
|
||||
}
|
||||
|
||||
// Load all documentation from config
|
||||
async function loadAllDocuments() {
|
||||
const documentationSection = document.getElementById('documentation-section');
|
||||
|
||||
try {
|
||||
const response = await fetch('./assets/documents/documents-config.json');
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to load documents config: ${response.status}`);
|
||||
}
|
||||
|
||||
const text = await response.text();
|
||||
console.log('Loaded documents config:', text);
|
||||
allDocuments = JSON.parse(text);
|
||||
|
||||
if (allDocuments && allDocuments.length > 0) {
|
||||
const hierarchy = buildDocumentHierarchy(allDocuments);
|
||||
createDocumentNavigation(hierarchy, documentationSection);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading documents configuration:', error);
|
||||
const errorDiv = document.createElement('div');
|
||||
errorDiv.className = 'px-4 py-3 mx-2 mt-4 text-xs text-amber-600 bg-amber-50 rounded-lg border border-amber-200';
|
||||
errorDiv.innerHTML = `
|
||||
<div class="font-semibold mb-1">⚠️ Documentation</div>
|
||||
<div>Unable to load documents-config.json</div>
|
||||
<div class="mt-1 text-amber-500">${error.message}</div>
|
||||
`;
|
||||
documentationSection.appendChild(errorDiv);
|
||||
}
|
||||
}
|
||||
|
||||
function setActiveNav(activeLink) {
|
||||
document.querySelectorAll('.sidebar a').forEach(link => {
|
||||
document.querySelectorAll('#sidebar-nav a').forEach(link => {
|
||||
link.classList.remove('bg-primary-50', 'border', 'border-primary-200', 'text-primary-700');
|
||||
link.classList.add('text-gray-700');
|
||||
});
|
||||
@@ -529,14 +837,17 @@
|
||||
panzoomInstance = null;
|
||||
}
|
||||
diagramContainer.innerHTML = '';
|
||||
documentContainer.innerHTML = '';
|
||||
currentScale = 1;
|
||||
|
||||
if (viewName === 'home') {
|
||||
homeView.classList.remove('hidden');
|
||||
diagramViewer.classList.add('hidden');
|
||||
documentViewer.classList.add('hidden');
|
||||
} else if (viewName === 'diagram') {
|
||||
homeView.classList.add('hidden');
|
||||
diagramViewer.classList.remove('hidden');
|
||||
documentViewer.classList.add('hidden');
|
||||
diagramTitle.textContent = title;
|
||||
diagramContainer.innerHTML = `
|
||||
<div class="flex flex-col items-center space-y-3">
|
||||
@@ -608,6 +919,41 @@
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
} else if (viewName === 'document') {
|
||||
homeView.classList.add('hidden');
|
||||
diagramViewer.classList.add('hidden');
|
||||
documentViewer.classList.remove('hidden');
|
||||
documentTitle.textContent = title;
|
||||
documentContainer.innerHTML = `
|
||||
<div class="flex flex-col items-center justify-center space-y-3 py-12">
|
||||
<div class="w-12 h-12 border-4 border-primary-200 border-t-primary-600 rounded-full animate-spin"></div>
|
||||
<p class="text-gray-600 font-medium">Loading document...</p>
|
||||
</div>
|
||||
`;
|
||||
|
||||
try {
|
||||
const response = await fetch(filePath);
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to load document: ${response.status}`);
|
||||
}
|
||||
|
||||
const markdownText = await response.text();
|
||||
const htmlContent = marked.parse(markdownText);
|
||||
documentContainer.innerHTML = htmlContent;
|
||||
} catch (error) {
|
||||
console.error('Error loading document:', error);
|
||||
documentContainer.innerHTML = `
|
||||
<div class="bg-red-50 border border-red-200 rounded-xl p-6">
|
||||
<div class="flex items-center space-x-3 mb-2">
|
||||
<svg class="w-6 h-6 text-red-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
||||
</svg>
|
||||
<h4 class="font-bold text-red-900">Failed to load document</h4>
|
||||
</div>
|
||||
<p class="text-red-700 text-sm">${error.message}</p>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -634,6 +980,7 @@
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
loadAllDiagrams();
|
||||
loadAllDocuments();
|
||||
showView('home', document.getElementById('nav-home'));
|
||||
});
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user