refactor(timer): redesign timer UI with text-based interface

Replace complex button-based UI with simple text links for better
integration and cleaner appearance. Improve subtask detection logic
to only show timers on actual parent tasks.

- Replace buttons with underlined text links for all actions
- Remove complex CSS styling in favor of minimal text styling
- Fix subtask detection to stop at same/lower indentation levels
- Update real-time display to refresh every second
- Add click event propagation prevention
- Simplify UI to show "⏱ time | action | action" format
- Remove 280+ lines of unnecessary CSS styling
This commit is contained in:
Quorafind 2025-07-28 23:00:37 +08:00
parent a05f49f9f7
commit 0caa51fd54
2 changed files with 79 additions and 406 deletions

View file

@ -65,94 +65,69 @@ class TaskTimerWidget extends WidgetType {
return this.dom;
}
this.dom = createDiv({ cls: `task-timer-widget ${this.timerState?.status || 'idle'}` });
this.createTimeDisplay(this.dom);
this.createButtons(this.dom);
// Create a simple text-based widget
this.dom = createDiv({ cls: 'task-timer-widget' });
this.dom.style.cssText = 'margin: 2px 0; font-size: 0.9em; color: var(--text-muted);';
this.updateTimerState();
this.createContent();
return this.dom;
}
/**
* Create time display area
* Create content based on timer state
*/
private createTimeDisplay(container: HTMLElement): void {
const timeSpan = container.createSpan({ cls: "task-timer-display" });
this.updateTimerState();
this.refreshTimeDisplay();
}
/**
* Create action buttons
*/
private createButtons(container: HTMLElement): void {
const buttonContainer = container.createDiv({ cls: "task-timer-buttons" });
private createContent(): void {
if (!this.dom) return;
this.updateTimerState();
this.dom.empty();
if (!this.timerState || this.timerState.status === 'idle') {
const startButton = buttonContainer.createEl("button", {
cls: "task-timer-button start",
text: "Start Task"
});
startButton.addEventListener("click", (e) => {
// Create text-style start button
const startSpan = this.dom.createSpan();
startSpan.style.cssText = 'cursor: pointer; text-decoration: underline; color: var(--text-accent);';
startSpan.setText('Start Task');
startSpan.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
this.startTimer();
});
} else if (this.timerState.status === 'running') {
const pauseButton = buttonContainer.createEl("button", {
cls: "task-timer-button pause",
text: "Pause"
});
pauseButton.addEventListener("click", (e) => {
e.preventDefault();
this.pauseTimer();
});
const resetButton = buttonContainer.createEl("button", {
cls: "task-timer-button reset",
text: "Reset"
});
resetButton.addEventListener("click", (e) => {
e.preventDefault();
this.resetTimer();
});
const completeButton = buttonContainer.createEl("button", {
cls: "task-timer-button complete",
text: "Complete"
});
completeButton.addEventListener("click", (e) => {
e.preventDefault();
this.completeTimer();
});
} else if (this.timerState.status === 'paused') {
const resumeButton = buttonContainer.createEl("button", {
cls: "task-timer-button resume",
text: "Resume"
});
resumeButton.addEventListener("click", (e) => {
e.preventDefault();
this.resumeTimer();
});
const resetButton = buttonContainer.createEl("button", {
cls: "task-timer-button reset",
text: "Reset"
});
resetButton.addEventListener("click", (e) => {
e.preventDefault();
this.resetTimer();
});
const completeButton = buttonContainer.createEl("button", {
cls: "task-timer-button complete",
text: "Complete"
});
completeButton.addEventListener("click", (e) => {
e.preventDefault();
this.completeTimer();
});
} else {
// Show elapsed time
const elapsedMs = Date.now() - this.timerState.startTime + (this.timerState.elapsed || 0);
const formattedTime = TaskTimerFormatter.formatDuration(elapsedMs, this.settings.timeFormat);
const timeSpan = this.dom.createSpan();
timeSpan.setText(`${formattedTime} `);
// Add action links
if (this.timerState.status === 'running') {
this.addActionLink('Pause', () => this.pauseTimer());
this.dom.appendText(' | ');
this.addActionLink('Complete', () => this.completeTimer());
} else if (this.timerState.status === 'paused') {
this.addActionLink('Resume', () => this.resumeTimer());
this.dom.appendText(' | ');
this.addActionLink('Complete', () => this.completeTimer());
}
this.dom.appendText(' | ');
this.addActionLink('Reset', () => this.resetTimer());
}
}
/**
* Add clickable action link
*/
private addActionLink(text: string, action: () => void): void {
if (!this.dom) return;
const link = this.dom.createSpan();
link.style.cssText = 'cursor: pointer; text-decoration: underline; color: var(--text-accent);';
link.setText(text);
link.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
action();
});
}
/**
* Start timer
@ -340,8 +315,8 @@ class TaskTimerWidget extends WidgetType {
}
this.updateInterval = window.setInterval(() => {
this.refreshTimeDisplay();
}, 5000); // Update every 5 seconds for performance
this.createContent(); // Update the entire content
}, 1000); // Update every second
}
/**
@ -354,51 +329,14 @@ class TaskTimerWidget extends WidgetType {
}
}
/**
* Refresh only the time display (performance optimization)
*/
private refreshTimeDisplay(): void {
if (!this.dom) return;
const timeDisplay = this.dom.querySelector('.task-timer-display') as HTMLElement;
if (!timeDisplay) return;
this.updateTimerState();
if (this.timerState) {
const elapsedMs = Date.now() - this.timerState.startTime;
const formattedTime = TaskTimerFormatter.formatDuration(elapsedMs, this.settings.timeFormat);
// Only update if the text has changed
if (timeDisplay.textContent !== formattedTime) {
timeDisplay.textContent = formattedTime;
}
// Update animation class
if (this.timerState.status === 'running') {
timeDisplay.addClass('running');
} else {
timeDisplay.removeClass('running');
}
} else {
timeDisplay.textContent = "00:00";
timeDisplay.removeClass('running');
}
}
/**
* Refresh the entire UI (used when state changes significantly)
*/
private refreshUI(): void {
if (!this.dom) return;
// Update widget class
this.dom.className = `task-timer-widget ${this.timerState?.status || 'idle'}`;
// Clear and recreate content
this.dom.empty();
this.createTimeDisplay(this.dom);
this.createButtons(this.dom);
this.updateTimerState();
this.createContent();
}
destroy() {
@ -536,18 +474,33 @@ function hasSubTasks(rangeText: string, parentLineText: string): boolean {
const parentMatch = parentLineText.match(/^(\s*)/);
const parentIndent = parentMatch ? parentMatch[1].length : 0;
console.log(`[TaskTimer] Checking subtasks for parent with indent ${parentIndent}`);
// Check subsequent lines for subtasks
let foundSubtask = false;
for (let i = 1; i < lines.length; i++) {
const line = lines[i];
if (isTaskLine(line)) {
const lineMatch = line.match(/^(\s*)/);
const lineIndent = lineMatch ? lineMatch[1].length : 0;
if (lineIndent > parentIndent) {
return true; // Found a subtask with greater indentation
}
// Skip empty lines
if (!line.trim()) continue;
const lineMatch = line.match(/^(\s*)/);
const lineIndent = lineMatch ? lineMatch[1].length : 0;
// If we find a line with same or less indentation that's not empty, stop checking
if (lineIndent <= parentIndent) {
break;
}
// Check if this indented line is a task
if (isTaskLine(line) && lineIndent > parentIndent) {
console.log(`[TaskTimer] Found subtask: ${line.trim()}`);
foundSubtask = true;
break;
}
}
return false;
return foundSubtask;
}
function extractBlockRef(lineText: string): string | undefined {

View file

@ -1,291 +1,11 @@
/* Task Timer Widget Styles */
/* Task Timer Widget Styles - Simple Text-Based */
.task-timer-widget {
display: flex;
align-items: center;
gap: 8px;
padding: 4px 8px;
margin: 2px 0;
background: var(--background-secondary);
border: 1px solid var(--background-modifier-border);
border-radius: 4px;
padding: 0;
font-size: 0.9em;
font-family: var(--font-interface);
color: var(--text-muted);
line-height: 1.4;
width: 100%;
box-sizing: border-box;
transition: all 0.2s ease;
}
.task-timer-widget:hover {
background: var(--background-modifier-hover);
border-color: var(--background-modifier-border-hover);
}
/* Button group container */
.task-timer-buttons {
display: flex;
align-items: center;
gap: 6px;
flex-wrap: wrap;
}
/* Individual button styles */
.task-timer-button {
display: inline-flex;
align-items: center;
padding: 2px 6px;
border: 1px solid var(--background-modifier-border);
border-radius: 3px;
background: var(--background-primary);
color: var(--text-normal);
font-size: 0.85em;
cursor: pointer;
user-select: none;
transition: all 0.15s ease;
white-space: nowrap;
}
.task-timer-button:hover {
background: var(--background-modifier-hover);
border-color: var(--background-modifier-border-hover);
color: var(--text-normal);
}
.task-timer-button:active {
background: var(--background-modifier-active);
transform: translateY(1px);
}
/* Specific button types */
.task-timer-button.start {
background: var(--color-green);
color: var(--text-on-accent);
border-color: var(--color-green);
}
.task-timer-button.start:hover {
background: var(--color-green-hover, var(--color-green));
opacity: 0.9;
}
.task-timer-button.pause {
background: var(--color-orange);
color: var(--text-on-accent);
border-color: var(--color-orange);
}
.task-timer-button.pause:hover {
background: var(--color-orange-hover, var(--color-orange));
opacity: 0.9;
}
.task-timer-button.resume {
background: var(--color-blue);
color: var(--text-on-accent);
border-color: var(--color-blue);
}
.task-timer-button.resume:hover {
background: var(--color-blue-hover, var(--color-blue));
opacity: 0.9;
}
.task-timer-button.reset {
background: var(--color-yellow);
color: var(--text-on-accent);
border-color: var(--color-yellow);
}
.task-timer-button.reset:hover {
background: var(--color-yellow-hover, var(--color-yellow));
opacity: 0.9;
}
.task-timer-button.complete {
background: var(--color-purple);
color: var(--text-on-accent);
border-color: var(--color-purple);
}
.task-timer-button.complete:hover {
background: var(--color-purple-hover, var(--color-purple));
opacity: 0.9;
}
/* Time display area */
.task-timer-display {
display: inline-flex;
align-items: center;
padding: 2px 6px;
background: var(--background-primary-alt);
border-radius: 3px;
font-family: var(--font-monospace);
font-size: 0.9em;
color: var(--text-normal);
min-width: 60px;
justify-content: center;
}
/* Status-specific styles */
.task-timer-widget.running {
border-color: var(--color-green);
box-shadow: 0 0 0 1px var(--color-green-transparent, rgba(46, 160, 67, 0.2));
}
.task-timer-widget.running .task-timer-display {
color: var(--color-green);
background: var(--color-green-transparent, rgba(46, 160, 67, 0.1));
}
.task-timer-widget.paused {
border-color: var(--color-orange);
box-shadow: 0 0 0 1px var(--color-orange-transparent, rgba(233, 151, 63, 0.2));
}
.task-timer-widget.paused .task-timer-display {
color: var(--color-orange);
background: var(--color-orange-transparent, rgba(233, 151, 63, 0.1));
}
/* Animation for running timer */
.task-timer-display.running {
animation: pulse-timer 2s ease-in-out infinite;
}
@keyframes pulse-timer {
0%, 100% {
opacity: 1;
}
50% {
opacity: 0.7;
}
}
/* Compact mode for smaller screens */
.task-timer-widget.compact {
padding: 2px 4px;
font-size: 0.8em;
}
.task-timer-widget.compact .task-timer-button {
padding: 1px 4px;
font-size: 0.75em;
}
.task-timer-widget.compact .task-timer-display {
padding: 1px 4px;
font-size: 0.8em;
min-width: 50px;
}
/* Dark theme adjustments */
.theme-dark .task-timer-widget {
background: var(--background-secondary-alpha);
border-color: var(--background-modifier-border);
}
.theme-dark .task-timer-button {
background: var(--background-primary-alt);
border-color: var(--background-modifier-border);
}
.theme-dark .task-timer-display {
background: var(--background-modifier-form-field);
}
/* High contrast mode */
@media (prefers-contrast: high) {
.task-timer-widget {
border-width: 2px;
border-color: var(--text-normal);
}
.task-timer-button {
border-width: 2px;
font-weight: 600;
}
}
/* Mobile responsive adjustments */
@media (max-width: 768px) {
.task-timer-widget {
flex-wrap: wrap;
font-size: 0.85em;
}
.task-timer-buttons {
gap: 4px;
}
.task-timer-button {
padding: 3px 6px;
min-height: 28px;
font-size: 0.8em;
}
}
/* Print styles */
@media print {
.task-timer-widget {
display: none !important;
}
}
/* Accessibility improvements */
.task-timer-button:focus {
outline: 2px solid var(--color-accent);
outline-offset: 2px;
}
.task-timer-button:focus:not(:focus-visible) {
outline: none;
}
/* Screen reader only text */
.task-timer-sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
/* Loading/processing state */
.task-timer-widget.processing {
pointer-events: none;
opacity: 0.6;
}
.task-timer-widget.processing .task-timer-button {
cursor: not-allowed;
}
/* Error state */
.task-timer-widget.error {
border-color: var(--color-red);
background: var(--color-red-transparent, rgba(233, 49, 71, 0.1));
}
.task-timer-widget.error .task-timer-display {
color: var(--color-red);
}
/* Icon support for buttons */
.task-timer-button .task-timer-icon {
width: 12px;
height: 12px;
margin-right: 3px;
flex-shrink: 0;
}
.task-timer-button.compact .task-timer-icon {
width: 10px;
height: 10px;
margin-right: 2px;
}
/* Remove all complex button styles - we're using text links now */