feat: Goa GEL Blockchain e-Licensing Platform - Full Stack Implementation
Complete implementation of the Goa Government e-Licensing platform with: Backend: - NestJS API with JWT authentication - PostgreSQL database with Knex ORM - Redis caching and session management - MinIO document storage - Hyperledger Besu blockchain integration - Multi-department workflow system - Comprehensive API tests (266/282 passing) Frontend: - Angular 21 with standalone components - Angular Material + TailwindCSS UI - Visual workflow builder - Document upload with progress tracking - Blockchain explorer integration - Role-based dashboards (Admin, Department, Citizen) - E2E tests with Playwright (37 tests) Infrastructure: - Docker Compose orchestration - Blockscout blockchain explorer - Development and production configurations
This commit is contained in:
27
Documentation/public/js/main.js
Normal file
27
Documentation/public/js/main.js
Normal file
@@ -0,0 +1,27 @@
|
||||
// Main.js - Homepage functionality
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
console.log('Goa-GEL Documentation loaded');
|
||||
|
||||
// Smooth scrolling for anchor links
|
||||
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
|
||||
anchor.addEventListener('click', function (e) {
|
||||
e.preventDefault();
|
||||
const target = document.querySelector(this.getAttribute('href'));
|
||||
if (target) {
|
||||
target.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
block: 'start'
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Highlight active navigation link
|
||||
const currentPath = window.location.pathname;
|
||||
document.querySelectorAll('.main-nav a').forEach(link => {
|
||||
if (link.getAttribute('href') === currentPath) {
|
||||
link.classList.add('active');
|
||||
}
|
||||
});
|
||||
});
|
||||
322
Documentation/public/js/viewer.js
Normal file
322
Documentation/public/js/viewer.js
Normal file
@@ -0,0 +1,322 @@
|
||||
// Viewer.js - Document viewer functionality
|
||||
|
||||
// Configuration
|
||||
const DOC_MAP = {
|
||||
'USER_GUIDE': '/docs/USER_GUIDE.md',
|
||||
'E2E_TESTING_GUIDE': '/docs/E2E_TESTING_GUIDE.md',
|
||||
'IMPLEMENTATION_COMPLETE': '/docs/IMPLEMENTATION_COMPLETE.md',
|
||||
'ARCHITECTURE_GUIDE': '/docs/ARCHITECTURE_GUIDE.md',
|
||||
'QUICK_START': '/docs/QUICK_START.md',
|
||||
'DOCUMENTATION_INDEX': '/docs/DOCUMENTATION_INDEX.md',
|
||||
'IMPLEMENTATION_SUMMARY': '/docs/IMPLEMENTATION_SUMMARY.md'
|
||||
};
|
||||
|
||||
let currentDoc = null;
|
||||
let sidebarOpen = true;
|
||||
|
||||
// Initialize on page load
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
initViewer();
|
||||
setupEventListeners();
|
||||
loadDocumentFromURL();
|
||||
});
|
||||
|
||||
function initViewer() {
|
||||
// Configure marked.js
|
||||
if (typeof marked !== 'undefined') {
|
||||
marked.setOptions({
|
||||
breaks: true,
|
||||
gfm: true,
|
||||
highlight: function(code, lang) {
|
||||
if (typeof hljs !== 'undefined' && lang && hljs.getLanguage(lang)) {
|
||||
try {
|
||||
return hljs.highlight(code, { language: lang }).value;
|
||||
} catch (err) {
|
||||
console.error('Highlight error:', err);
|
||||
}
|
||||
}
|
||||
return code;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function setupEventListeners() {
|
||||
// Sidebar toggle
|
||||
const toggleBtn = document.getElementById('toggle-sidebar');
|
||||
if (toggleBtn) {
|
||||
toggleBtn.addEventListener('click', toggleSidebar);
|
||||
}
|
||||
|
||||
// Document selector
|
||||
const docSelector = document.getElementById('doc-selector');
|
||||
if (docSelector) {
|
||||
docSelector.addEventListener('change', function() {
|
||||
if (this.value) {
|
||||
loadDocument(this.value);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Print button
|
||||
const printBtn = document.getElementById('print-doc');
|
||||
if (printBtn) {
|
||||
printBtn.addEventListener('click', function() {
|
||||
window.print();
|
||||
});
|
||||
}
|
||||
|
||||
// Download button
|
||||
const downloadBtn = document.getElementById('download-doc');
|
||||
if (downloadBtn) {
|
||||
downloadBtn.addEventListener('click', downloadCurrentDoc);
|
||||
}
|
||||
|
||||
// Sidebar navigation links
|
||||
document.querySelectorAll('.sidebar-nav .nav-link').forEach(link => {
|
||||
link.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
const url = this.getAttribute('href');
|
||||
const params = new URLSearchParams(url.split('?')[1]);
|
||||
const doc = params.get('doc');
|
||||
if (doc) {
|
||||
loadDocument(doc);
|
||||
updateURL(doc);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function loadDocumentFromURL() {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const doc = params.get('doc');
|
||||
if (doc && DOC_MAP[doc]) {
|
||||
loadDocument(doc);
|
||||
} else {
|
||||
showError('No document specified. Please select a document from the navigation.');
|
||||
}
|
||||
}
|
||||
|
||||
async function loadDocument(docKey) {
|
||||
const docPath = DOC_MAP[docKey];
|
||||
if (!docPath) {
|
||||
showError(`Document "${docKey}" not found`);
|
||||
return;
|
||||
}
|
||||
|
||||
currentDoc = docKey;
|
||||
showLoading();
|
||||
hideError();
|
||||
|
||||
try {
|
||||
const response = await fetch(docPath);
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const markdown = await response.text();
|
||||
renderMarkdown(markdown);
|
||||
generateTOC();
|
||||
updateActiveNav(docKey);
|
||||
updateDocSelector(docKey);
|
||||
updateURL(docKey);
|
||||
|
||||
// Scroll to top
|
||||
window.scrollTo(0, 0);
|
||||
} catch (error) {
|
||||
console.error('Error loading document:', error);
|
||||
showError(`Failed to load document: ${error.message}`);
|
||||
} finally {
|
||||
hideLoading();
|
||||
}
|
||||
}
|
||||
|
||||
function renderMarkdown(markdown) {
|
||||
const contentDiv = document.getElementById('content');
|
||||
if (!contentDiv) return;
|
||||
|
||||
try {
|
||||
// Parse markdown to HTML
|
||||
const html = marked.parse(markdown);
|
||||
|
||||
// Sanitize HTML using DOMPurify if available, otherwise use trusted content
|
||||
const safeHTML = (typeof DOMPurify !== 'undefined')
|
||||
? DOMPurify.sanitize(html, { ADD_ATTR: ['target'] })
|
||||
: html;
|
||||
|
||||
contentDiv.innerHTML = safeHTML;
|
||||
|
||||
// Syntax highlighting for code blocks
|
||||
if (typeof hljs !== 'undefined') {
|
||||
contentDiv.querySelectorAll('pre code').forEach((block) => {
|
||||
hljs.highlightElement(block);
|
||||
});
|
||||
}
|
||||
|
||||
// Make external links open in new tab
|
||||
contentDiv.querySelectorAll('a[href^="http"]').forEach(link => {
|
||||
link.setAttribute('target', '_blank');
|
||||
link.setAttribute('rel', 'noopener noreferrer');
|
||||
});
|
||||
|
||||
// Add IDs to headings for anchor links
|
||||
contentDiv.querySelectorAll('h1, h2, h3, h4, h5, h6').forEach((heading, index) => {
|
||||
if (!heading.id) {
|
||||
heading.id = `heading-${index}`;
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error rendering markdown:', error);
|
||||
showError(`Failed to render document: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
function generateTOC() {
|
||||
const tocContent = document.getElementById('toc-content');
|
||||
const content = document.getElementById('content');
|
||||
|
||||
if (!tocContent || !content) return;
|
||||
|
||||
const headings = content.querySelectorAll('h2, h3');
|
||||
if (headings.length === 0) {
|
||||
tocContent.textContent = 'No headings found';
|
||||
return;
|
||||
}
|
||||
|
||||
// Clear existing content
|
||||
tocContent.innerHTML = '';
|
||||
|
||||
// Create TOC links using DOM methods
|
||||
headings.forEach(heading => {
|
||||
const level = heading.tagName.toLowerCase();
|
||||
const text = heading.textContent;
|
||||
const id = heading.id || `heading-${text.replace(/\s+/g, '-').toLowerCase()}`;
|
||||
heading.id = id;
|
||||
|
||||
const link = document.createElement('a');
|
||||
link.href = `#${id}`;
|
||||
link.textContent = text;
|
||||
|
||||
if (level === 'h3') {
|
||||
link.style.marginLeft = '1rem';
|
||||
}
|
||||
|
||||
link.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
const target = document.getElementById(id);
|
||||
if (target) {
|
||||
target.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||
}
|
||||
});
|
||||
|
||||
tocContent.appendChild(link);
|
||||
});
|
||||
}
|
||||
|
||||
function toggleSidebar() {
|
||||
const sidebar = document.querySelector('.sidebar');
|
||||
const icon = document.getElementById('sidebar-icon');
|
||||
|
||||
if (sidebar) {
|
||||
sidebarOpen = !sidebarOpen;
|
||||
sidebar.classList.toggle('active');
|
||||
if (icon) {
|
||||
icon.textContent = sidebarOpen ? '☰' : '✕';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function updateActiveNav(docKey) {
|
||||
document.querySelectorAll('.sidebar-nav .nav-link').forEach(link => {
|
||||
link.classList.remove('active');
|
||||
const url = link.getAttribute('href');
|
||||
if (url && url.includes(`doc=${docKey}`)) {
|
||||
link.classList.add('active');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function updateDocSelector(docKey) {
|
||||
const selector = document.getElementById('doc-selector');
|
||||
if (selector) {
|
||||
selector.value = docKey;
|
||||
}
|
||||
}
|
||||
|
||||
function updateURL(docKey) {
|
||||
const newURL = `${window.location.pathname}?doc=${docKey}`;
|
||||
window.history.pushState({ doc: docKey }, '', newURL);
|
||||
}
|
||||
|
||||
function downloadCurrentDoc() {
|
||||
if (!currentDoc) {
|
||||
alert('No document loaded');
|
||||
return;
|
||||
}
|
||||
|
||||
const docPath = DOC_MAP[currentDoc];
|
||||
const filename = docPath.split('/').pop();
|
||||
|
||||
fetch(docPath)
|
||||
.then(response => response.text())
|
||||
.then(text => {
|
||||
const blob = new Blob([text], { type: 'text/markdown' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = filename;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(url);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Download error:', error);
|
||||
alert('Failed to download document');
|
||||
});
|
||||
}
|
||||
|
||||
function showLoading() {
|
||||
const loading = document.getElementById('loading');
|
||||
const content = document.getElementById('content');
|
||||
|
||||
if (loading) loading.style.display = 'block';
|
||||
if (content) content.style.display = 'none';
|
||||
}
|
||||
|
||||
function hideLoading() {
|
||||
const loading = document.getElementById('loading');
|
||||
const content = document.getElementById('content');
|
||||
|
||||
if (loading) loading.style.display = 'none';
|
||||
if (content) content.style.display = 'block';
|
||||
}
|
||||
|
||||
function showError(message) {
|
||||
const error = document.getElementById('error');
|
||||
const errorMessage = document.getElementById('error-message');
|
||||
const content = document.getElementById('content');
|
||||
|
||||
if (error) error.style.display = 'block';
|
||||
if (errorMessage) errorMessage.textContent = message;
|
||||
if (content) content.style.display = 'none';
|
||||
}
|
||||
|
||||
function hideError() {
|
||||
const error = document.getElementById('error');
|
||||
if (error) error.style.display = 'none';
|
||||
}
|
||||
|
||||
// Handle browser back/forward buttons
|
||||
window.addEventListener('popstate', function(event) {
|
||||
if (event.state && event.state.doc) {
|
||||
loadDocument(event.state.doc);
|
||||
}
|
||||
});
|
||||
|
||||
// Search functionality (future enhancement)
|
||||
function searchDocumentation(query) {
|
||||
// TODO: Implement search across all documentation
|
||||
console.log('Search query:', query);
|
||||
}
|
||||
Reference in New Issue
Block a user