mirror of
https://github.com/callumalpass/tasknotes.git
synced 2026-07-22 12:50:26 +00:00
Refactor UI styling, messaging, and SVG icon rendering across task settings and views
• Normalize wording for new priority/status and untitled tasks: – In PriorityManager and StatusManager, update “New Priority”/“New Status” labels to use lowercase “priority/status”. – Change default task titles from “Untitled Task” to “Untitled task” in task info extraction. • Update settings tab UI to use centralized CSS classes rather than inline styles: – Remove inline flex, borders, paddings from tab navigation and table elements. – Change “General” tab name to “Basic setup”. – Replace inline warning message styling with a “settings-warning” div. – Refactor status/priority list rows to use new classes (e.g., “settings-item-row”, “settings-input”, “settings-delete-button”, etc.). – Update CSS (styles.css) with new styling rules for tab nav, table, lists, input elements, and buttons. • Simplify and secure SVG icon rendering in TaskListView: – Replace innerHTML usage for SVG icons with a new createSVGIcon helper that safely constructs SVG nodes. – Update toggle and archive button icon rendering to use the new helper methods. – Update text content for time icons to use textContent rather than innerHTML. – Ensure document click handler cleanup for dropdowns is properly tracked. • Clear YAML cache on file index rebuild: – In FileIndexer, clear the global YAML cache when the index is rebuilt. • Miscellaneous improvements: – Remove extraneous debugging logs in CalendarView. – Adjust minor styling and element class names for clarity and consistency. This commit brings improved UI consistency, enhanced security by avoiding innerHTML for SVGs, and better code maintainability by moving styling to CSS.
This commit is contained in:
parent
8905686a69
commit
9dd3eebcb4
9 changed files with 227 additions and 142 deletions
|
|
@ -204,7 +204,7 @@ export class PriorityManager {
|
|||
return {
|
||||
id,
|
||||
value: 'new-priority',
|
||||
label: 'New Priority',
|
||||
label: 'New priority',
|
||||
color: '#808080',
|
||||
weight
|
||||
};
|
||||
|
|
|
|||
|
|
@ -170,7 +170,7 @@ export class StatusManager {
|
|||
return {
|
||||
id,
|
||||
value: 'new-status',
|
||||
label: 'New Status',
|
||||
label: 'New status',
|
||||
color: '#808080',
|
||||
isCompleted: false,
|
||||
order
|
||||
|
|
|
|||
|
|
@ -147,12 +147,9 @@ export class TaskNotesSettingTab extends PluginSettingTab {
|
|||
|
||||
// Create tab navigation
|
||||
const tabNav = containerEl.createDiv('settings-tab-nav');
|
||||
tabNav.style.display = 'flex';
|
||||
tabNav.style.borderBottom = '1px solid var(--background-modifier-border)';
|
||||
tabNav.style.marginBottom = '20px';
|
||||
|
||||
const tabs = [
|
||||
{ id: 'general', name: 'General' },
|
||||
{ id: 'general', name: 'Basic setup' },
|
||||
{ id: 'field-mapping', name: 'Field mapping' },
|
||||
{ id: 'statuses', name: 'Statuses' },
|
||||
{ id: 'priorities', name: 'Priorities' },
|
||||
|
|
@ -166,13 +163,6 @@ export class TaskNotesSettingTab extends PluginSettingTab {
|
|||
cls: this.activeTab === tab.id ? 'settings-tab-button active' : 'settings-tab-button'
|
||||
});
|
||||
|
||||
tabButton.style.padding = '8px 16px';
|
||||
tabButton.style.border = 'none';
|
||||
tabButton.style.background = this.activeTab === tab.id ? 'var(--interactive-accent)' : 'transparent';
|
||||
tabButton.style.color = this.activeTab === tab.id ? 'var(--text-on-accent)' : 'var(--text-normal)';
|
||||
tabButton.style.cursor = 'pointer';
|
||||
tabButton.style.borderRadius = '4px 4px 0 0';
|
||||
|
||||
tabButton.addEventListener('click', () => {
|
||||
this.switchTab(tab.id);
|
||||
});
|
||||
|
|
@ -184,7 +174,9 @@ export class TaskNotesSettingTab extends PluginSettingTab {
|
|||
// Create all tab content containers
|
||||
tabs.forEach(tab => {
|
||||
const tabContent = tabContentsEl.createDiv('settings-tab-content');
|
||||
tabContent.style.display = this.activeTab === tab.id ? 'block' : 'none';
|
||||
if (this.activeTab === tab.id) {
|
||||
tabContent.addClass('active');
|
||||
}
|
||||
this.tabContents[tab.id] = tabContent;
|
||||
});
|
||||
|
||||
|
|
@ -329,15 +321,9 @@ export class TaskNotesSettingTab extends PluginSettingTab {
|
|||
const container = this.tabContents['field-mapping'];
|
||||
|
||||
// Warning message
|
||||
const warning = container.createDiv();
|
||||
warning.style.padding = '12px';
|
||||
warning.style.marginBottom = '20px';
|
||||
warning.style.backgroundColor = 'var(--background-modifier-warning)';
|
||||
warning.style.borderRadius = '4px';
|
||||
warning.innerHTML = `
|
||||
<strong>⚠️ Warning:</strong> TaskNotes will read AND write using these property names.
|
||||
Changing these after creating tasks may cause inconsistencies.
|
||||
`;
|
||||
const warning = container.createDiv('settings-warning');
|
||||
const warningIcon = warning.createEl('strong', { text: '⚠️ Warning:' });
|
||||
warning.createSpan({ text: ' TaskNotes will read AND write using these property names. Changing these after creating tasks may cause inconsistencies.' });
|
||||
|
||||
container.createEl('h3', { text: 'Field mapping' });
|
||||
container.createEl('p', {
|
||||
|
|
@ -345,9 +331,7 @@ export class TaskNotesSettingTab extends PluginSettingTab {
|
|||
});
|
||||
|
||||
// Create mapping table
|
||||
const table = container.createEl('table');
|
||||
table.style.width = '100%';
|
||||
table.style.borderCollapse = 'collapse';
|
||||
const table = container.createEl('table', { cls: 'settings-table' });
|
||||
|
||||
const header = table.createEl('tr');
|
||||
header.createEl('th', { text: 'TaskNotes field' });
|
||||
|
|
@ -371,20 +355,14 @@ export class TaskNotesSettingTab extends PluginSettingTab {
|
|||
fieldMappings.forEach(([field, label]) => {
|
||||
const row = table.createEl('tr');
|
||||
const labelCell = row.createEl('td');
|
||||
labelCell.style.padding = '8px';
|
||||
labelCell.style.borderBottom = '1px solid var(--background-modifier-border)';
|
||||
labelCell.textContent = label;
|
||||
|
||||
const inputCell = row.createEl('td');
|
||||
inputCell.style.padding = '8px';
|
||||
inputCell.style.borderBottom = '1px solid var(--background-modifier-border)';
|
||||
|
||||
const input = inputCell.createEl('input', {
|
||||
type: 'text',
|
||||
value: this.plugin.settings.fieldMapping[field]
|
||||
});
|
||||
input.style.width = '100%';
|
||||
input.style.padding = '4px';
|
||||
|
||||
input.addEventListener('change', async () => {
|
||||
this.plugin.settings.fieldMapping[field] = input.value;
|
||||
|
|
@ -409,14 +387,13 @@ export class TaskNotesSettingTab extends PluginSettingTab {
|
|||
private renderStatusesTab(): void {
|
||||
const container = this.tabContents['statuses'];
|
||||
|
||||
container.createEl('h3', { text: 'Task Statuses' });
|
||||
container.createEl('h3', { text: 'Task statuses' });
|
||||
container.createEl('p', {
|
||||
text: 'Define the statuses available for your tasks. The order determines the cycling sequence.'
|
||||
});
|
||||
|
||||
// Status list
|
||||
const statusList = container.createDiv();
|
||||
statusList.style.marginBottom = '20px';
|
||||
const statusList = container.createDiv('settings-list');
|
||||
|
||||
this.renderStatusList(statusList);
|
||||
|
||||
|
|
@ -424,7 +401,7 @@ export class TaskNotesSettingTab extends PluginSettingTab {
|
|||
new Setting(container)
|
||||
.setName('Add new status')
|
||||
.addButton(button => button
|
||||
.setButtonText('Add Status')
|
||||
.setButtonText('Add status')
|
||||
.onClick(async () => {
|
||||
const newStatus = StatusManager.createDefaultStatus(this.plugin.settings.customStatuses);
|
||||
this.plugin.settings.customStatuses.push(newStatus);
|
||||
|
|
@ -439,72 +416,48 @@ export class TaskNotesSettingTab extends PluginSettingTab {
|
|||
const sortedStatuses = [...this.plugin.settings.customStatuses].sort((a, b) => a.order - b.order);
|
||||
|
||||
sortedStatuses.forEach((status, index) => {
|
||||
const statusRow = container.createDiv();
|
||||
statusRow.style.display = 'flex';
|
||||
statusRow.style.alignItems = 'center';
|
||||
statusRow.style.marginBottom = '12px';
|
||||
statusRow.style.padding = '12px';
|
||||
statusRow.style.border = '1px solid var(--background-modifier-border)';
|
||||
statusRow.style.borderRadius = '4px';
|
||||
const statusRow = container.createDiv('settings-item-row');
|
||||
|
||||
// Color indicator
|
||||
const colorIndicator = statusRow.createDiv();
|
||||
colorIndicator.style.width = '20px';
|
||||
colorIndicator.style.height = '20px';
|
||||
colorIndicator.style.borderRadius = '50%';
|
||||
colorIndicator.style.backgroundColor = status.color;
|
||||
colorIndicator.style.marginRight = '12px';
|
||||
colorIndicator.style.flexShrink = '0';
|
||||
const colorIndicator = statusRow.createDiv('settings-color-indicator');
|
||||
colorIndicator.style.backgroundColor = status.color; // Keep this - user color
|
||||
|
||||
// Status value input
|
||||
const valueInput = statusRow.createEl('input', {
|
||||
type: 'text',
|
||||
value: status.value
|
||||
value: status.value,
|
||||
cls: 'settings-input value-input'
|
||||
});
|
||||
valueInput.style.marginRight = '12px';
|
||||
valueInput.style.width = '120px';
|
||||
|
||||
// Status label input
|
||||
const labelInput = statusRow.createEl('input', {
|
||||
type: 'text',
|
||||
value: status.label
|
||||
value: status.label,
|
||||
cls: 'settings-input label-input'
|
||||
});
|
||||
labelInput.style.marginRight = '12px';
|
||||
labelInput.style.width = '120px';
|
||||
|
||||
// Color input
|
||||
const colorInput = statusRow.createEl('input', {
|
||||
type: 'color',
|
||||
value: status.color
|
||||
value: status.color,
|
||||
cls: 'settings-input color-input'
|
||||
});
|
||||
colorInput.style.marginRight = '12px';
|
||||
colorInput.style.width = '40px';
|
||||
|
||||
// Completed checkbox
|
||||
const completedLabel = statusRow.createEl('label');
|
||||
completedLabel.style.marginRight = '12px';
|
||||
completedLabel.style.display = 'flex';
|
||||
completedLabel.style.alignItems = 'center';
|
||||
const completedLabel = statusRow.createEl('label', { cls: 'settings-checkbox-label' });
|
||||
|
||||
const completedCheckbox = completedLabel.createEl('input', {
|
||||
type: 'checkbox'
|
||||
});
|
||||
completedCheckbox.checked = status.isCompleted;
|
||||
completedCheckbox.style.marginRight = '4px';
|
||||
|
||||
completedLabel.createSpan({ text: 'Completed' });
|
||||
|
||||
// Delete button
|
||||
const deleteButton = statusRow.createEl('button', {
|
||||
text: 'Delete'
|
||||
text: 'Delete',
|
||||
cls: 'settings-delete-button'
|
||||
});
|
||||
deleteButton.style.marginLeft = 'auto';
|
||||
deleteButton.style.backgroundColor = 'var(--interactive-accent-rgb)';
|
||||
deleteButton.style.color = 'var(--text-on-accent)';
|
||||
deleteButton.style.border = 'none';
|
||||
deleteButton.style.padding = '4px 8px';
|
||||
deleteButton.style.borderRadius = '4px';
|
||||
deleteButton.style.cursor = 'pointer';
|
||||
|
||||
// Event listeners
|
||||
const updateStatus = async () => {
|
||||
|
|
@ -546,8 +499,7 @@ export class TaskNotesSettingTab extends PluginSettingTab {
|
|||
});
|
||||
|
||||
// Priority list
|
||||
const priorityList = container.createDiv();
|
||||
priorityList.style.marginBottom = '20px';
|
||||
const priorityList = container.createDiv('settings-list');
|
||||
|
||||
this.renderPriorityList(priorityList);
|
||||
|
||||
|
|
@ -555,7 +507,7 @@ export class TaskNotesSettingTab extends PluginSettingTab {
|
|||
new Setting(container)
|
||||
.setName('Add new priority')
|
||||
.addButton(button => button
|
||||
.setButtonText('Add Priority')
|
||||
.setButtonText('Add priority')
|
||||
.onClick(async () => {
|
||||
const newPriority = PriorityManager.createDefaultPriority(this.plugin.settings.customPriorities);
|
||||
this.plugin.settings.customPriorities.push(newPriority);
|
||||
|
|
@ -570,66 +522,45 @@ export class TaskNotesSettingTab extends PluginSettingTab {
|
|||
const sortedPriorities = [...this.plugin.settings.customPriorities].sort((a, b) => b.weight - a.weight);
|
||||
|
||||
sortedPriorities.forEach((priority, index) => {
|
||||
const priorityRow = container.createDiv();
|
||||
priorityRow.style.display = 'flex';
|
||||
priorityRow.style.alignItems = 'center';
|
||||
priorityRow.style.marginBottom = '12px';
|
||||
priorityRow.style.padding = '12px';
|
||||
priorityRow.style.border = '1px solid var(--background-modifier-border)';
|
||||
priorityRow.style.borderRadius = '4px';
|
||||
const priorityRow = container.createDiv('settings-item-row');
|
||||
|
||||
// Color indicator
|
||||
const colorIndicator = priorityRow.createDiv();
|
||||
colorIndicator.style.width = '20px';
|
||||
colorIndicator.style.height = '20px';
|
||||
colorIndicator.style.borderRadius = '50%';
|
||||
colorIndicator.style.backgroundColor = priority.color;
|
||||
colorIndicator.style.marginRight = '12px';
|
||||
colorIndicator.style.flexShrink = '0';
|
||||
const colorIndicator = priorityRow.createDiv('settings-color-indicator');
|
||||
colorIndicator.style.backgroundColor = priority.color; // Keep this - user color
|
||||
|
||||
// Priority value input
|
||||
const valueInput = priorityRow.createEl('input', {
|
||||
type: 'text',
|
||||
value: priority.value
|
||||
value: priority.value,
|
||||
cls: 'settings-input value-input'
|
||||
});
|
||||
valueInput.style.marginRight = '12px';
|
||||
valueInput.style.width = '120px';
|
||||
|
||||
// Priority label input
|
||||
const labelInput = priorityRow.createEl('input', {
|
||||
type: 'text',
|
||||
value: priority.label
|
||||
value: priority.label,
|
||||
cls: 'settings-input label-input'
|
||||
});
|
||||
labelInput.style.marginRight = '12px';
|
||||
labelInput.style.width = '120px';
|
||||
|
||||
// Color input
|
||||
const colorInput = priorityRow.createEl('input', {
|
||||
type: 'color',
|
||||
value: priority.color
|
||||
value: priority.color,
|
||||
cls: 'settings-input color-input'
|
||||
});
|
||||
colorInput.style.marginRight = '12px';
|
||||
colorInput.style.width = '40px';
|
||||
|
||||
// Weight input
|
||||
const weightInput = priorityRow.createEl('input', {
|
||||
type: 'number',
|
||||
value: priority.weight.toString()
|
||||
value: priority.weight.toString(),
|
||||
cls: 'settings-input weight-input'
|
||||
});
|
||||
weightInput.style.marginRight = '12px';
|
||||
weightInput.style.width = '80px';
|
||||
|
||||
// Delete button
|
||||
const deleteButton = priorityRow.createEl('button', {
|
||||
text: 'Delete'
|
||||
text: 'Delete',
|
||||
cls: 'settings-delete-button'
|
||||
});
|
||||
deleteButton.style.marginLeft = 'auto';
|
||||
deleteButton.style.backgroundColor = 'var(--interactive-accent-rgb)';
|
||||
deleteButton.style.color = 'var(--text-on-accent)';
|
||||
deleteButton.style.border = 'none';
|
||||
deleteButton.style.padding = '4px 8px';
|
||||
deleteButton.style.borderRadius = '4px';
|
||||
deleteButton.style.cursor = 'pointer';
|
||||
|
||||
// Event listeners
|
||||
const updatePriority = async () => {
|
||||
|
|
|
|||
|
|
@ -721,6 +721,7 @@ export class FileIndexer {
|
|||
// Clear all caches
|
||||
this.fileIndex = null;
|
||||
this.calendarCache.clear();
|
||||
YAMLCache.clearCache(); // Clear global YAML cache
|
||||
|
||||
// Clear event handlers object
|
||||
this.eventHandlers = {};
|
||||
|
|
|
|||
|
|
@ -324,7 +324,7 @@ export function extractTaskInfo(
|
|||
|
||||
// Ensure required fields have defaults
|
||||
const taskInfo: TaskInfo = {
|
||||
title: mappedTask.title || 'Untitled Task',
|
||||
title: mappedTask.title || 'Untitled task',
|
||||
status: mappedTask.status || 'open',
|
||||
priority: mappedTask.priority || 'normal',
|
||||
due: mappedTask.due,
|
||||
|
|
@ -367,7 +367,7 @@ export function extractTaskInfo(
|
|||
const contexts = yaml.contexts || [];
|
||||
|
||||
return {
|
||||
title: yaml.title || 'Untitled Task',
|
||||
title: yaml.title || 'Untitled task',
|
||||
status: yaml.status || 'open',
|
||||
priority: yaml.priority || 'normal',
|
||||
due: yaml.due,
|
||||
|
|
|
|||
|
|
@ -371,7 +371,7 @@ export class CalendarView extends ItemView {
|
|||
|
||||
const indicator = document.createElement('div');
|
||||
indicator.className = 'cache-loading-indicator';
|
||||
indicator.innerHTML = 'Loading calendar data...';
|
||||
indicator.textContent = 'Loading calendar data...';
|
||||
container.prepend(indicator);
|
||||
}
|
||||
|
||||
|
|
@ -887,7 +887,6 @@ export class CalendarView extends ItemView {
|
|||
let dailyNotesCache: Set<string>;
|
||||
|
||||
if (!CalendarView.dailyNotesInitialized) {
|
||||
console.debug('First call to colorizeCalendarForDailyNotes, forcing cache rebuild');
|
||||
// Use the targeted rebuild method instead of rebuilding the entire index
|
||||
dailyNotesCache = await this.plugin.fileIndexer.rebuildDailyNotesCache(currentYear, currentMonth);
|
||||
CalendarView.dailyNotesInitialized = true;
|
||||
|
|
@ -897,11 +896,6 @@ export class CalendarView extends ItemView {
|
|||
dailyNotesCache = calendarData.dailyNotes;
|
||||
}
|
||||
|
||||
// Log the number of daily notes found for debugging
|
||||
console.debug(`Found ${dailyNotesCache.size} daily notes for ${currentYear}-${currentMonth+1}`);
|
||||
if (dailyNotesCache.size > 0) {
|
||||
console.debug('Daily notes:', Array.from(dailyNotesCache));
|
||||
}
|
||||
|
||||
// Find all calendar days
|
||||
const calendarDays = this.contentEl.querySelectorAll('.calendar-day');
|
||||
|
|
|
|||
|
|
@ -131,10 +131,8 @@ export class NotesView extends ItemView {
|
|||
|
||||
// Add loading indicator
|
||||
this.loadingIndicator = notesList.createDiv({ cls: 'loading-indicator' });
|
||||
this.loadingIndicator.innerHTML = `
|
||||
<div class="loading-spinner"></div>
|
||||
<div class="loading-text">Loading notes...</div>
|
||||
`;
|
||||
this.loadingIndicator.createDiv({ cls: 'loading-spinner' });
|
||||
this.loadingIndicator.createDiv({ cls: 'loading-text', text: 'Loading notes...' });
|
||||
this.loadingIndicator.addClass('is-hidden');
|
||||
|
||||
// Show loading state
|
||||
|
|
|
|||
|
|
@ -166,7 +166,8 @@ export class TaskListView extends ItemView {
|
|||
'aria-expanded': 'true'
|
||||
}
|
||||
});
|
||||
toggleOrgButton.innerHTML = '<svg viewBox="0 0 24 24" width="16" height="16"><path fill="currentColor" d="M7 10l5 5 5-5z"></path></svg>';
|
||||
const downArrowIcon = this.createSVGIcon('0 0 24 24', 16, 16, 'M7 10l5 5 5-5z');
|
||||
toggleOrgButton.appendChild(downArrowIcon);
|
||||
|
||||
// Add label next to toggle
|
||||
const toggleLabel = primaryFiltersRow.createEl('span', {
|
||||
|
|
@ -194,7 +195,9 @@ export class TaskListView extends ItemView {
|
|||
if (this.plugin.settings.taskOrgFiltersCollapsed) {
|
||||
organizationFiltersRow.addClass('is-hidden');
|
||||
toggleOrgButton.setAttribute('aria-expanded', 'false');
|
||||
toggleOrgButton.innerHTML = '<svg viewBox="0 0 24 24" width="16" height="16"><path fill="currentColor" d="M7 14l5-5 5 5z"></path></svg>';
|
||||
toggleOrgButton.empty();
|
||||
const upArrowIcon = this.createSVGIcon('0 0 24 24', 16, 16, 'M7 14l5-5 5 5z');
|
||||
toggleOrgButton.appendChild(upArrowIcon);
|
||||
}
|
||||
|
||||
// Status filter (moved to collapsible section)
|
||||
|
|
@ -291,10 +294,16 @@ export class TaskListView extends ItemView {
|
|||
});
|
||||
|
||||
// Close dropdown when clicking outside
|
||||
document.addEventListener('click', (e) => {
|
||||
const documentClickHandler = (e: Event) => {
|
||||
if (!contextDropdown.contains(e.target as Node)) {
|
||||
contextMenu.addClass('is-hidden');
|
||||
}
|
||||
};
|
||||
document.addEventListener('click', documentClickHandler);
|
||||
|
||||
// Track for cleanup
|
||||
this.listeners.push(() => {
|
||||
document.removeEventListener('click', documentClickHandler);
|
||||
});
|
||||
|
||||
// Toggle filters visibility
|
||||
|
|
@ -306,9 +315,11 @@ export class TaskListView extends ItemView {
|
|||
organizationFiltersRow.addClass('is-hidden');
|
||||
}
|
||||
toggleOrgButton.setAttribute('aria-expanded', isHidden.toString());
|
||||
toggleOrgButton.innerHTML = isHidden
|
||||
? '<svg viewBox="0 0 24 24" width="16" height="16"><path fill="currentColor" d="M7 10l5 5 5-5z"></path></svg>'
|
||||
: '<svg viewBox="0 0 24 24" width="16" height="16"><path fill="currentColor" d="M7 14l5-5 5 5z"></path></svg>';
|
||||
toggleOrgButton.empty();
|
||||
const arrowIcon = isHidden
|
||||
? this.createSVGIcon('0 0 24 24', 16, 16, 'M7 10l5 5 5-5z')
|
||||
: this.createSVGIcon('0 0 24 24', 16, 16, 'M7 14l5-5 5 5z');
|
||||
toggleOrgButton.appendChild(arrowIcon);
|
||||
|
||||
// Save the collapse state
|
||||
this.plugin.settings.taskOrgFiltersCollapsed = !isHidden;
|
||||
|
|
@ -330,10 +341,8 @@ export class TaskListView extends ItemView {
|
|||
|
||||
// Add loading indicator
|
||||
this.loadingIndicator = taskList.createDiv({ cls: 'loading-indicator' });
|
||||
this.loadingIndicator.innerHTML = `
|
||||
<div class="loading-spinner"></div>
|
||||
<div class="loading-text">Loading tasks...</div>
|
||||
`;
|
||||
this.loadingIndicator.createDiv({ cls: 'loading-spinner' });
|
||||
this.loadingIndicator.createDiv({ cls: 'loading-text', text: 'Loading tasks...' });
|
||||
this.loadingIndicator.addClass('is-hidden');
|
||||
|
||||
// Show loading state if we're fetching data
|
||||
|
|
@ -433,6 +442,33 @@ export class TaskListView extends ItemView {
|
|||
return `${prefix}-${value.replace(/[^a-zA-Z0-9-]/g, '-')}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create SVG icon element safely without innerHTML
|
||||
*/
|
||||
private createSVGIcon(viewBox: string, width: number, height: number, pathData: string): SVGElement {
|
||||
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
||||
svg.setAttribute('viewBox', viewBox);
|
||||
svg.setAttribute('width', width.toString());
|
||||
svg.setAttribute('height', height.toString());
|
||||
|
||||
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
||||
path.setAttribute('fill', 'currentColor');
|
||||
path.setAttribute('d', pathData);
|
||||
|
||||
svg.appendChild(path);
|
||||
return svg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create archive/unarchive icon
|
||||
*/
|
||||
private createArchiveIcon(isArchived: boolean): SVGElement {
|
||||
const unarchiveIcon = 'M20.54 5.23l-1.39-1.68C18.88 3.21 18.47 3 18 3H6c-.47 0-.88.21-1.16.55L3.46 5.23C3.17 5.57 3 6.02 3 6.5V19c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V6.5c0-.48-.17-.93-.46-1.27zM12 17.5L6.5 12H10v-2h4v2h3.5L12 17.5zM5.12 5l.81-1h12l.94 1H5.12z';
|
||||
const archiveIcon = 'M20.54 5.23l-1.39-1.68C18.88 3.21 18.47 3 18 3H6c-.47 0-.88.21-1.16.55L3.46 5.23C3.17 5.57 3 6.02 3 6.5V19c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V6.5c0-.48-.17-.93-.46-1.27zM12 9.5l5.5 5.5H14v2h-4v-2H6.5L12 9.5zM5.12 5l.81-1h12l.94 1H5.12z';
|
||||
|
||||
return this.createSVGIcon('0 0 24 24', 16, 16, isArchived ? unarchiveIcon : archiveIcon);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format group name for display
|
||||
*/
|
||||
|
|
@ -562,7 +598,7 @@ export class TaskListView extends ItemView {
|
|||
}
|
||||
});
|
||||
|
||||
timeIcon.innerHTML = isTracking ? '⏸' : '▶';
|
||||
timeIcon.textContent = isTracking ? '⏸' : '▶';
|
||||
|
||||
timeIcon.addEventListener('click', async (e) => {
|
||||
e.stopPropagation();
|
||||
|
|
@ -654,9 +690,8 @@ export class TaskListView extends ItemView {
|
|||
});
|
||||
|
||||
// Add icon based on archive status
|
||||
archiveButton.innerHTML = task.archived
|
||||
? '<svg viewBox="0 0 24 24" width="16" height="16"><path fill="currentColor" d="M20.54 5.23l-1.39-1.68C18.88 3.21 18.47 3 18 3H6c-.47 0-.88.21-1.16.55L3.46 5.23C3.17 5.57 3 6.02 3 6.5V19c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V6.5c0-.48-.17-.93-.46-1.27zM12 17.5L6.5 12H10v-2h4v2h3.5L12 17.5zM5.12 5l.81-1h12l.94 1H5.12z"></path></svg>'
|
||||
: '<svg viewBox="0 0 24 24" width="16" height="16"><path fill="currentColor" d="M20.54 5.23l-1.39-1.68C18.88 3.21 18.47 3 18 3H6c-.47 0-.88.21-1.16.55L3.46 5.23C3.17 5.57 3 6.02 3 6.5V19c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V6.5c0-.48-.17-.93-.46-1.27zM12 9.5l5.5 5.5H14v2h-4v-2H6.5L12 9.5zM5.12 5l.81-1h12l.94 1H5.12z"></path></svg>';
|
||||
const archiveIcon = this.createArchiveIcon(task.archived);
|
||||
archiveButton.appendChild(archiveIcon);
|
||||
|
||||
// Add event listener for archive toggle
|
||||
archiveButton.addEventListener('click', async (e) => {
|
||||
|
|
@ -724,7 +759,7 @@ export class TaskListView extends ItemView {
|
|||
|
||||
// Apply custom status color
|
||||
if (statusConfig) {
|
||||
statusBadge.style.backgroundColor = statusConfig.color;
|
||||
statusBadge.style.setProperty('background', statusConfig.color, 'important');
|
||||
}
|
||||
|
||||
// Add click handler for status badge (non-recurring tasks only)
|
||||
|
|
@ -1103,7 +1138,7 @@ export class TaskListView extends ItemView {
|
|||
|
||||
// Apply custom status color
|
||||
if (statusConfig) {
|
||||
statusBadge.style.backgroundColor = statusConfig.color;
|
||||
statusBadge.style.setProperty('background', statusConfig.color, 'important');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1129,9 +1164,9 @@ export class TaskListView extends ItemView {
|
|||
if (archiveButton) {
|
||||
archiveButton.className = `archive-button-icon ${updatedTask.archived ? 'archived' : ''}`;
|
||||
archiveButton.title = updatedTask.archived ? 'Unarchive this task' : 'Archive this task';
|
||||
archiveButton.innerHTML = updatedTask.archived
|
||||
? '<svg viewBox="0 0 24 24" width="16" height="16"><path fill="currentColor" d="M20.54 5.23l-1.39-1.68C18.88 3.21 18.47 3 18 3H6c-.47 0-.88.21-1.16.55L3.46 5.23C3.17 5.57 3 6.02 3 6.5V19c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V6.5c0-.48-.17-.93-.46-1.27zM12 17.5L6.5 12H10v-2h4v2h3.5L12 17.5zM5.12 5l.81-1h12l.94 1H5.12z"></path></svg>'
|
||||
: '<svg viewBox="0 0 24 24" width="16" height="16"><path fill="currentColor" d="M20.54 5.23l-1.39-1.68C18.88 3.21 18.47 3 18 3H6c-.47 0-.88.21-1.16.55L3.46 5.23C3.17 5.57 3 6.02 3 6.5V19c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V6.5c0-.48-.17-.93-.46-1.27zM12 9.5l5.5 5.5H14v2h-4v-2H6.5L12 9.5zM5.12 5l.81-1h12l.94 1H5.12z"></path></svg>';
|
||||
archiveButton.empty();
|
||||
const updatedArchiveIcon = this.createArchiveIcon(updatedTask.archived);
|
||||
archiveButton.appendChild(updatedArchiveIcon);
|
||||
}
|
||||
|
||||
// Update archived badge visibility
|
||||
|
|
@ -1204,7 +1239,7 @@ export class TaskListView extends ItemView {
|
|||
const isTracking = !!activeSession;
|
||||
|
||||
timeIcon.className = `time-icon ${isTracking ? 'tracking' : 'idle'}`;
|
||||
timeIcon.innerHTML = isTracking ? '⏸' : '▶';
|
||||
timeIcon.textContent = isTracking ? '⏸' : '▶';
|
||||
timeIcon.setAttribute('aria-label', isTracking ? 'Stop time tracking' : 'Start time tracking');
|
||||
timeIcon.setAttribute('title', isTracking ? 'Stop time tracking' : 'Start time tracking');
|
||||
}
|
||||
|
|
|
|||
126
styles.css
126
styles.css
|
|
@ -2947,6 +2947,132 @@ button.processing::after {
|
|||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
/* Settings tab styling */
|
||||
.settings-tab-nav {
|
||||
display: flex;
|
||||
border-bottom: 1px solid var(--background-modifier-border);
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.settings-tab-button {
|
||||
padding: 8px 16px;
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: var(--text-normal);
|
||||
cursor: pointer;
|
||||
border-radius: 4px 4px 0 0;
|
||||
transition: all var(--cs-transition-fast);
|
||||
}
|
||||
|
||||
.settings-tab-button.active {
|
||||
background: var(--interactive-accent);
|
||||
color: var(--text-on-accent);
|
||||
}
|
||||
|
||||
.settings-tab-button:hover:not(.active) {
|
||||
background: var(--background-modifier-hover);
|
||||
}
|
||||
|
||||
.settings-tab-content {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.settings-tab-content.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* Settings warning box */
|
||||
.settings-warning {
|
||||
padding: 12px;
|
||||
margin-bottom: 20px;
|
||||
background-color: var(--background-modifier-warning);
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--color-orange);
|
||||
}
|
||||
|
||||
/* Settings table */
|
||||
.settings-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.settings-table td {
|
||||
padding: 8px;
|
||||
border-bottom: 1px solid var(--background-modifier-border);
|
||||
}
|
||||
|
||||
.settings-table input {
|
||||
width: 100%;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
/* Status/Priority lists */
|
||||
.settings-list {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.settings-item-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
padding: 12px;
|
||||
border: 1px solid var(--background-modifier-border);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.settings-color-indicator {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 50%;
|
||||
margin-right: 12px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.settings-input {
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.settings-input.value-input {
|
||||
width: 120px;
|
||||
}
|
||||
|
||||
.settings-input.label-input {
|
||||
width: 120px;
|
||||
}
|
||||
|
||||
.settings-input.color-input {
|
||||
width: 40px;
|
||||
}
|
||||
|
||||
.settings-input.weight-input {
|
||||
width: 80px;
|
||||
}
|
||||
|
||||
.settings-checkbox-label {
|
||||
margin-right: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.settings-checkbox-label input {
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.settings-delete-button {
|
||||
margin-left: auto;
|
||||
background-color: var(--interactive-accent);
|
||||
color: var(--text-on-accent);
|
||||
border: none;
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition: all var(--cs-transition-fast);
|
||||
}
|
||||
|
||||
.settings-delete-button:hover {
|
||||
background-color: var(--interactive-accent-hover);
|
||||
}
|
||||
|
||||
.priority-badge {
|
||||
font-size: var(--cs-text-xs);
|
||||
padding: 1px var(--cs-spacing-xs);
|
||||
|
|
|
|||
Loading…
Reference in a new issue