fix: make Todo reminder time selectable
This commit is contained in:
parent
0cd2c2a6ab
commit
f23fb6e993
|
|
@ -18,7 +18,7 @@
|
||||||
'.todo-title{font-size:.86rem;font-weight:600}.todo-count,.todo-status,.todo-scope{font-size:.72rem;color:var(--vt-color-text-muted,#7f8aa3)}.todo-spacer{flex:1}',
|
'.todo-title{font-size:.86rem;font-weight:600}.todo-count,.todo-status,.todo-scope{font-size:.72rem;color:var(--vt-color-text-muted,#7f8aa3)}.todo-spacer{flex:1}',
|
||||||
'.todo-filters{display:flex;align-items:center;gap:.35rem;min-width:0;flex:1;flex-wrap:wrap}',
|
'.todo-filters{display:flex;align-items:center;gap:.35rem;min-width:0;flex:1;flex-wrap:wrap}',
|
||||||
'.todo-input,.todo-select{box-sizing:border-box;min-height:1.9rem;border:1px solid var(--vt-color-border-strong,#2c456a);border-radius:var(--vt-radius-sm,4px);background:var(--vt-color-surface,#15152c);color:var(--vt-color-text-primary,#f4f7fb);color-scheme:dark;font:inherit;font-size:.78rem;padding:.32rem .45rem}',
|
'.todo-input,.todo-select{box-sizing:border-box;min-height:1.9rem;border:1px solid var(--vt-color-border-strong,#2c456a);border-radius:var(--vt-radius-sm,4px);background:var(--vt-color-surface,#15152c);color:var(--vt-color-text-primary,#f4f7fb);color-scheme:dark;font:inherit;font-size:.78rem;padding:.32rem .45rem}',
|
||||||
'.todo-input.search{width:min(15rem,100%)}.todo-input.textarea{min-height:6.5rem;resize:vertical;line-height:1.4}.todo-select{max-width:12rem;appearance:none;background-color:var(--vt-color-surface,#15152c);background-image:linear-gradient(45deg,transparent 50%,var(--vt-color-text-muted,#7f8aa3) 50%),linear-gradient(135deg,var(--vt-color-text-muted,#7f8aa3) 50%,transparent 50%);background-position:calc(100% - 14px) 50%,calc(100% - 9px) 50%;background-size:5px 5px,5px 5px;background-repeat:no-repeat;padding-right:1.7rem}.todo-select option{background:var(--vt-color-surface,#15152c);color:var(--vt-color-text-primary,#f4f7fb)}',
|
'.todo-input.search{width:min(15rem,100%)}.todo-input.textarea{min-height:6.5rem;resize:vertical;line-height:1.4}.todo-select{max-width:12rem;appearance:none;background-color:var(--vt-color-surface,#15152c);background-image:linear-gradient(45deg,transparent 50%,var(--vt-color-text-muted,#7f8aa3) 50%),linear-gradient(135deg,var(--vt-color-text-muted,#7f8aa3) 50%,transparent 50%);background-position:calc(100% - 14px) 50%,calc(100% - 9px) 50%;background-size:5px 5px,5px 5px;background-repeat:no-repeat;padding-right:1.7rem}.todo-select option{background:var(--vt-color-surface,#15152c);color:var(--vt-color-text-primary,#f4f7fb)}.todo-time-picker{display:grid;grid-template-columns:1fr 1fr;gap:.4rem}.todo-time-select{width:100%;max-width:none}',
|
||||||
'.todo-btn{min-height:1.9rem;padding:.32rem .62rem;border:1px solid var(--vt-color-border-strong,#2c456a);border-radius:var(--vt-radius-md,6px);background:var(--vt-color-surface-hover,#1b2440);color:var(--vt-color-text-secondary,#b7c0d4);font-size:.78rem;cursor:pointer}.todo-btn:hover{border-color:var(--vt-color-accent,#4ecca3);color:var(--vt-color-text-primary,#f4f7fb)}.todo-btn.primary{background:var(--vt-color-accent,#4ecca3);border-color:var(--vt-color-accent,#4ecca3);color:#101827}.todo-btn.danger{border-color:rgba(233,69,96,.5);color:#ff9a9a}.todo-btn:disabled{opacity:.45;cursor:default}',
|
'.todo-btn{min-height:1.9rem;padding:.32rem .62rem;border:1px solid var(--vt-color-border-strong,#2c456a);border-radius:var(--vt-radius-md,6px);background:var(--vt-color-surface-hover,#1b2440);color:var(--vt-color-text-secondary,#b7c0d4);font-size:.78rem;cursor:pointer}.todo-btn:hover{border-color:var(--vt-color-accent,#4ecca3);color:var(--vt-color-text-primary,#f4f7fb)}.todo-btn.primary{background:var(--vt-color-accent,#4ecca3);border-color:var(--vt-color-accent,#4ecca3);color:#101827}.todo-btn.danger{border-color:rgba(233,69,96,.5);color:#ff9a9a}.todo-btn:disabled{opacity:.45;cursor:default}',
|
||||||
'.todo-list{flex:1;min-height:0;overflow:auto;padding:.5rem .75rem .85rem}.todo-empty{height:100%;display:flex;align-items:center;justify-content:center;padding:2rem;text-align:center;color:var(--vt-color-text-muted,#7f8aa3);font-size:.86rem}',
|
'.todo-list{flex:1;min-height:0;overflow:auto;padding:.5rem .75rem .85rem}.todo-empty{height:100%;display:flex;align-items:center;justify-content:center;padding:2rem;text-align:center;color:var(--vt-color-text-muted,#7f8aa3);font-size:.86rem}',
|
||||||
'.todo-row{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:.75rem;align-items:start;margin-top:.5rem;padding:.75rem .85rem;border:1px solid var(--vt-color-border,#202b46);border-radius:var(--vt-radius-lg,8px);background:var(--vt-color-surface,#15152c)}.todo-row:hover{background:var(--vt-color-surface-hover,#1b2440)}.todo-row.done .todo-row-title{text-decoration:line-through;color:var(--vt-color-text-muted,#7f8aa3)}',
|
'.todo-row{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:.75rem;align-items:start;margin-top:.5rem;padding:.75rem .85rem;border:1px solid var(--vt-color-border,#202b46);border-radius:var(--vt-radius-lg,8px);background:var(--vt-color-surface,#15152c)}.todo-row:hover{background:var(--vt-color-surface-hover,#1b2440)}.todo-row.done .todo-row-title{text-decoration:line-through;color:var(--vt-color-text-muted,#7f8aa3)}',
|
||||||
|
|
@ -132,11 +132,12 @@
|
||||||
return match ? { date: match[1], time: match[2] } : { date: '', time: '' };
|
return match ? { date: match[1], time: match[2] } : { date: '', time: '' };
|
||||||
}
|
}
|
||||||
|
|
||||||
function joinReminderDateTime(date, time) {
|
function joinReminderDateTime(date, hour, minute) {
|
||||||
date = cleanDate(date);
|
date = cleanDate(date);
|
||||||
time = text(time).trim();
|
hour = text(hour).trim();
|
||||||
if (!date || !/^\d{2}:\d{2}$/.test(time)) return '';
|
minute = text(minute).trim();
|
||||||
return cleanDateTime(date + 'T' + time);
|
if (!date || !/^\d{2}$/.test(hour) || !/^\d{2}$/.test(minute)) return '';
|
||||||
|
return cleanDateTime(date + 'T' + hour + ':' + minute);
|
||||||
}
|
}
|
||||||
|
|
||||||
function todoId(workspaceRoot, title) {
|
function todoId(workspaceRoot, title) {
|
||||||
|
|
@ -329,6 +330,15 @@
|
||||||
return el('option', { value: value, textContent: label });
|
return el('option', { value: value, textContent: label });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function timeOptions(limit, emptyLabel) {
|
||||||
|
var options = [option('', emptyLabel)];
|
||||||
|
for (var value = 0; value < limit; value += 1) {
|
||||||
|
var formatted = String(value).padStart(2, '0');
|
||||||
|
options.push(option(formatted, formatted));
|
||||||
|
}
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
function cleanStatusFilter(value) {
|
function cleanStatusFilter(value) {
|
||||||
value = text(value).trim().toLowerCase();
|
value = text(value).trim().toLowerCase();
|
||||||
return value === 'all' || STATUS_VALUES.indexOf(value) !== -1 ? value : 'all';
|
return value === 'all' || STATUS_VALUES.indexOf(value) !== -1 ? value : 'all';
|
||||||
|
|
@ -448,7 +458,11 @@
|
||||||
var dueInput = el('input', { className: 'todo-input', type: 'date', value: editing ? existingTodo.dueAt : '', 'data-todo-input': 'dueAt' });
|
var dueInput = el('input', { className: 'todo-input', type: 'date', value: editing ? existingTodo.dueAt : '', 'data-todo-input': 'dueAt' });
|
||||||
var reminder = splitReminderDateTime(editing ? existingTodo.reminderAt : '');
|
var reminder = splitReminderDateTime(editing ? existingTodo.reminderAt : '');
|
||||||
var reminderDateInput = el('input', { className: 'todo-input', type: 'date', value: reminder.date, 'data-todo-input': 'reminderDate' });
|
var reminderDateInput = el('input', { className: 'todo-input', type: 'date', value: reminder.date, 'data-todo-input': 'reminderDate' });
|
||||||
var reminderTimeInput = el('input', { className: 'todo-input', type: 'time', value: reminder.time, 'data-todo-input': 'reminderTime' });
|
var reminderTime = reminder.time.split(':');
|
||||||
|
var reminderHourInput = el('select', { className: 'todo-select todo-time-select', 'data-todo-input': 'reminderHour', 'aria-label': tr('ui.field.reminderHour', null, 'Hour') }, timeOptions(24, tr('ui.time.hourPlaceholder', null, 'Hour')));
|
||||||
|
var reminderMinuteInput = el('select', { className: 'todo-select todo-time-select', 'data-todo-input': 'reminderMinute', 'aria-label': tr('ui.field.reminderMinute', null, 'Minute') }, timeOptions(60, tr('ui.time.minutePlaceholder', null, 'Minute')));
|
||||||
|
reminderHourInput.value = reminderTime[0] || '';
|
||||||
|
reminderMinuteInput.value = reminderTime[1] || '';
|
||||||
var workspaceInput = null;
|
var workspaceInput = null;
|
||||||
var workspace = editing ? existingTodo.workspaceRootPath : scope.workspaceRoot;
|
var workspace = editing ? existingTodo.workspaceRootPath : scope.workspaceRoot;
|
||||||
if (scope.mode === 'global') {
|
if (scope.mode === 'global') {
|
||||||
|
|
@ -479,7 +493,7 @@
|
||||||
status: editing ? existingTodo.status : 'open',
|
status: editing ? existingTodo.status : 'open',
|
||||||
priority: priorityInput.value,
|
priority: priorityInput.value,
|
||||||
dueAt: dueInput.value,
|
dueAt: dueInput.value,
|
||||||
reminderAt: joinReminderDateTime(reminderDateInput.value, reminderTimeInput.value),
|
reminderAt: joinReminderDateTime(reminderDateInput.value, reminderHourInput.value, reminderMinuteInput.value),
|
||||||
createdAt: editing ? existingTodo.createdAt : timestamp,
|
createdAt: editing ? existingTodo.createdAt : timestamp,
|
||||||
updatedAt: timestamp,
|
updatedAt: timestamp,
|
||||||
completedAt: editing ? existingTodo.completedAt : '',
|
completedAt: editing ? existingTodo.completedAt : '',
|
||||||
|
|
@ -505,7 +519,7 @@
|
||||||
el('label', { className: 'todo-field' }, [tr('ui.field.priority', null, 'Priority'), priorityInput]),
|
el('label', { className: 'todo-field' }, [tr('ui.field.priority', null, 'Priority'), priorityInput]),
|
||||||
el('label', { className: 'todo-field' }, [tr('ui.field.due', null, 'Due date'), dueInput]),
|
el('label', { className: 'todo-field' }, [tr('ui.field.due', null, 'Due date'), dueInput]),
|
||||||
el('label', { className: 'todo-field' }, [tr('ui.field.reminderDate', null, 'Reminder date'), reminderDateInput]),
|
el('label', { className: 'todo-field' }, [tr('ui.field.reminderDate', null, 'Reminder date'), reminderDateInput]),
|
||||||
el('label', { className: 'todo-field' }, [tr('ui.field.reminderTime', null, 'Reminder time'), reminderTimeInput])
|
el('div', { className: 'todo-field' }, [tr('ui.field.reminderTime', null, 'Reminder time'), el('div', { className: 'todo-time-picker' }, [reminderHourInput, reminderMinuteInput])])
|
||||||
];
|
];
|
||||||
if (workspaceInput) fields.push(el('label', { className: 'todo-field' }, [tr('ui.field.workspace', null, 'Workspace'), workspaceInput]));
|
if (workspaceInput) fields.push(el('label', { className: 'todo-field' }, [tr('ui.field.workspace', null, 'Workspace'), workspaceInput]));
|
||||||
else fields.push(el('div', { className: 'todo-field', textContent: tr('ui.workspaceValue', { workspace: scope.workspaceRoot }, 'Workspace: ' + scope.workspaceRoot) }));
|
else fields.push(el('div', { className: 'todo-field', textContent: tr('ui.workspaceValue', { workspace: scope.workspaceRoot }, 'Workspace: ' + scope.workspaceRoot) }));
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,10 @@
|
||||||
"ui.field.reminder": "Reminder",
|
"ui.field.reminder": "Reminder",
|
||||||
"ui.field.reminderDate": "Reminder date",
|
"ui.field.reminderDate": "Reminder date",
|
||||||
"ui.field.reminderTime": "Reminder time",
|
"ui.field.reminderTime": "Reminder time",
|
||||||
|
"ui.field.reminderHour": "Hour",
|
||||||
|
"ui.field.reminderMinute": "Minute",
|
||||||
|
"ui.time.hourPlaceholder": "Hour",
|
||||||
|
"ui.time.minutePlaceholder": "Minute",
|
||||||
"ui.field.workspace": "Workspace",
|
"ui.field.workspace": "Workspace",
|
||||||
"ui.workspaceValue": "Workspace: {workspace}",
|
"ui.workspaceValue": "Workspace: {workspace}",
|
||||||
"ui.edit": "Edit Todo",
|
"ui.edit": "Edit Todo",
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,10 @@
|
||||||
"ui.field.reminder": "Напоминание",
|
"ui.field.reminder": "Напоминание",
|
||||||
"ui.field.reminderDate": "Дата напоминания",
|
"ui.field.reminderDate": "Дата напоминания",
|
||||||
"ui.field.reminderTime": "Время напоминания",
|
"ui.field.reminderTime": "Время напоминания",
|
||||||
|
"ui.field.reminderHour": "Часы",
|
||||||
|
"ui.field.reminderMinute": "Минуты",
|
||||||
|
"ui.time.hourPlaceholder": "Часы",
|
||||||
|
"ui.time.minutePlaceholder": "Минуты",
|
||||||
"ui.field.workspace": "Рабочее пространство",
|
"ui.field.workspace": "Рабочее пространство",
|
||||||
"ui.workspaceValue": "Рабочее пространство: {workspace}",
|
"ui.workspaceValue": "Рабочее пространство: {workspace}",
|
||||||
"ui.edit": "Изменить задачу",
|
"ui.edit": "Изменить задачу",
|
||||||
|
|
|
||||||
|
|
@ -233,11 +233,14 @@ async function mountWithApi(apiState, props, emittedEvents = [], document = make
|
||||||
byData(container, 'data-todo-input', 'priority').value = 'high';
|
byData(container, 'data-todo-input', 'priority').value = 'high';
|
||||||
byData(container, 'data-todo-input', 'dueAt').value = '01/02/2000';
|
byData(container, 'data-todo-input', 'dueAt').value = '01/02/2000';
|
||||||
const reminderDate = byData(container, 'data-todo-input', 'reminderDate');
|
const reminderDate = byData(container, 'data-todo-input', 'reminderDate');
|
||||||
const reminderTime = byData(container, 'data-todo-input', 'reminderTime');
|
const reminderHour = byData(container, 'data-todo-input', 'reminderHour');
|
||||||
|
const reminderMinute = byData(container, 'data-todo-input', 'reminderMinute');
|
||||||
if (!reminderDate || reminderDate.getAttribute('type') !== 'date') throw new Error('Todo reminder date input was not rendered');
|
if (!reminderDate || reminderDate.getAttribute('type') !== 'date') throw new Error('Todo reminder date input was not rendered');
|
||||||
if (!reminderTime || reminderTime.getAttribute('type') !== 'time') throw new Error('Todo reminder time input was not rendered');
|
if (!reminderHour || reminderHour.tagName !== 'SELECT') throw new Error('Todo reminder hour selector was not rendered');
|
||||||
|
if (!reminderMinute || reminderMinute.tagName !== 'SELECT') throw new Error('Todo reminder minute selector was not rendered');
|
||||||
reminderDate.value = '01/02/2000';
|
reminderDate.value = '01/02/2000';
|
||||||
reminderTime.value = '09:00';
|
reminderHour.value = '09';
|
||||||
|
reminderMinute.value = '30';
|
||||||
byData(container, 'data-todo-action', 'save').click();
|
byData(container, 'data-todo-action', 'save').click();
|
||||||
await flush();
|
await flush();
|
||||||
|
|
||||||
|
|
@ -246,7 +249,7 @@ async function mountWithApi(apiState, props, emittedEvents = [], document = make
|
||||||
const createdTodo = storedAfterCreate[0];
|
const createdTodo = storedAfterCreate[0];
|
||||||
if (createdTodo.workspaceRootPath !== 'Project') throw new Error('workspace Todo did not keep the Project root path');
|
if (createdTodo.workspaceRootPath !== 'Project') throw new Error('workspace Todo did not keep the Project root path');
|
||||||
if (createdTodo.status !== 'open' || createdTodo.priority !== 'high') throw new Error('Todo status or priority was not stored');
|
if (createdTodo.status !== 'open' || createdTodo.priority !== 'high') throw new Error('Todo status or priority was not stored');
|
||||||
if (createdTodo.dueAt !== '2000-01-02' || createdTodo.reminderAt !== '2000-01-02T09:00') throw new Error('Todo due/reminder metadata was not stored');
|
if (createdTodo.dueAt !== '2000-01-02' || createdTodo.reminderAt !== '2000-01-02T09:30') throw new Error('Todo due/reminder metadata was not stored');
|
||||||
if (!container.textContent.includes('Overdue') || !container.textContent.includes('Reminder due')) throw new Error('due/reminder indicators were not rendered');
|
if (!container.textContent.includes('Overdue') || !container.textContent.includes('Reminder due')) throw new Error('due/reminder indicators were not rendered');
|
||||||
const scheduledAfterCreate = apiState.notificationCalls.at(-1) || [];
|
const scheduledAfterCreate = apiState.notificationCalls.at(-1) || [];
|
||||||
if (scheduledAfterCreate.length !== 1
|
if (scheduledAfterCreate.length !== 1
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue