From e8e80eede92c687b6d8c07e5e3582003301edf0d Mon Sep 17 00:00:00 2001 From: Max Litruv Boonzaayer Date: Tue, 25 Mar 2025 13:01:34 +1100 Subject: [PATCH] updated plugin to show titles + order --- docs/.obsidian/plugins/docs-viewer/main.js | 223 +++++++++++++----- .../plugins/docs-viewer/manifest.json | 2 - docs/.obsidian/plugins/docs-viewer/styles.css | 119 ++++------ 3 files changed, 199 insertions(+), 145 deletions(-) diff --git a/docs/.obsidian/plugins/docs-viewer/main.js b/docs/.obsidian/plugins/docs-viewer/main.js index 561f01f..766e080 100644 --- a/docs/.obsidian/plugins/docs-viewer/main.js +++ b/docs/.obsidian/plugins/docs-viewer/main.js @@ -1,44 +1,50 @@ const obsidian = require('obsidian'); -class TitleAppenderPlugin extends obsidian.Plugin { +/** + * @class DocsViewerPlugin + * @description An Obsidian plugin that enhances document viewing by adding titles from frontmatter to file and folder elements + * @extends {obsidian.Plugin} + */ +class DocsViewerPlugin extends obsidian.Plugin { + /** + * @description Plugin initialization method called when the plugin is loaded + * @returns {Promise} + */ async onload() { - console.log('Loading TitleAppender plugin'); + console.log('Loading DocsViewer plugin'); - // Initial update attempt this.updateAllTitles(); - // Wait for layout to be ready before registering events this.app.workspace.onLayoutReady(() => { this.registerEvents(); - // Force another update after layout is ready setTimeout(() => { this.updateAllTitles(); }, 500); }); - // Schedule periodic updates to catch any missed files this.registerInterval( window.setInterval(() => this.updateAllTitles(), 5000) ); } + /** + * @description Registers event listeners for file and layout changes + * @returns {void} + */ registerEvents() { - // Update styles when files change this.registerEvent( this.app.metadataCache.on('changed', () => { this.updateAllTitles(); }) ); - // Update on file explorer changes this.registerEvent( this.app.workspace.on('file-menu', () => { this.updateAllTitles(); }) ); - // Update when layout changes this.registerEvent( this.app.workspace.on('layout-change', () => { this.updateAllTitles(); @@ -46,79 +52,168 @@ class TitleAppenderPlugin extends obsidian.Plugin { ); } + /** + * @description Updates display titles for files and folders based on frontmatter data + * @details Processes all markdown files to extract frontmatter and update the UI with proper titles and sorting + * @returns {void} + */ updateAllTitles() { try { - // Get all markdown files const files = this.app.vault.getMarkdownFiles(); - - // First, clean up any existing elements this.cleanupExistingElements(); - - // Apply classes and attributes for each file with frontmatter - files.forEach(file => { - try { - const metadata = this.app.metadataCache.getFileCache(file); - const frontmatter = metadata?.frontmatter; - const title = frontmatter?.title; - const sortValue = frontmatter?.sort; - - if (title || sortValue) { - const escapedPath = CSS.escape(file.path); - const fileElements = document.querySelectorAll(`.nav-file-title[data-path="${escapedPath}"] .nav-file-title-content`); - - fileElements.forEach(fileElement => { - if (fileElement) { - // Add title if available - if (title) { - fileElement.classList.add('has-title'); - fileElement.setAttribute('data-title', ` (${title})`); - } - - // Add sort value if available - if (sortValue !== undefined) { - fileElement.classList.add('has-sort-value'); - - // If both title and sort exist, create a span for the sort value - if (title) { - // Create a span for the sort value to apply different color - const sortSpan = document.createElement('span'); - sortSpan.className = 'sort-suffix'; - sortSpan.textContent = `[${sortValue}]`; - fileElement.appendChild(sortSpan); - } else { - // If only sort exists, use the attribute - fileElement.setAttribute('data-sort-value', `[${sortValue}]`); - } - } - } - }); + + this.ensureCustomStyles(); + + document.querySelectorAll('.tree-item.nav-file, .nav-file, .tree-item.nav-folder, .nav-folder').forEach(item => { + let path, isFolder; + + const folderTitleEl = item.querySelector('.tree-item-self[data-path]'); + if (folderTitleEl && item.classList.contains('nav-folder')) { + path = folderTitleEl.getAttribute('data-path'); + isFolder = true; + } else { + const fileEl = item.querySelector('.tree-item-self, .nav-file-title'); + if (fileEl) { + path = fileEl.getAttribute('data-path'); + isFolder = false; + } else { + return; + } + } + + if (!path) return; + + let file; + if (isFolder) { + const folderName = path.split('/').pop(); + file = files.find(f => f.path === `${path}/${folderName}.md`); + + if (!file) return; + } else { + file = files.find(f => f.path === path); + } + + if (!file) return; + + const frontmatter = this.app.metadataCache.getFileCache(file)?.frontmatter; + + const parentContainer = item.parentElement; + if (parentContainer) { + parentContainer.classList.add('docs-viewer-flex-container'); + + let sortValue; + + if (!isFolder) { + const filePath = file.path; + const folderName = filePath.substring(0, filePath.lastIndexOf('/')).split('/').pop(); + if (folderName && file.basename === folderName) { + sortValue = -9999999; + } else { + sortValue = frontmatter?.sort !== undefined ? parseInt(frontmatter.sort) : 9999; + } + } else { + sortValue = frontmatter?.sort !== undefined ? parseInt(frontmatter.sort) : 9999; + } + + item.style.order = sortValue; + } + + if (!isFolder) { + const titleEl = item.querySelector('.tree-item-inner, .nav-file-title-content'); + if (titleEl) { + const frontTitle = frontmatter?.title; + if (frontTitle && frontTitle !== file.basename) { + const frontSort = frontmatter?.sort; + let displayTitle = frontTitle; + if (frontSort !== undefined) { + titleEl.setAttribute('data-sort', frontSort); + } + + titleEl.classList.add('has-title'); + titleEl.setAttribute('data-title', ` (${displayTitle})`); + } + } + } + else { + const folderTitleEl = item.querySelector('.tree-item-inner') || + item.querySelector('.nav-folder-title-content'); + + if (folderTitleEl && frontmatter && frontmatter.title) { + const frontTitle = frontmatter.title; + const frontSort = frontmatter.sort; + + folderTitleEl.classList.add('has-title'); + folderTitleEl.setAttribute('data-title', ` (${frontTitle})`); + + if (frontSort !== undefined) { + folderTitleEl.setAttribute('data-sort', frontSort); + } } - } catch (e) { - console.error('Error processing file:', file?.path, e); } }); + } catch (error) { console.error('Error updating titles:', error); } } - + + /** + * @description Ensures the plugin's custom CSS styles are loaded in the document + * @returns {void} + */ + ensureCustomStyles() { + const styleId = 'docs-viewer-style'; + // Check if our stylesheet is already in the document + if (!document.getElementById(styleId)) { + const link = document.createElement('link'); + link.id = styleId; + link.rel = 'stylesheet'; + link.type = 'text/css'; + link.href = 'obsidian://css-theme-plugins/docs-viewer/styles.css'; + document.head.appendChild(link); + } + } + + /** + * @description Removes all custom classes and attributes added by this plugin + * @details Called during cleanup and before applying new changes to prevent duplication + * @returns {void} + */ cleanupExistingElements() { - // Remove all classes and attributes - document.querySelectorAll('.has-sort-value, .has-title').forEach(el => { - el.classList.remove('has-sort-value', 'has-title'); - el.removeAttribute('data-sort-value'); + // Remove custom classes and attributes from elements + document.querySelectorAll('.has-title, .folder-has-title').forEach(el => { + el.classList.remove('has-title', 'folder-has-title'); el.removeAttribute('data-title'); - - // Remove any added elements - const sortSuffix = el.querySelector('.sort-suffix'); - if (sortSuffix) sortSuffix.remove(); + el.removeAttribute('data-sort'); + el.removeAttribute('data-folder-title'); + }); + + // Reset order styles on tree items and files + document.querySelectorAll('.tree-item, .nav-file').forEach(el => { + el.style.order = ''; }); } + /** + * @description Plugin lifecycle method called when the plugin is disabled or Obsidian is closed + * @details Performs cleanup to remove all plugin-added elements and styles + * @returns {void} + */ onunload() { - console.log('Unloading TitleAppender plugin'); + console.log('Unloading DocsViewer plugin'); + // Remove all custom attributes and classes this.cleanupExistingElements(); + + // Remove flex container class from parent elements + document.querySelectorAll('.docs-viewer-flex-container').forEach(el => { + el.classList.remove('docs-viewer-flex-container'); + }); + + // Ensure order styles are cleared from navigation elements + document.querySelectorAll('.nav-file, .nav-file-title').forEach(el => { + el.style.order = ''; + }); } } -module.exports = TitleAppenderPlugin; +module.exports = DocsViewerPlugin; diff --git a/docs/.obsidian/plugins/docs-viewer/manifest.json b/docs/.obsidian/plugins/docs-viewer/manifest.json index 1095cc7..a9f4a76 100644 --- a/docs/.obsidian/plugins/docs-viewer/manifest.json +++ b/docs/.obsidian/plugins/docs-viewer/manifest.json @@ -6,7 +6,5 @@ "description": "An Obsidian plugin to edit and view Docs-Viewer metadata", "author": "Litruv", "authorUrl": "https://lit.ruv.wtf", - "fundingUrl": "https://ko-fi.com/zsolt", - "helpUrl": "https://github.com/zsviczian/obsidian-excalidraw-plugin#readme", "isDesktopOnly": false } diff --git a/docs/.obsidian/plugins/docs-viewer/styles.css b/docs/.obsidian/plugins/docs-viewer/styles.css index 43da82b..a30487f 100644 --- a/docs/.obsidian/plugins/docs-viewer/styles.css +++ b/docs/.obsidian/plugins/docs-viewer/styles.css @@ -1,105 +1,66 @@ -.title-appended { - color: var(--text-normal); - display: inline; -} - -.title-metadata { - color: var(--color-orange); - font-size: 0.85em; - opacity: 0.8; - margin-left: 0; -} - -/* Sort value styling */ -.nav-file-title-content::before { - margin-right: 4px; -} - +/* Basic styling */ .nav-file-title-content { padding: 0; line-height: 1.1; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; - vertical-align: middle; -} - -/* Reduce indent for child items - improved spacing */ -.nav-folder .nav-folder .nav-file-title, -.nav-folder .nav-folder .nav-folder-title { - padding-left: 15px !important; -} - -/* Ensure proper alignment with sort values and titles */ -.nav-file-title-content.has-sort-value, -.nav-file-title-content.has-title { - padding-right: 5px; - display: inline-block; - max-width: calc(100% - 10px); } /* Title styling */ -.has-title::after { +.has-title::after, +.tree-item-inner.has-title::after, +.nav-folder-title-content.has-title::after, +.nav-file-title-content.has-title::after { content: attr(data-title); color: var(--color-orange); font-size: 0.85em; opacity: 0.8; } -/* Sort value styling - moved to appear last (after title) */ -.has-sort-value::after { - content: attr(data-sort-value); +/* Sort number display */ +.has-title[data-sort]::before, +.tree-item-inner.has-title[data-sort]::before, +.nav-folder-title-content.has-title[data-sort]::before, +.nav-file-title-content.has-title[data-sort]::before { + content: attr(data-sort); color: var(--color-yellow); - margin-left: 4px; -} - -/* Handle case when both title and sort are present - REPLACE THIS */ -.has-title.has-sort-value::after { - content: none; /* Remove the combined content */ -} - -/* Add separate pseudo-elements for title and sort when both exist */ -.has-title.has-sort-value::after { - content: attr(data-title); - color: var(--color-orange); + margin-right: 4px; font-size: 0.85em; - opacity: 0.8; } -.has-title.has-sort-value::before { - content: none; /* Ensure no content shows before */ +/* Container styling for flex ordering */ +.title-appender-flex-container { + display: flex !important; + flex-direction: column !important; +} +.nav-folder-children { + display: flex !important; + flex-direction: column !important; +} +.nav-files-container { + display: flex !important; + flex-direction: column !important; } -/* Add sort value after the title with proper color */ -.has-title.has-sort-value .sort-suffix { - color: var(--color-yellow); - margin-left: 4px; +/* Folder title styling */ +.folder-has-title { + position: relative; +} +.folder-has-title::after { + content: attr(data-folder-title); + position: absolute; + left: 0; + top: 0; + width: 100%; + height: 100%; + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + pointer-events: none; } -/* Fix conflicting rules for title and sort display */ -.has-title::after { - content: attr(data-title); - color: var(--color-orange); - font-size: 0.85em; - opacity: 0.8; -} - -/* Sort value styling when sort value is alone */ -.has-sort-value:not(.has-title)::after { - content: attr(data-sort-value); - color: var(--color-yellow); - margin-left: 4px; -} - -/* Handle case when both title and sort are present */ -.has-title.has-sort-value::after { - content: attr(data-title); - color: var(--color-orange); - font-size: 0.85em; - opacity: 0.8; -} - -/* Style for the sort suffix span */ +/* Sort value styling */ .sort-suffix { color: var(--color-yellow); margin-left: 4px;