diff --git a/README.md b/README.md
index 6430e91..d2fda8e 100644
--- a/README.md
+++ b/README.md
@@ -212,7 +212,9 @@ the device safe area, and moves out of the way while a full-screen task is open.
Desktop layout is unchanged.
My Work also has an account-synced **Later** queue. **Later today** defers an item for four
-hours, while **Tomorrow** returns it at 09:00 in the device's local timezone.
+hours, while **Tomorrow** returns it at 09:00 in the device's local timezone. **Choose date & time**
+accepts a valid future local date and time and returns the item at that exact instant; the picker
+shows the device timezone and rejects empty, normalized, invalid, or past values before saving.
Deferred items leave normal and Attention queues without marking notifications read
or changing any Gitea issue or pull request. They automatically return to their
existing priority position at the wake time, and **Bring back now** restores them
diff --git a/frontend/dashboard.css b/frontend/dashboard.css
index 1d1d142..0084993 100644
--- a/frontend/dashboard.css
+++ b/frontend/dashboard.css
@@ -109,6 +109,14 @@ textarea { resize: vertical; min-height: 120px; }
.detail-defer summary, .detail-defer button { min-height:44px; display:flex; align-items:center; justify-content:center; }
.detail-defer summary { cursor:pointer; border:1px solid #60a5fa; border-radius:10px; font-weight:700; }
.detail-defer-options { display:grid; grid-template-columns:repeat(auto-fit,minmax(120px,1fr)); gap:8px; margin-top:8px; }
+.later-picker { box-sizing:border-box; width:100%; height:100%; max-width:none; max-height:none; margin:0; padding:0; border:0; position:fixed; inset:0; z-index:90; display:flex; align-items:flex-end; justify-content:center; background:rgba(5,12,21,.78); backdrop-filter:blur(4px); }
+.later-picker[hidden] { display:none; }
+.later-picker-panel { box-sizing:border-box; width:min(560px,100%); max-height:100%; overflow:auto; display:grid; gap:12px; padding:18px; padding-bottom:calc(18px + env(safe-area-inset-bottom)); border:1px solid #2a496e; border-radius:18px 18px 0 0; background:#0b1526; }
+.later-picker-header { display:flex; align-items:center; justify-content:space-between; gap:12px; }
+.later-picker-header h2 { margin:0; }
+.later-picker input, .later-picker button { box-sizing:border-box; width:100%; min-height:44px; }
+.later-picker input { padding:10px; border:1px solid #2a496e; border-radius:10px; color:var(--text); background:#08111f; color-scheme:dark; font:inherit; }
+.later-picker-error { min-height:1.4em; color:#fca5a5; }
.review-sheet-actions { position:sticky; bottom:0; z-index:3; padding:10px 4px; padding-bottom:calc(10px + env(safe-area-inset-bottom)); background:rgba(11,21,38,.98); border-top:1px solid #2a496e; }
.mark-update-read { min-height:44px; width:100%; }
.read-update { min-height:44px; width:100%; display:flex; align-items:center; justify-content:center; }
diff --git a/frontend/dashboard.js b/frontend/dashboard.js
index 888dbcb..2b9ca83 100644
--- a/frontend/dashboard.js
+++ b/frontend/dashboard.js
@@ -696,6 +696,54 @@
announce: message => { qs('#my-work-action-status').textContent = message; },
formatTime: fmt,
});
+ const laterPickerElement = qs('#later-picker');
+ const laterPickerInput = qs('#later-picker-time');
+ const laterPicker = createLaterPicker({
+ history: window.history,
+ eventTarget: window,
+ onState: state => {
+ qs('#later-picker-error').textContent = state.message || '';
+ if (state.open) {
+ laterPickerInput.value = state.value || laterPickerInput.value;
+ qs('#later-picker-timezone').textContent = 'Times use ' +
+ (Intl.DateTimeFormat().resolvedOptions().timeZone || 'your device timezone') + '.';
+ laterPickerElement.hidden = false;
+ if (!laterPickerElement.open) laterPickerElement.showModal();
+ laterPickerInput.focus();
+ } else {
+ if (laterPickerElement.open) laterPickerElement.close();
+ laterPickerElement.hidden = true;
+ }
+ },
+ onConfirm: (item, until, context) => {
+ if (context === 'detail') {
+ const inSession = workSession.active();
+ const deferred = detailDefer.deferUntil(item, until, {
+ closeSheet:false,
+ restoreFocus:false,
+ });
+ if (!deferred) return false;
+ return inSession ? true : () => workRoute.close();
+ }
+ const result = laterWork.defer(item, until);
+ qs('#my-work-action-status').textContent = result === 'deferred' ?
+ 'Deferred until ' + fmt(until) + '; work stays unread and unchanged in Gitea.' :
+ (result === 'invalid' ? 'Choose a valid future time.' : 'Could not save Later on this device.');
+ if (result !== 'deferred') return false;
+ refreshMyWorkView();
+ return true;
+ },
+ });
+ laterPicker.start();
+ qs('#later-picker-form').addEventListener('submit', event => {
+ event.preventDefault();
+ laterPicker.submit(laterPickerInput.value);
+ });
+ qs('#cancel-later-picker').addEventListener('click', () => laterPicker.close());
+ laterPickerElement.addEventListener('cancel', event => {
+ event.preventDefault();
+ laterPicker.close();
+ });
document.querySelectorAll('[data-detail-defer-preset]').forEach(button => {
button.addEventListener('click', () => {
const item = selectedUpdate || selectedReview || selectedIssue || selectedPull;
@@ -703,6 +751,13 @@
detailDefer.defer(item, button.dataset.detailDeferPreset);
});
});
+ document.querySelectorAll('[data-detail-defer-custom]').forEach(button => {
+ button.addEventListener('click', () => {
+ const item = selectedUpdate || selectedReview || selectedIssue || selectedPull;
+ button.closest('.detail-defer').open = false;
+ laterPicker.open(item, button, 'detail');
+ });
+ });
document.querySelectorAll('[data-detail-defer-cancel]').forEach(button => {
button.addEventListener('click', () => {
const chooser = button.closest('.detail-defer');
@@ -712,7 +767,7 @@
});
function updatePlanningAvailability() {
- document.querySelectorAll('[data-detail-defer-preset]').forEach(button => {
+ document.querySelectorAll('[data-detail-defer-preset], [data-detail-defer-custom]').forEach(button => {
button.disabled = !planningOwnerLogin;
button.toggleAttribute('data-planning-disabled', !planningOwnerLogin);
});
@@ -1045,7 +1100,7 @@
const planningDisabled = planningOwnerLogin ? '' : ' disabled data-planning-disabled';
const laterActions = selectedWorkFilter === 'later' ?
'
' :
- '';
+ '';
const alreadyToday = todayWork.contains(item);
const todayPosition = todayWork.position(item);
const todayActions = selectedWorkFilter === 'today' ?
@@ -1113,6 +1168,17 @@
refreshMyWorkView();
});
});
+ document.querySelectorAll('[data-later-custom]').forEach(button => {
+ button.addEventListener('click', () => {
+ const item = lastMyWork[Number(button.dataset.workIndex)];
+ if (!item) return;
+ if (!planningOwnerLogin) {
+ qs('#my-work-action-status').textContent = 'Planning is unavailable until your operator identity is restored.';
+ return;
+ }
+ laterPicker.open(item, button, 'card');
+ });
+ });
document.querySelectorAll('[data-later-restore]').forEach(button => {
button.addEventListener('click', () => {
const item = lastMyWork[Number(button.dataset.workIndex)];
diff --git a/frontend/detail-defer.js b/frontend/detail-defer.js
index 7712370..b7a3ebd 100644
--- a/frontend/detail-defer.js
+++ b/frontend/detail-defer.js
@@ -8,21 +8,23 @@ function createDetailDefer({
formatTime = value => new Date(value).toLocaleString(),
}) {
return {
- defer(item, preset) {
+ deferUntil(item, until, { closeSheet = true, restoreFocus = true } = {}) {
if (!item) return false;
- const until = laterWork.presetUntil(preset);
const result = laterWork.defer(item, until);
if (result !== 'deferred') {
announce(result === 'invalid' ? 'Choose a valid future time.' : 'Could not save Later on this device.');
return false;
}
const inSession = session.active();
- if (!inSession) close();
+ if (!inSession && closeSheet) close();
refresh();
announce('Deferred until ' + formatTime(until) + '. It stays unread and unchanged in Gitea.');
- if (!inSession) focus();
+ if (!inSession && restoreFocus) focus();
return true;
},
+ defer(item, preset) {
+ return this.deferUntil(item, laterWork.presetUntil(preset));
+ },
};
}
diff --git a/frontend/index.html b/frontend/index.html
index 7cb13bd..922049c 100644
--- a/frontend/index.html
+++ b/frontend/index.html
@@ -294,7 +294,7 @@
Open in Gitea
- Defer
+ Defer