updated plugin to show titles + order

This commit is contained in:
2025-03-25 13:01:34 +11:00
parent e64738d751
commit e8e80eede9
3 changed files with 199 additions and 145 deletions

View File

@@ -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<void>}
*/
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;

View File

@@ -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
}

View File

@@ -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;