Skip to content

Commit 3512141

Browse files
committed
fix: Standardize tooltips to use Obsidian's native setTooltip function (#257)
Replaces HTML title attributes with Obsidian's setTooltip() throughout the codebase to eliminate duplicate tooltip issues and provide consistent user experience. Changes: - Updated all components to import and use setTooltip from Obsidian - Removed HTML title attributes from button elements, icons, and interactive elements - Added consistent tooltip placement (top) across all components - Fixed duplicate tooltip display issues in task widgets and modals Files affected: - TaskLinkWidget.ts: Task inline preview tooltips - InstantConvertButtons.ts: Convert button tooltips - TaskModal.ts and TaskCreationModal.ts: Modal action icons - TimeblockCreationModal.ts and TimeblockInfoModal.ts: Attachment management - StatusBarService.ts: Status bar tracking tooltips - settings.ts: Settings page interactive elements - FilterBar.ts: Filter controls and inputs - TaskCard.ts, AdvancedCalendarView.ts, MiniCalendarView.ts, PomodoroView.ts: View-specific tooltips Fixes #257
1 parent 0c16b70 commit 3512141

File tree

13 files changed

+113
-82
lines changed

13 files changed

+113
-82
lines changed

src/editor/InstantConvertButtons.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Extension, RangeSetBuilder, StateField, Transaction } from '@codemirror/state';
22
import { Decoration, DecorationSet, EditorView, WidgetType } from '@codemirror/view';
3-
import { setIcon, MarkdownView, Editor } from 'obsidian';
3+
import { setIcon, MarkdownView, Editor, setTooltip } from 'obsidian';
44
import TaskNotesPlugin from '../main';
55
import { TasksPluginParser } from '../utils/TasksPluginParser';
66

