From 612f15f7b4f567b065e095e93f2522adf09700ce Mon Sep 17 00:00:00 2001 From: Max Litruv Boonzaayer Date: Thu, 30 Jan 2025 05:27:16 +1100 Subject: [PATCH] Add initial configuration files and update documentation links --- docs/.obsidian/app.json | 1 + docs/.obsidian/appearance.json | 1 + docs/.obsidian/core-plugins.json | 30 +++ docs/.obsidian/workspace.json | 171 ++++++++++++++++++ ...tion Trace.md => Blueprint Depth Trace.md} | 0 docs/index.md | 5 +- index.js | 131 ++++++++------ index.json | 4 +- styles.css | 23 ++- 9 files changed, 301 insertions(+), 65 deletions(-) create mode 100644 docs/.obsidian/app.json create mode 100644 docs/.obsidian/appearance.json create mode 100644 docs/.obsidian/core-plugins.json create mode 100644 docs/.obsidian/workspace.json rename docs/{Blueprint Penetration Trace.md => Blueprint Depth Trace.md} (100%) diff --git a/docs/.obsidian/app.json b/docs/.obsidian/app.json new file mode 100644 index 0000000..9e26dfe --- /dev/null +++ b/docs/.obsidian/app.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/docs/.obsidian/appearance.json b/docs/.obsidian/appearance.json new file mode 100644 index 0000000..9e26dfe --- /dev/null +++ b/docs/.obsidian/appearance.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/docs/.obsidian/core-plugins.json b/docs/.obsidian/core-plugins.json new file mode 100644 index 0000000..436f43c --- /dev/null +++ b/docs/.obsidian/core-plugins.json @@ -0,0 +1,30 @@ +{ + "file-explorer": true, + "global-search": true, + "switcher": true, + "graph": true, + "backlink": true, + "canvas": true, + "outgoing-link": true, + "tag-pane": true, + "properties": false, + "page-preview": true, + "daily-notes": true, + "templates": true, + "note-composer": true, + "command-palette": true, + "slash-command": false, + "editor-status": true, + "bookmarks": true, + "markdown-importer": false, + "zk-prefixer": false, + "random-note": false, + "outline": true, + "word-count": true, + "slides": false, + "audio-recorder": false, + "workspaces": false, + "file-recovery": true, + "publish": false, + "sync": false +} \ No newline at end of file diff --git a/docs/.obsidian/workspace.json b/docs/.obsidian/workspace.json new file mode 100644 index 0000000..7817f7a --- /dev/null +++ b/docs/.obsidian/workspace.json @@ -0,0 +1,171 @@ +{ + "main": { + "id": "cd6401f1d421f524", + "type": "split", + "children": [ + { + "id": "380ab91822574142", + "type": "tabs", + "children": [ + { + "id": "1eda716d35acafe8", + "type": "leaf", + "state": { + "type": "markdown", + "state": { + "file": "index.md", + "mode": "source", + "source": false + }, + "icon": "lucide-file", + "title": "index" + } + } + ] + } + ], + "direction": "vertical" + }, + "left": { + "id": "66837e31272b0a87", + "type": "split", + "children": [ + { + "id": "2c0fd441985c6245", + "type": "tabs", + "children": [ + { + "id": "ba3b4ab7076efb70", + "type": "leaf", + "state": { + "type": "file-explorer", + "state": { + "sortOrder": "alphabetical" + }, + "icon": "lucide-folder-closed", + "title": "Files" + } + }, + { + "id": "a6d0910ec51944ee", + "type": "leaf", + "state": { + "type": "search", + "state": { + "query": "", + "matchingCase": false, + "explainSearch": false, + "collapseAll": false, + "extraContext": false, + "sortOrder": "alphabetical" + }, + "icon": "lucide-search", + "title": "Search" + } + }, + { + "id": "f8024f6e5f6c45ac", + "type": "leaf", + "state": { + "type": "bookmarks", + "state": {}, + "icon": "lucide-bookmark", + "title": "Bookmarks" + } + } + ] + } + ], + "direction": "horizontal", + "width": 300 + }, + "right": { + "id": "642d5319f1f437db", + "type": "split", + "children": [ + { + "id": "6c06163fdc608e38", + "type": "tabs", + "children": [ + { + "id": "b06989fdae9db98b", + "type": "leaf", + "state": { + "type": "backlink", + "state": { + "file": "index.md", + "collapseAll": false, + "extraContext": false, + "sortOrder": "alphabetical", + "showSearch": false, + "searchQuery": "", + "backlinkCollapsed": false, + "unlinkedCollapsed": true + }, + "icon": "links-coming-in", + "title": "Backlinks for index" + } + }, + { + "id": "bf67716e89e5fce6", + "type": "leaf", + "state": { + "type": "outgoing-link", + "state": { + "file": "index.md", + "linksCollapsed": false, + "unlinkedCollapsed": true + }, + "icon": "links-going-out", + "title": "Outgoing links from index" + } + }, + { + "id": "ad8500c55403fa87", + "type": "leaf", + "state": { + "type": "tag", + "state": { + "sortOrder": "frequency", + "useHierarchy": true + }, + "icon": "lucide-tags", + "title": "Tags" + } + }, + { + "id": "b67ceee19888e290", + "type": "leaf", + "state": { + "type": "outline", + "state": { + "file": "index.md" + }, + "icon": "lucide-list", + "title": "Outline of index" + } + } + ] + } + ], + "direction": "horizontal", + "width": 300, + "collapsed": true + }, + "left-ribbon": { + "hiddenItems": { + "switcher:Open quick switcher": false, + "graph:Open graph view": false, + "canvas:Create new canvas": false, + "daily-notes:Open today's daily note": false, + "templates:Insert template": false, + "command-palette:Open command palette": false + } + }, + "active": "1eda716d35acafe8", + "lastOpenFiles": [ + "Blueprint Penetration Trace.md", + "Blueprint Depth Trace.md", + "index.md" + ] +} \ No newline at end of file diff --git a/docs/Blueprint Penetration Trace.md b/docs/Blueprint Depth Trace.md similarity index 100% rename from docs/Blueprint Penetration Trace.md rename to docs/Blueprint Depth Trace.md diff --git a/docs/index.md b/docs/index.md index 4cdfb87..c80b85a 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,13 +1,10 @@ ---- -title: Welcome ---- Welcome to the Documentation Here you'll find documentation for various tools and projects I've created. Select a document from the sidebar to get started. ## Available Documentation -- **Blueprint Depth Trace** - A Blueprint Function Library for depth-based line traces in Unreal Engine +- **[[Blueprint Depth Trace]]** - A Blueprint Function Library for depth-based line traces in Unreal Engine - More documentation coming soon... ## Getting Help diff --git a/index.js b/index.js index 6ad2f4b..1faa778 100644 --- a/index.js +++ b/index.js @@ -1,4 +1,3 @@ -// Initialize marked when it's available let markedPromise = new Promise((resolve) => { if (typeof marked !== 'undefined') { resolve(marked); @@ -7,9 +6,24 @@ let markedPromise = new Promise((resolve) => { } }); -// Configure marked options async function initializeMarked() { const marked = await markedPromise; + + // Create a new renderer + const renderer = new marked.Renderer(); + + // Store the original link renderer + const originalLink = renderer.link.bind(renderer); + + // Override the link renderer + renderer.link = (href, title, text) => { + const isExternal = href.startsWith('http') || href.startsWith('https'); + const attrs = isExternal ? ' target="_blank" rel="noopener noreferrer"' : ''; + const link = originalLink(href, title, text); + return link.replace(/^ { if (item.path === path) return true; if (item.type === 'folder') { @@ -81,7 +97,6 @@ function findParentFolders(documents, path, parentFolders = []) { if (found) { parentFolders.push(doc); - // Continue searching in case of nested folders doc.items.forEach(item => { if (item.type === 'folder') { findParentFolders([item], path, parentFolders); @@ -93,7 +108,6 @@ function findParentFolders(documents, path, parentFolders = []) { return parentFolders; } -// Helper function to find document by slug in nested structure function findDocumentBySlug(documents, slug) { for (const doc of documents) { if (doc.type === 'folder') { @@ -112,28 +126,24 @@ async function loadIndex() { const data = await response.json(); const fileIndex = document.getElementById('file-index'); - // Clear existing content fileIndex.innerHTML = ''; - // Build file index with folder support data.documents.forEach(doc => createFileIndexItem(doc, fileIndex)); - // Handle initial URL load or load index.md by default const queryString = window.location.search; if (queryString) { - const slug = queryString.substring(1); // Remove the leading '?' - console.log('Loading document for slug:', slug); // Debug log + const slug = queryString.substring(1); + console.log('Loading document for slug:', slug); const matchingDoc = findDocumentBySlug(data.documents, slug); if (matchingDoc) { - console.log('Found matching document:', matchingDoc); // Debug log + console.log('Found matching document:', matchingDoc); await loadDocument(matchingDoc.path); } else { - console.warn('No matching document found for slug:', slug); // Debug log + console.warn('No matching document found for slug:', slug); } } else { - // Load index.md by default when no document is specified const defaultDoc = findDocumentBySlug(data.documents, 'welcome'); if (defaultDoc) { await loadDocument(defaultDoc.path); @@ -146,7 +156,6 @@ async function loadIndex() { } } -// Add popstate handler for browser back/forward buttons window.addEventListener('popstate', async () => { const urlParams = new URLSearchParams(window.location.search); const slug = urlParams.toString().replace(/^=/, '').replace(/^\?/, ''); @@ -165,17 +174,14 @@ function extractMetadata(content) { let metadata = {}; let contentStart = 0; - // Check if the file starts with a metadata section (accounting for whitespace) if (lines[0].trim() === '---') { let endMetadata = -1; - // Find the closing '---' for (let i = 1; i < lines.length; i++) { if (lines[i].trim() === '---') { endMetadata = i; break; } - // Parse key-value pairs (ignore empty lines) const line = lines[i].trim(); if (line) { const match = line.match(/^([\w-]+):\s*(.*)$/); @@ -190,27 +196,39 @@ function extractMetadata(content) { } } - // Trim any leading/trailing whitespace from the content return { metadata, content: lines.slice(contentStart).join('\n').trim() }; } +function findDocumentByTitle(documents, title) { + for (const doc of documents) { + if (doc.type === 'folder') { + const found = findDocumentByTitle(doc.items, title); + if (found) return found; + } else if (doc.title === title || doc.path.endsWith(title + '.md')) { + return doc; + } + } + return null; +} + async function loadDocument(path) { try { - // Update active state in sidebar and expand folders + const documentContent = document.getElementById('document-content'); + const documentOutline = document.getElementById('document-outline'); + const basePath = path.substring(0, path.lastIndexOf('/')); + document.querySelectorAll('#file-index a').forEach(link => { link.classList.toggle('active', link.dataset.path === path); - // If this is the active link, expand its parent folders if (link.dataset.path === path) { const response = fetch('index.json') .then(res => res.json()) .then(data => { const parentFolders = findParentFolders(data.documents, path); parentFolders.forEach(folder => { - // Find and expand the corresponding folder div const folderDiv = document.querySelector(`.folder[data-path="${folder.title}"]`); if (folderDiv) { folderDiv.classList.add('open'); @@ -231,19 +249,34 @@ async function loadDocument(path) { let rawContent = await response.text(); const { metadata, content } = extractMetadata(rawContent); - const basePath = path.substring(0, path.lastIndexOf('/')); - const documentContent = document.getElementById('document-content'); - const documentOutline = document.getElementById('document-outline'); + // Get the title from index.json + const indexResponse = await fetch('index.json'); + const indexData = await indexResponse.json(); + const docEntry = findDocumentByPath(indexData.documents, path); - // Create title element - const titleContent = metadata.title || path.split('/').pop().replace('.md', ''); - const processedContent = `# ${titleContent}\n\n${content}`; + // Use title priority: frontmatter > json > filename + const titleContent = metadata.title || (docEntry && docEntry.title) || path.split('/').pop().replace('.md', ''); - // Convert Obsidian media links to HTML with proper spacing + // Process Obsidian internal links before adding the title + let processedContent = content.replace(/\[\[(.*?)\]\]/g, (match, linkText) => { + // Skip image/media processing + if (linkText.match(/\.(png|jpg|jpeg|gif|mp4|webm)$/i)) { + return match; + } + + const doc = findDocumentByTitle(indexData.documents, linkText); + if (doc) { + return `[${doc.title}](?${doc.slug})`; + } + return match; + }); + + processedContent = `# ${titleContent}\n\n${processedContent}`; + + // Process images/media after internal links const finalContent = processedContent.replace(/!\[\[(.*?)\]\]/g, (match, filename) => { const mediaPath = `${basePath}/images/${filename}`; - // Check if it's a video file if (filename.toLowerCase().endsWith('.mp4')) { return `\n\n\n`; } - // Default to image handling with added newlines return `\n![${filename}](${mediaPath})\n\n`; }); - // Update meta tags const description = metadata.description || `Documentation for ${titleContent}`; const url = `${window.location.origin}${window.location.pathname}${window.location.search}`; @@ -267,7 +298,6 @@ async function loadDocument(path) { document.querySelector('meta[name="twitter:title"]').setAttribute('content', titleContent); document.querySelector('meta[name="twitter:description"]').setAttribute('content', description); - // If there's a cover image in metadata if (metadata.image) { const imageUrl = `${window.location.origin}${window.location.pathname}${basePath}/images/${metadata.image}`; document.querySelector('meta[property="og:image"]')?.remove(); @@ -284,15 +314,12 @@ async function loadDocument(path) { document.head.appendChild(twitterImage); } - // Update page and title bar while preserving menu button and brand document.title = `Litruv / ${titleContent}`; document.querySelector('.title-text .page-title').textContent = titleContent; - // Parse markdown content documentContent.className = 'markdown-content'; documentContent.innerHTML = marked.parse(finalContent); - // Generate outline documentOutline.innerHTML = ''; const headings = documentContent.querySelectorAll('h1, h2, h3, h4, h5, h6'); @@ -310,25 +337,20 @@ async function loadDocument(path) { link.onclick = (e) => { e.preventDefault(); - // Update URL without triggering navigation history.pushState(null, '', link.href); - // Remove existing highlights document.querySelectorAll('.highlight').forEach(el => { el.classList.remove('highlight'); }); - // Add highlight class to trigger animation heading.classList.add('highlight'); - // Smooth scroll heading.scrollIntoView({ behavior: 'smooth' }); }; documentOutline.appendChild(link); }); - // Check for hash in URL and highlight on load if (window.location.hash) { const id = window.location.hash.substring(1); const heading = document.getElementById(id); @@ -338,7 +360,6 @@ async function loadDocument(path) { } } - // Highlight code blocks Prism.highlightAll(); } catch (error) { console.error('Error loading document:', error); @@ -347,7 +368,18 @@ async function loadDocument(path) { } } -// Handle image paths +function findDocumentByPath(documents, path) { + for (const doc of documents) { + if (doc.type === 'folder') { + const found = findDocumentByPath(doc.items, path); + if (found) return found; + } else if (doc.path === path) { + return doc; + } + } + return null; +} + function fixImagePaths(basePath, content) { return content.replace(/!\[\[(.*?)\]\]/g, (match, filename) => { const imagePath = basePath + '/images/' + filename; @@ -355,7 +387,6 @@ function fixImagePaths(basePath, content) { }); } -// Add menu toggle functionality function setupMobileMenu() { const menuButton = document.querySelector('.menu-button'); const leftSidebar = document.querySelector('.left-sidebar'); @@ -365,7 +396,6 @@ function setupMobileMenu() { leftSidebar.classList.toggle('show'); }); - // Close menu when clicking outside content.addEventListener('click', () => { if (leftSidebar.classList.contains('show')) { leftSidebar.classList.remove('show'); @@ -373,9 +403,8 @@ function setupMobileMenu() { }); } -// Add new initialization with proper order window.addEventListener('load', () => { - console.log('Window loaded, initializing...'); // Debug log + console.log('Window loaded, initializing...'); setupMobileMenu(); loadIndex().catch(error => { console.error('Failed to initialize:', error); diff --git a/index.json b/index.json index 7f0813a..1114a6b 100644 --- a/index.json +++ b/index.json @@ -11,8 +11,8 @@ "items": [ { "title": "Blueprint Depth Trace", - "path": "docs/Blueprint Penetration Trace.md", - "slug": "blueprint-penetration-trace" + "path": "docs/Blueprint Depth Trace.md", + "slug": "blueprint-depth-trace" } ] } diff --git a/styles.css b/styles.css index c7beae7..4c8fb31 100644 --- a/styles.css +++ b/styles.css @@ -74,11 +74,15 @@ body { border-right: 1px solid var(--ifm-border-color); display: flex; flex-direction: column; + min-height: calc(100vh - var(--title-bar-height)); + position: sticky; + top: var(--title-bar-height); } #file-index { flex: 1; overflow-y: auto; + min-height: 0; /* This ensures the flex item can shrink */ } #file-index a { @@ -139,11 +143,11 @@ body { .social-links { padding: 1rem 1rem 0.5rem; - border-top: 1px solid var(--ifm-border-color); display: flex; justify-content: center; gap: 1rem; flex-wrap: wrap; + background: var(--ifm-background-surface-color); } .social-links a { @@ -157,11 +161,13 @@ body { } .subtitle { + margin-top: auto; /* Push to bottom of flex container */ text-align: center; padding: 0 1rem 1rem; font-size: 0.8rem; color: var(--ifm-color-content-secondary); - border-top: none; + border-top: 1px solid var(--ifm-border-color); + background: var(--ifm-background-surface-color); } .subtitle .name { @@ -203,16 +209,15 @@ body { color: var(--ifm-color-primary); } +#document-outline { + padding: 0 1rem; /* Add horizontal padding */ +} + #document-outline a { color: var(--ifm-color-content-secondary); border-left: 2px solid transparent; margin: 2px 0; -} - -#document-outline a:hover { - background-color: transparent; - color: var(--ifm-color-primary); - border-left: 2px solid var(--ifm-color-primary); + padding: 0.4rem 1rem; /* Adjust padding to match */ } /* Markdown content styling */ @@ -413,6 +418,8 @@ body { bottom: 0; z-index: 100; transition: var(--sidebar-transition); + min-height: unset; /* Remove min-height for mobile */ + height: calc(100% - var(--title-bar-height)); } .right-sidebar {