Skip to content

Commit 662f5a6

Browse files
committed
feat(task-view): implement dynamic metadata positioning
Add intelligent layout system that positions task metadata inline with short content or below with multi-line content. Includes: - New contentMetadataContainer with flex-wrap layout - Dynamic height detection using line-height comparison - CSS classes for single-line and multi-line content modes - Smooth transitions using Obsidian's toggleClass API - Responsive design for both list and tree view components The layout automatically adapts based on content length, improving readability while maintaining compact display for short tasks.
1 parent 1bf838a commit 662f5a6

File tree

4 files changed

+152
-6
lines changed

4 files changed

+152
-6
lines changed

src/components/task-view/listItem.ts

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ export class TaskListItemComponent extends Component {
2323
private markdownRenderer: MarkdownRendererComponent;
2424
private containerEl: HTMLElement;
2525
private contentEl: HTMLElement;
26+
private contentMetadataContainer: HTMLElement;
2627

2728
private metadataEl: HTMLElement;
2829

@@ -183,19 +184,22 @@ export class TaskListItemComponent extends Component {
183184
cls: "task-item-container",
184185
});
185186

187+
// Create content-metadata container for dynamic layout
188+
this.contentMetadataContainer = this.containerEl.createDiv({
189+
cls: "task-content-metadata-container",
190+
});
191+
186192
// Task content
187-
this.contentEl = createDiv({
193+
this.contentEl = this.contentMetadataContainer.createDiv({
188194
cls: "task-item-content",
189195
});
190196

191-
this.containerEl.appendChild(this.contentEl);
192-
193197
// Make content clickable for editing
194198
this.registerContentClickHandler();
195199

196200
this.renderMarkdown();
197201

198-
this.metadataEl = this.containerEl.createDiv({
202+
this.metadataEl = this.contentMetadataContainer.createDiv({
199203
cls: "task-item-metadata",
200204
});
201205

@@ -746,6 +750,35 @@ export class TaskListItemComponent extends Component {
746750

747751
// Re-register the click event for editing after rendering
748752
this.registerContentClickHandler();
753+
754+
// Update layout mode after content is rendered
755+
// Use requestAnimationFrame to ensure the content is fully rendered
756+
requestAnimationFrame(() => {
757+
this.updateLayoutMode();
758+
});
759+
}
760+
761+
/**
762+
* Detect content height and update layout mode
763+
*/
764+
private updateLayoutMode() {
765+
if (!this.contentEl || !this.contentMetadataContainer) {
766+
return;
767+
}
768+
769+
// Get the line height of the content element
770+
const computedStyle = window.getComputedStyle(this.contentEl);
771+
const lineHeight = parseFloat(computedStyle.lineHeight) || parseFloat(computedStyle.fontSize) * 1.4;
772+
773+
// Get actual content height
774+
const contentHeight = this.contentEl.scrollHeight;
775+
776+
// Check if content is multi-line (with some tolerance)
777+
const isMultiLine = contentHeight > lineHeight * 1.2;
778+
779+
// Apply appropriate layout class using Obsidian's toggleClass method
780+
this.contentMetadataContainer.toggleClass("multi-line-content", isMultiLine);
781+
this.contentMetadataContainer.toggleClass("single-line-content", !isMultiLine);
749782
}
750783

751784
/**

src/components/task-view/treeItem.ts

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ export class TaskTreeItemComponent extends Component {
3838

3939
private markdownRenderer: MarkdownRendererComponent;
4040
private contentEl: HTMLElement;
41+
private contentMetadataContainer: HTMLElement;
4142
private taskMap: Map<string, Task>;
4243

4344
// Use shared editor manager instead of individual editors
@@ -267,8 +268,13 @@ export class TaskTreeItemComponent extends Component {
267268
cls: "task-item-container",
268269
});
269270

271+
// Create content-metadata container for dynamic layout
272+
this.contentMetadataContainer = taskItemContainer.createDiv({
273+
cls: "task-content-metadata-container",
274+
});
275+
270276
// Task content with markdown rendering
271-
this.contentEl = taskItemContainer.createDiv({
277+
this.contentEl = this.contentMetadataContainer.createDiv({
272278
cls: "task-item-content",
273279
});
274280

@@ -278,7 +284,7 @@ export class TaskTreeItemComponent extends Component {
278284
this.renderMarkdown();
279285

280286
// Metadata container
281-
const metadataEl = taskItemContainer.createDiv({
287+
const metadataEl = this.contentMetadataContainer.createDiv({
282288
cls: "task-metadata",
283289
});
284290

@@ -823,6 +829,35 @@ export class TaskTreeItemComponent extends Component {
823829

824830
// Re-register the click event for editing after rendering
825831
this.registerContentClickHandler();
832+
833+
// Update layout mode after content is rendered
834+
// Use requestAnimationFrame to ensure the content is fully rendered
835+
requestAnimationFrame(() => {
836+
this.updateLayoutMode();
837+
});
838+
}
839+
840+
/**
841+
* Detect content height and update layout mode
842+
*/
843+
private updateLayoutMode() {
844+
if (!this.contentEl || !this.contentMetadataContainer) {
845+
return;
846+
}
847+
848+
// Get the line height of the content element
849+
const computedStyle = window.getComputedStyle(this.contentEl);
850+
const lineHeight = parseFloat(computedStyle.lineHeight) || parseFloat(computedStyle.fontSize) * 1.4;
851+
852+
// Get actual content height
853+
const contentHeight = this.contentEl.scrollHeight;
854+
855+
// Check if content is multi-line (with some tolerance)
856+
const isMultiLine = contentHeight > lineHeight * 1.2;
857+
858+
// Apply appropriate layout class using Obsidian's toggleClass method
859+
this.contentMetadataContainer.toggleClass("multi-line-content", isMultiLine);
860+
this.contentMetadataContainer.toggleClass("single-line-content", !isMultiLine);
826861
}
827862

828863
/**

src/styles/task-list.css

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,45 @@
6868
text-overflow: ellipsis;
6969
}
7070

71+
/* Dynamic content-metadata container */
72+
.task-content-metadata-container {
73+
display: flex;
74+
flex-wrap: wrap;
75+
align-items: flex-start;
76+
gap: var(--size-2-2);
77+
transition: all 0.2s ease;
78+
}
79+
80+
/* Single line content layout - horizontal */
81+
.task-content-metadata-container.single-line-content {
82+
flex-direction: row;
83+
align-items: center;
84+
}
85+
86+
.task-content-metadata-container.single-line-content .task-item-content {
87+
flex: 1;
88+
min-width: 0; /* Allow content to shrink */
89+
}
90+
91+
.task-content-metadata-container.single-line-content .task-item-metadata {
92+
flex-shrink: 0;
93+
margin-top: 0;
94+
}
95+
96+
/* Multi-line content layout - vertical */
97+
.task-content-metadata-container.multi-line-content {
98+
flex-direction: column;
99+
align-items: flex-start;
100+
}
101+
102+
.task-content-metadata-container.multi-line-content .task-item-content {
103+
width: 100%;
104+
}
105+
106+
.task-content-metadata-container.multi-line-content .task-item-metadata {
107+
margin-top: var(--size-2-2);
108+
}
109+
71110
.task-item-metadata {
72111
display: flex;
73112
align-items: center;

src/styles/tree-view.css

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,45 @@
100100
color: var(--text-muted);
101101
}
102102

103+
/* Dynamic content-metadata container for tree view */
104+
.tree-task-item .task-content-metadata-container {
105+
display: flex;
106+
flex-wrap: wrap;
107+
align-items: flex-start;
108+
gap: var(--size-2-2);
109+
transition: all 0.2s ease;
110+
}
111+
112+
/* Single line content layout - horizontal */
113+
.tree-task-item .task-content-metadata-container.single-line-content {
114+
flex-direction: row;
115+
align-items: center;
116+
}
117+
118+
.tree-task-item .task-content-metadata-container.single-line-content .task-item-content {
119+
flex: 1;
120+
min-width: 0; /* Allow content to shrink */
121+
}
122+
123+
.tree-task-item .task-content-metadata-container.single-line-content .task-metadata {
124+
flex-shrink: 0;
125+
margin-top: 0;
126+
}
127+
128+
/* Multi-line content layout - vertical */
129+
.tree-task-item .task-content-metadata-container.multi-line-content {
130+
flex-direction: column;
131+
align-items: flex-start;
132+
}
133+
134+
.tree-task-item .task-content-metadata-container.multi-line-content .task-item-content {
135+
width: 100%;
136+
}
137+
138+
.tree-task-item .task-content-metadata-container.multi-line-content .task-metadata {
139+
margin-top: 4px;
140+
}
141+
103142
/* Task metadata */
104143
.task-metadata {
105144
display: flex;

0 commit comments

Comments
 (0)