@@ -19,9 +19,11 @@ class ConvertButtonWidget extends WidgetType {
1919
const container = document.createElement('span');
2020
container.className = 'tasknotes-plugin';
2121

22-
const button = container.createEl('button', { cls: 'instant-convert-button' });
23-
button.setAttribute('title', 'Convert to TaskNote');
24-
button.setAttribute('aria-label', 'Convert to TaskNote');
22+
const button = container.createEl('button', {
23+
cls: 'instant-convert-button',
24+
attr: { 'aria-label': 'Convert to TaskNote' }
25+
});
26+
setTooltip(button, 'Convert to TaskNote', { placement: 'top' });
2527

2628
// Add the convert icon
2729
const iconSpan = button.createEl('span', { cls: 'instant-convert-button__icon' });

src/editor/TaskLinkWidget.ts

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { EditorView, WidgetType } from '@codemirror/view';
2-
import { TFile, setIcon, Notice } from 'obsidian';
2+
import { TFile, setIcon, Notice, setTooltip } from 'obsidian';
33
import { TaskInfo } from '../types';
44
import TaskNotesPlugin from '../main';
55
import { formatDateTimeForDisplay, getDatePart, getTimePart } from '../utils/dateUtils';
@@ -49,9 +49,9 @@ export class TaskLinkWidget extends WidgetType {
4949
// Status indicator dot (BEFORE text, styled like task cards)
5050
const statusConfig = this.plugin.statusManager.getStatusConfig(effectiveStatus);
5151
const statusDot = container.createEl('span', {
52-
cls: 'task-inline-preview__status-dot',
53-
attr: { title: `Status: ${statusConfig ? statusConfig.label : effectiveStatus}` }
52+
cls: 'task-inline-preview__status-dot'
5453
});
54+
setTooltip(statusDot, `Status: ${statusConfig ? statusConfig.label : effectiveStatus}`, { placement: 'top' });
5555
if (statusConfig) {
5656
statusDot.style.borderColor = statusConfig.color;
5757

@@ -149,9 +149,9 @@ export class TaskLinkWidget extends WidgetType {
149149
const priorityConfig = this.plugin.priorityManager.getPriorityConfig(this.taskInfo.priority);
150150
if (priorityConfig) {
151151
const priorityDot = container.createEl('span', {
152-
cls: 'task-inline-preview__priority-dot task-card__priority-dot',
153-
attr: { title: `Priority: ${priorityConfig.label} (click to change)` }
152+
cls: 'task-inline-preview__priority-dot task-card__priority-dot'
154153
});
154+
setTooltip(priorityDot, `Priority: ${priorityConfig.label} (click to change)`, { placement: 'top' });
155155
priorityDot.style.backgroundColor = priorityConfig.color;
156156

157157
// Add click context menu for priority (same as TaskCard)
@@ -171,7 +171,7 @@ export class TaskLinkWidget extends WidgetType {
171171
const newPriorityConfig = this.plugin.priorityManager.getPriorityConfig(newPriority);
172172
if (newPriorityConfig) {
173173
priorityDot.style.backgroundColor = newPriorityConfig.color;
174-
priorityDot.setAttribute('title', `Priority: ${newPriorityConfig.label} (click to change)`);
174+
setTooltip(priorityDot, `Priority: ${newPriorityConfig.label} (click to change)`, { placement: 'top' });
175175
}
176176
} catch (error) {
177177
console.error('Error updating priority:', error);
@@ -209,9 +209,9 @@ export class TaskLinkWidget extends WidgetType {
209209
});
210210

211211
const dueDateSpan = container.createEl('span', {
212-
cls: 'task-inline-preview__date task-inline-preview__date--due task-card__metadata-date task-card__metadata-date--due',
213-
attr: { title: `Due: ${tooltipText} (click to change)` }
212+
cls: 'task-inline-preview__date task-inline-preview__date--due task-card__metadata-date task-card__metadata-date--due'
214213
});
214+
setTooltip(dueDateSpan, `Due: ${tooltipText} (click to change)`, { placement: 'top' });
215215

216216
const calendarIcon = dueDateSpan.createEl('span', { cls: 'task-inline-preview__date-icon' });
217217
setIcon(calendarIcon, 'calendar');
@@ -266,9 +266,9 @@ export class TaskLinkWidget extends WidgetType {
266266
});
267267

268268
const scheduledSpan = container.createEl('span', {
269-
cls: 'task-inline-preview__date task-inline-preview__date--scheduled task-card__metadata-date task-card__metadata-date--scheduled',
270-
attr: { title: `Scheduled: ${tooltipText} (click to change)` }
269+
cls: 'task-inline-preview__date task-inline-preview__date--scheduled task-card__metadata-date task-card__metadata-date--scheduled'
271270
});
271+
setTooltip(scheduledSpan, `Scheduled: ${tooltipText} (click to change)`, { placement: 'top' });
272272

273273
const clockIcon = scheduledSpan.createEl('span', { cls: 'task-inline-preview__date-icon' });
274274
setIcon(clockIcon, 'clock');
@@ -311,10 +311,10 @@ export class TaskLinkWidget extends WidgetType {
311311
const recurringIndicator = container.createEl('span', {
312312
cls: 'task-inline-preview__recurring-indicator',
313313
attr: {
314-
'title': `Recurring task (click to change)`,
315314
'aria-label': `Recurring task (click to change)`
316315
}
317316
});
317+
setTooltip(recurringIndicator, 'Recurring task (click to change)', { placement: 'top' });
318318

319319
// Use Obsidian's built-in rotate-ccw icon for recurring tasks
320320
setIcon(recurringIndicator, 'rotate-ccw');
@@ -345,9 +345,9 @@ export class TaskLinkWidget extends WidgetType {
345345
// Add Lucide pencil icon
346346
const pencilIcon = container.createEl('span', {
347347
cls: 'task-inline-preview__pencil',
348-
attr: { 'title': 'Task options' },
349348
text: ''
350349
});
350+
setTooltip(pencilIcon, 'Task options', { placement: 'top' });
351351
setIcon(pencilIcon, 'ellipsis-vertical');
352352

353353
// Store data for interactions

src/modals/TaskCreationModal.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { App, Notice, setIcon, AbstractInputSuggest } from 'obsidian';
1+
import { App, Notice, setIcon, AbstractInputSuggest, setTooltip } from 'obsidian';
22
import TaskNotesPlugin from '../main';
33
import { TaskModal } from './TaskModal';
44
import { TaskInfo, TaskCreationData } from '../types';
@@ -404,7 +404,7 @@ export class TaskCreationModal extends TaskModal {
404404
if (iconEl) {
405405
setIcon(iconEl as HTMLElement, this.isExpanded ? 'chevron-up' : 'chevron-down');
406406
}
407-
icon.setAttribute('title', this.isExpanded ? 'Hide detailed options' : 'Show detailed options');
407+
setTooltip(icon, this.isExpanded ? 'Hide detailed options' : 'Show detailed options', { placement: 'top' });
408408
});
409409

410410
// Add separator

src/modals/TaskModal.ts

Lines changed: 26 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { App, Modal, Setting, setIcon, TAbstractFile, TFile, AbstractInputSuggest } from 'obsidian';
1+
import { App, Modal, Setting, setIcon, TAbstractFile, TFile, AbstractInputSuggest, setTooltip } from 'obsidian';
22
import TaskNotesPlugin from '../main';
33
import { DateContextMenu } from '../components/DateContextMenu';
44
import { PriorityContextMenu } from '../components/PriorityContextMenu';
@@ -139,7 +139,8 @@ export abstract class TaskModal extends Modal {
139139
): HTMLElement {
140140
const iconContainer = container.createDiv('action-icon');
141141
iconContainer.setAttribute('aria-label', tooltip);
142-
iconContainer.setAttribute('title', tooltip);
142+
// Store initial tooltip for later updates but don't set title attribute
143+
iconContainer.setAttribute('data-initial-tooltip', tooltip);
143144

144145
// Add data attribute for easier identification
145146
if (dataType) {
@@ -520,22 +521,26 @@ export abstract class TaskModal extends Modal {
520521

521522
// Update due date icon
522523
const dueDateIcon = this.actionBar.querySelector('[data-type="due-date"]') as HTMLElement;
523-
if (dueDateIcon && this.dueDate) {
524-
dueDateIcon.classList.add('has-value');
525-
dueDateIcon.setAttribute('title', `Due: ${this.dueDate}`);
526-
} else if (dueDateIcon) {
527-
dueDateIcon.classList.remove('has-value');
528-
dueDateIcon.setAttribute('title', 'Set due date');
524+
if (dueDateIcon) {
525+
if (this.dueDate) {
526+
dueDateIcon.classList.add('has-value');
527+
setTooltip(dueDateIcon, `Due: ${this.dueDate}`, { placement: 'top' });
528+
} else {
529+
dueDateIcon.classList.remove('has-value');
530+
setTooltip(dueDateIcon, 'Set due date', { placement: 'top' });
531+
}
529532
}
530533

531534
// Update scheduled date icon
532535
const scheduledDateIcon = this.actionBar.querySelector('[data-type="scheduled-date"]') as HTMLElement;
533-
if (scheduledDateIcon && this.scheduledDate) {
534-
scheduledDateIcon.classList.add('has-value');
535-
scheduledDateIcon.setAttribute('title', `Scheduled: ${this.scheduledDate}`);
536-
} else if (scheduledDateIcon) {
537-
scheduledDateIcon.classList.remove('has-value');
538-
scheduledDateIcon.setAttribute('title', 'Set scheduled date');
536+
if (scheduledDateIcon) {
537+
if (this.scheduledDate) {
538+
scheduledDateIcon.classList.add('has-value');
539+
setTooltip(scheduledDateIcon, `Scheduled: ${this.scheduledDate}`, { placement: 'top' });
540+
} else {
541+
scheduledDateIcon.classList.remove('has-value');
542+
setTooltip(scheduledDateIcon, 'Set scheduled date', { placement: 'top' });
543+
}
539544
}
540545

541546
// Update status icon
@@ -547,10 +552,10 @@ export abstract class TaskModal extends Modal {
547552

548553
if (this.status && statusConfig && statusConfig.value !== this.getDefaultStatus()) {
549554
statusIcon.classList.add('has-value');
550-
statusIcon.setAttribute('title', `Status: ${statusLabel}`);
555+
setTooltip(statusIcon, `Status: ${statusLabel}`, { placement: 'top' });
551556
} else {
552557
statusIcon.classList.remove('has-value');
553-
statusIcon.setAttribute('title', 'Set status');
558+
setTooltip(statusIcon, 'Set status', { placement: 'top' });
554559
}
555560

556561
// Apply status color to the icon
@@ -571,10 +576,10 @@ export abstract class TaskModal extends Modal {
571576

572577
if (this.priority && priorityConfig && priorityConfig.value !== this.getDefaultPriority()) {
573578
priorityIcon.classList.add('has-value');
574-
priorityIcon.setAttribute('title', `Priority: ${priorityLabel}`);
579+
setTooltip(priorityIcon, `Priority: ${priorityLabel}`, { placement: 'top' });
575580
} else {
576581
priorityIcon.classList.remove('has-value');
577-
priorityIcon.setAttribute('title', 'Set priority');
582+
setTooltip(priorityIcon, 'Set priority', { placement: 'top' });
578583
}
579584

580585
// Apply priority color to the icon
@@ -591,10 +596,10 @@ export abstract class TaskModal extends Modal {
591596
if (recurrenceIcon) {
592597
if (this.recurrenceRule && this.recurrenceRule.trim()) {
593598
recurrenceIcon.classList.add('has-value');
594-
recurrenceIcon.setAttribute('title', `Recurrence: ${this.getRecurrenceDisplayText()}`);
599+
setTooltip(recurrenceIcon, `Recurrence: ${this.getRecurrenceDisplayText()}`, { placement: 'top' });
595600
} else {
596601
recurrenceIcon.classList.remove('has-value');
597-
recurrenceIcon.setAttribute('title', 'Set recurrence');
602+
setTooltip(recurrenceIcon, 'Set recurrence', { placement: 'top' });
598603
}
599604
}
600605
}
@@ -703,7 +708,7 @@ export abstract class TaskModal extends Modal {
703708
cls: 'task-project-remove',
704709
text: '×'
705710
});
706-
removeBtn.title = 'Remove project';
711+
setTooltip(removeBtn, 'Remove project', { placement: 'top' });
707712
removeBtn.addEventListener('click', () => {
708713
this.removeProject(file);
709714
});

src/modals/TimeblockCreationModal.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { App, Modal, Setting, Notice, TAbstractFile, parseYaml, stringifyYaml } from 'obsidian';
1+
import { App, Modal, Setting, Notice, TAbstractFile, parseYaml, stringifyYaml, setTooltip } from 'obsidian';
22
import TaskNotesPlugin from '../main';
33
import { TimeBlock, DailyNoteFrontmatter } from '../types';
44
import { generateTimeblockId } from '../utils/helpers';
@@ -329,7 +329,7 @@ export class TimeblockCreationModal extends Modal {
329329
cls: 'timeblock-attachment-remove',
330330
text: '×'
331331
});
332-
removeBtn.title = 'Remove attachment';
332+
setTooltip(removeBtn, 'Remove attachment', { placement: 'top' });
333333
removeBtn.addEventListener('click', () => {
334334
this.removeAttachment(file);
335335
});

src/modals/TimeblockInfoModal.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { App, Modal, Setting, Notice, TAbstractFile, parseYaml, stringifyYaml, TFile } from 'obsidian';
1+
import { App, Modal, Setting, Notice, TAbstractFile, parseYaml, stringifyYaml, TFile, setTooltip } from 'obsidian';
22
import TaskNotesPlugin from '../main';
33
import { AttachmentSelectModal } from './AttachmentSelectModal';
44
import {
@@ -216,7 +216,7 @@ export class TimeblockInfoModal extends Modal {
216216
// Info container (clickable to open)
217217
const infoEl = attachmentItem.createDiv({ cls: 'timeblock-attachment-info' });
218218
infoEl.style.cursor = 'pointer';
219-
infoEl.title = 'Click to open';
219+
setTooltip(infoEl, 'Click to open', { placement: 'top' });
220220
infoEl.addEventListener('click', () => this.openAttachment(file));
221221

222222
// File name
@@ -234,7 +234,7 @@ export class TimeblockInfoModal extends Modal {
234234
cls: 'timeblock-attachment-remove',
235235
text: '×'
236236
});
237-
removeBtn.title = 'Remove attachment';
237+
setTooltip(removeBtn, 'Remove attachment', { placement: 'top' });
238238
removeBtn.addEventListener('click', (e) => {
239239
e.stopPropagation();
240240
this.removeAttachment(file);

src/services/StatusBarService.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { TaskInfo } from '../types';
22
import { RequestDeduplicator } from '../utils/RequestDeduplicator';
3+
import { setTooltip } from 'obsidian';
34

45
export class StatusBarService {
56
private plugin: import('../main').default;
@@ -111,7 +112,7 @@ export class StatusBarService {
111112
textEl.setText(`Tracking: ${truncatedTitle}`);
112113

113114
// Add tooltip with full title
114-
this.statusBarElement.setAttribute('title', `Currently tracking: ${task.title}`);
115+
setTooltip(this.statusBarElement, `Currently tracking: ${task.title}`, { placement: 'top' });
115116
} else {
116117
textEl.setText(`Tracking ${count} tasks`);
117118

@@ -123,7 +124,7 @@ export class StatusBarService {
123124
const tooltipText = count > 5
124125
? `${taskTitles}\n... and ${count - 5} more`
125126
: taskTitles;
126-
this.statusBarElement.setAttribute('title', `Currently tracking:\n${tooltipText}`);
127+
setTooltip(this.statusBarElement, `Currently tracking:\n${tooltipText}`, { placement: 'top' });
127128
}
128129
}
129130

src/settings/settings.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { App, PluginSettingTab, Setting, Notice, setIcon, TAbstractFile, TFile } from 'obsidian';
1+
import { App, PluginSettingTab, Setting, Notice, setIcon, TAbstractFile, TFile, setTooltip } from 'obsidian';
22
import TaskNotesPlugin from '../main';
33
import { FieldMapping, StatusConfig, PriorityConfig, SavedView } from '../types';
44
import { StatusManager } from '../services/StatusManager';
@@ -1732,7 +1732,7 @@ export class TaskNotesSettingTab extends PluginSettingTab {
17321732
// Drag handle
17331733
const dragHandle = statusRow.createDiv('settings-drag-handle');
17341734
dragHandle.innerHTML = '☰';
1735-
dragHandle.setAttribute('title', 'Drag to reorder');
1735+
setTooltip(dragHandle, 'Drag to reorder', { placement: 'top' });
17361736

17371737
// Color indicator
17381738
const colorIndicator = statusRow.createDiv('settings-color-indicator settings-view__color-indicator');
@@ -2338,13 +2338,13 @@ export class TaskNotesSettingTab extends PluginSettingTab {
23382338
const statusIndicator = subRow.createDiv('settings-status-indicator');
23392339
if (subscription.enabled) {
23402340
statusIndicator.addClass('enabled');
2341-
statusIndicator.title = subscription.lastError ? `Error: ${subscription.lastError}` : 'Active';
2341+
setTooltip(statusIndicator, subscription.lastError ? `Error: ${subscription.lastError}` : 'Active', { placement: 'top' });
23422342
if (subscription.lastError) {
23432343
statusIndicator.addClass('error');
23442344
}
23452345
} else {
23462346
statusIndicator.addClass('disabled');
2347-
statusIndicator.title = 'Disabled';
2347+
setTooltip(statusIndicator, 'Disabled', { placement: 'top' });
23482348
}
23492349

23502350
// Subscription info
@@ -2656,7 +2656,7 @@ export class TaskNotesSettingTab extends PluginSettingTab {
26562656
cls: 'default-project-remove',
26572657
text: '×'
26582658
});
2659-
removeBtn.title = 'Remove project';
2659+
setTooltip(removeBtn, 'Remove project', { placement: 'top' });
26602660
removeBtn.addEventListener('click', () => {
26612661
this.removeDefaultProject(file);
26622662
this.renderDefaultProjectsList(container);

0 commit comments

Comments
 (0)