Skip to content

Commit e8e80ee

Browse files
committed
updated plugin to show titles + order
1 parent e64738d commit e8e80ee

File tree

3 files changed

+195
-141
lines changed

3 files changed

+195
-141
lines changed
Lines changed: 157 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,124 +1,219 @@
11
const obsidian = require('obsidian');
22

3-
class TitleAppenderPlugin extends obsidian.Plugin {
3+
/**
4+
* @class DocsViewerPlugin
5+
* @description An Obsidian plugin that enhances document viewing by adding titles from frontmatter to file and folder elements
6+
* @extends {obsidian.Plugin}
7+
*/
8+
class DocsViewerPlugin extends obsidian.Plugin {
9+
/**
10+
* @description Plugin initialization method called when the plugin is loaded
11+
* @returns {Promise<void>}
12+
*/
413
async onload() {
5-
console.log('Loading TitleAppender plugin');
14+
console.log('Loading DocsViewer plugin');
615

7-
// Initial update attempt
816
this.updateAllTitles();
917

10-
// Wait for layout to be ready before registering events
1118
this.app.workspace.onLayoutReady(() => {
1219
this.registerEvents();
1320

14-
// Force another update after layout is ready
1521
setTimeout(() => {
1622
this.updateAllTitles();
1723
}, 500);
1824
});
1925

20-
// Schedule periodic updates to catch any missed files
2126
this.registerInterval(
2227
window.setInterval(() => this.updateAllTitles(), 5000)
2328
);
2429
}
2530

31+
/**
32+
* @description Registers event listeners for file and layout changes
33+
* @returns {void}
34+
*/
2635
registerEvents() {
27-
// Update styles when files change
2836
this.registerEvent(
2937
this.app.metadataCache.on('changed', () => {
3038
this.updateAllTitles();
3139
})
3240
);
3341

34-
// Update on file explorer changes
3542
this.registerEvent(
3643
this.app.workspace.on('file-menu', () => {
3744
this.updateAllTitles();
3845
})
3946
);
4047

41-
// Update when layout changes
4248
this.registerEvent(
4349
this.app.workspace.on('layout-change', () => {
4450
this.updateAllTitles();
4551
})
4652
);
4753
}
4854

55+
/**
56+
* @description Updates display titles for files and folders based on frontmatter data
57+
* @details Processes all markdown files to extract frontmatter and update the UI with proper titles and sorting
58+
* @returns {void}
59+
*/
4960
updateAllTitles() {
5061
try {
51-
// Get all markdown files
5262
const files = this.app.vault.getMarkdownFiles();
53-
54-
// First, clean up any existing elements
5563
this.cleanupExistingElements();
56-
57-
// Apply classes and attributes for each file with frontmatter
58-
files.forEach(file => {
59-
try {
60-
const metadata = this.app.metadataCache.getFileCache(file);
61-
const frontmatter = metadata?.frontmatter;
62-
const title = frontmatter?.title;
63-
const sortValue = frontmatter?.sort;
64+
65+
this.ensureCustomStyles();
66+
67+
document.querySelectorAll('.tree-item.nav-file, .nav-file, .tree-item.nav-folder, .nav-folder').forEach(item => {
68+
let path, isFolder;
69+
70+
const folderTitleEl = item.querySelector('.tree-item-self[data-path]');
71+
if (folderTitleEl && item.classList.contains('nav-folder')) {
72+
path = folderTitleEl.getAttribute('data-path');
73+
isFolder = true;
74+
} else {
75+
const fileEl = item.querySelector('.tree-item-self, .nav-file-title');
76+
if (fileEl) {
77+
path = fileEl.getAttribute('data-path');
78+
isFolder = false;
79+
} else {
80+
return;
81+
}
82+
}
83+
84+
if (!path) return;
85+
86+
let file;
87+
if (isFolder) {
88+
const folderName = path.split('/').pop();
89+
file = files.find(f => f.path === `${path}/${folderName}.md`);
6490

65-
if (title || sortValue) {
66-
const escapedPath = CSS.escape(file.path);
67-
const fileElements = document.querySelectorAll(`.nav-file-title[data-path="${escapedPath}"] .nav-file-title-content`);
68-
69-
fileElements.forEach(fileElement => {
70-
if (fileElement) {
71-
// Add title if available
72-
if (title) {
73-
fileElement.classList.add('has-title');
74-
fileElement.setAttribute('data-title', ` (${title})`);
75-
}
76-
77-
// Add sort value if available
78-
if (sortValue !== undefined) {
79-
fileElement.classList.add('has-sort-value');
80-
81-
// If both title and sort exist, create a span for the sort value
82-
if (title) {
83-
// Create a span for the sort value to apply different color
84-
const sortSpan = document.createElement('span');
85-
sortSpan.className = 'sort-suffix';
86-
sortSpan.textContent = `[${sortValue}]`;
87-
fileElement.appendChild(sortSpan);
88-
} else {
89-
// If only sort exists, use the attribute
90-
fileElement.setAttribute('data-sort-value', `[${sortValue}]`);
91-
}
92-
}
91+
if (!file) return;
92+
} else {
93+
file = files.find(f => f.path === path);
94+
}
95+
96+
if (!file) return;
97+
98+
const frontmatter = this.app.metadataCache.getFileCache(file)?.frontmatter;
99+
100+
const parentContainer = item.parentElement;
101+
if (parentContainer) {
102+
parentContainer.classList.add('docs-viewer-flex-container');
103+
104+
let sortValue;
105+
106+
if (!isFolder) {
107+
const filePath = file.path;
108+
const folderName = filePath.substring(0, filePath.lastIndexOf('/')).split('/').pop();
109+
if (folderName && file.basename === folderName) {
110+
sortValue = -9999999;
111+
} else {
112+
sortValue = frontmatter?.sort !== undefined ? parseInt(frontmatter.sort) : 9999;
113+
}
114+
} else {
115+
sortValue = frontmatter?.sort !== undefined ? parseInt(frontmatter.sort) : 9999;
116+
}
117+
118+
item.style.order = sortValue;
119+
}
120+
121+
if (!isFolder) {
122+
const titleEl = item.querySelector('.tree-item-inner, .nav-file-title-content');
123+
if (titleEl) {
124+
const frontTitle = frontmatter?.title;
125+
if (frontTitle && frontTitle !== file.basename) {
126+
const frontSort = frontmatter?.sort;
127+
let displayTitle = frontTitle;
128+
if (frontSort !== undefined) {
129+
titleEl.setAttribute('data-sort', frontSort);
93130
}
94-
});
131+
132+
titleEl.classList.add('has-title');
133+
titleEl.setAttribute('data-title', ` (${displayTitle})`);
134+
}
135+
}
136+
}
137+
else {
138+
const folderTitleEl = item.querySelector('.tree-item-inner') ||
139+
item.querySelector('.nav-folder-title-content');
140+
141+
if (folderTitleEl && frontmatter && frontmatter.title) {
142+
const frontTitle = frontmatter.title;
143+
const frontSort = frontmatter.sort;
144+
145+
folderTitleEl.classList.add('has-title');
146+
folderTitleEl.setAttribute('data-title', ` (${frontTitle})`);
147+
148+
if (frontSort !== undefined) {
149+
folderTitleEl.setAttribute('data-sort', frontSort);
150+
}
95151
}
96-
} catch (e) {
97-
console.error('Error processing file:', file?.path, e);
98152
}
99153
});
154+
100155
} catch (error) {
101156
console.error('Error updating titles:', error);
102157
}
103158
}
104-
159+
160+
/**
161+
* @description Ensures the plugin's custom CSS styles are loaded in the document
162+
* @returns {void}
163+
*/
164+
ensureCustomStyles() {
165+
const styleId = 'docs-viewer-style';
166+
// Check if our stylesheet is already in the document
167+
if (!document.getElementById(styleId)) {
168+
const link = document.createElement('link');
169+
link.id = styleId;
170+
link.rel = 'stylesheet';
171+
link.type = 'text/css';
172+
link.href = 'obsidian://css-theme-plugins/docs-viewer/styles.css';
173+
document.head.appendChild(link);
174+
}
175+
}
176+
177+
/**
178+
* @description Removes all custom classes and attributes added by this plugin
179+
* @details Called during cleanup and before applying new changes to prevent duplication
180+
* @returns {void}
181+
*/
105182
cleanupExistingElements() {
106-
// Remove all classes and attributes
107-
document.querySelectorAll('.has-sort-value, .has-title').forEach(el => {
108-
el.classList.remove('has-sort-value', 'has-title');
109-
el.removeAttribute('data-sort-value');
183+
// Remove custom classes and attributes from elements
184+
document.querySelectorAll('.has-title, .folder-has-title').forEach(el => {
185+
el.classList.remove('has-title', 'folder-has-title');
110186
el.removeAttribute('data-title');
111-
112-
// Remove any added elements
113-
const sortSuffix = el.querySelector('.sort-suffix');
114-
if (sortSuffix) sortSuffix.remove();
187+
el.removeAttribute('data-sort');
188+
el.removeAttribute('data-folder-title');
189+
});
190+
191+
// Reset order styles on tree items and files
192+
document.querySelectorAll('.tree-item, .nav-file').forEach(el => {
193+
el.style.order = '';
115194
});
116195
}
117196

197+
/**
198+
* @description Plugin lifecycle method called when the plugin is disabled or Obsidian is closed
199+
* @details Performs cleanup to remove all plugin-added elements and styles
200+
* @returns {void}
201+
*/
118202
onunload() {
119-
console.log('Unloading TitleAppender plugin');
203+
console.log('Unloading DocsViewer plugin');
204+
// Remove all custom attributes and classes
120205
this.cleanupExistingElements();
206+
207+
// Remove flex container class from parent elements
208+
document.querySelectorAll('.docs-viewer-flex-container').forEach(el => {
209+
el.classList.remove('docs-viewer-flex-container');
210+
});
211+
212+
// Ensure order styles are cleared from navigation elements
213+
document.querySelectorAll('.nav-file, .nav-file-title').forEach(el => {
214+
el.style.order = '';
215+
});
121216
}
122217
}
123218

124-
module.exports = TitleAppenderPlugin;
219+
module.exports = DocsViewerPlugin;

docs/.obsidian/plugins/docs-viewer/manifest.json

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,5 @@
66
"description": "An Obsidian plugin to edit and view Docs-Viewer metadata",
77
"author": "Litruv",
88
"authorUrl": "https://lit.ruv.wtf",
9-
"fundingUrl": "https://ko-fi.com/zsolt",
10-
"helpUrl": "https://github.com/zsviczian/obsidian-excalidraw-plugin#readme",
119
"isDesktopOnly": false
1210
}

0 commit comments

Comments
 (0)