feat: file mobile Drafts sequentially (Closes #629)
This commit is contained in:
1 parent
1079ec6561
commit
7948d2e353
8 files changed
+176
-18
No files matched your search
@@ -211,6 +211,10 @@ textarea { resize: vertical; min-height: 120px; }
|
||||
.create-issue-actions { display:grid; grid-template-columns:repeat(auto-fit,minmax(140px,1fr)); gap:8px; padding-bottom:env(safe-area-inset-bottom); }
|
||||
.create-issue-actions button { min-height:44px; max-width:100%; width:100%; }
|
||||
.create-issue-actions #create-issue-status { grid-column:1/-1; }
|
||||
.draft-filing-session { position:sticky; top:0; z-index:2; display:grid; gap:6px; margin-bottom:12px; padding:10px; padding-bottom:calc(10px + env(safe-area-inset-bottom)); border:1px solid #31577f; border-radius:12px; background:rgba(11,21,38,.98); }
|
||||
.draft-filing-session[hidden] { display:none; }
|
||||
.draft-filing-session-actions { display:grid; grid-template-columns:1fr 1fr; gap:8px; }
|
||||
.draft-filing-session-actions button { min-height:44px; width:100%; }
|
||||
.my-work-card { min-height: 44px; display:grid; gap:8px; padding:12px; border:1px solid #1f3a5f; border-radius:12px; background:#0f1d33; color:var(--text); }
|
||||
.my-work-card-main { display:block; width:100%; color:var(--text); text-align:left; font:inherit; background:transparent; border:0; padding:0; }
|
||||
.my-work-card-main.review-trigger { width:100%; text-align:left; font:inherit; }
|
||||
|
||||
+21
-13
@@ -407,7 +407,13 @@
|
||||
getCaptureLogin: () => String(lastContextSnapshot?.user?.login || '').trim(),
|
||||
getCurrentLogin: () => activeFlushLogin,
|
||||
});
|
||||
let resumedUnfiledCaptureId = '';
|
||||
const dFS = createDraftFilingSession({list:()=>unfiledCaptures.list().filter(item=>!item.quarantined)});
|
||||
dFS.attach(qs, {
|
||||
captures:unfiledCaptures, issueCapture, attachment:createIssueAttachmentController,
|
||||
getLogin:()=>activeFlushLogin, setResumedId:id=>{ rUC = id; },
|
||||
openSheet:openCreateIssueSheet, setFilingMode:setIssueFilingMode,
|
||||
});
|
||||
let rUC = '';
|
||||
let backgroundIssueSync = null;
|
||||
if ('indexedDB' in window) {
|
||||
const backgroundIssueStore = createIssueSyncStore();
|
||||
@@ -2118,12 +2124,8 @@
|
||||
if (!item) return;
|
||||
if (item.kind === 'unfiled-issue') {
|
||||
try {
|
||||
const resumed = await unfiledCaptures.resume(item.capture_id, activeFlushLogin);
|
||||
issueCapture.saveDraft(resumed);
|
||||
resumedUnfiledCaptureId = item.capture_id;
|
||||
await openCreateIssueSheet();
|
||||
if (resumed.attachment) createIssueAttachmentController.restore(resumed.attachment);
|
||||
qs('#create-issue-status').textContent = 'Capture restored. Choose a repository to file it.';
|
||||
dFS.start(item.capture_id);
|
||||
await dFS.nextCapture();
|
||||
} catch (error) { qs('#my-work-action-status').textContent = error.message; }
|
||||
} else if (item.kind === 'new-issue') openCreateIssueSheet();
|
||||
else if (item.route) workRoute.open(item.route);
|
||||
@@ -4053,6 +4055,7 @@
|
||||
}
|
||||
});
|
||||
qs('#new-issue').addEventListener('click', openCreateIssueSheet);
|
||||
dFS.bind();
|
||||
qs('#file-new-issue').addEventListener('click', () => {
|
||||
if (!qs('#create-issue-title').value.trim()) {
|
||||
qs('#create-issue-capture-status').textContent = 'Add a title before filing.';
|
||||
@@ -4073,9 +4076,9 @@
|
||||
};
|
||||
if (showDraftCapacityDialog(unfiledCaptures)) return;
|
||||
const savedCapture = await unfiledCaptures.save(captureDraft);
|
||||
if (resumedUnfiledCaptureId) {
|
||||
await unfiledCaptures.completeResume(resumedUnfiledCaptureId);
|
||||
resumedUnfiledCaptureId = '';
|
||||
if (rUC) {
|
||||
await unfiledCaptures.completeResume(rUC);
|
||||
rUC = '';
|
||||
}
|
||||
issueCapture.clearDraft();
|
||||
qs('#create-issue-title').value = '';
|
||||
@@ -4222,9 +4225,14 @@
|
||||
const admission = editingOutboxId ? await issueOutbox.updateDurably(editingOutboxId, durableDraft) :
|
||||
await issueOutbox.enqueueDurably(durableDraft);
|
||||
const queued = admission.item;
|
||||
if (resumedUnfiledCaptureId && (!durableDraft.attachment || admission.background)) {
|
||||
await unfiledCaptures.completeResume(resumedUnfiledCaptureId);
|
||||
resumedUnfiledCaptureId = '';
|
||||
const fS = dFS.current();
|
||||
if (rUC && (!durableDraft.attachment || admission.background)) {
|
||||
await unfiledCaptures.completeResume(rUC);
|
||||
rUC = '';
|
||||
}
|
||||
if (fS && !rUC && await dFS.advance(fS.id)) {
|
||||
refreshMyWorkView();
|
||||
return;
|
||||
}
|
||||
if (!admission.background) {
|
||||
editingOutboxId = queued.id;
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
function createDraftFilingSession({list}) {
|
||||
let orderedIds = [];
|
||||
let currentId = '';
|
||||
|
||||
function visibleIds() {
|
||||
const available = new Set(list().map(item => item.id));
|
||||
orderedIds = orderedIds.filter(id => available.has(id));
|
||||
for (const item of list()) {
|
||||
if (!orderedIds.includes(item.id)) orderedIds.push(item.id);
|
||||
}
|
||||
return orderedIds;
|
||||
}
|
||||
|
||||
function state() {
|
||||
const ids = visibleIds();
|
||||
if (!ids.length) {
|
||||
currentId = '';
|
||||
return null;
|
||||
}
|
||||
if (!ids.includes(currentId)) currentId = ids[0];
|
||||
const index = ids.indexOf(currentId);
|
||||
return {id:currentId, position:index + 1, total:ids.length, remaining:ids.length - index - 1};
|
||||
}
|
||||
|
||||
function start(id) {
|
||||
orderedIds = list().map(item => item.id);
|
||||
if (!orderedIds.includes(id)) throw new Error('This capture is no longer available.');
|
||||
currentId = id;
|
||||
return state();
|
||||
}
|
||||
|
||||
function move(offset) {
|
||||
const ids = visibleIds();
|
||||
if (!ids.length) return state();
|
||||
const index = Math.max(0, ids.indexOf(currentId));
|
||||
currentId = ids[(index + offset + ids.length) % ids.length];
|
||||
return state();
|
||||
}
|
||||
|
||||
function removeCurrentAndNext(removedId) {
|
||||
const previousIds = orderedIds.slice();
|
||||
const removedIndex = Math.max(0, previousIds.indexOf(removedId));
|
||||
orderedIds = previousIds.filter(id => id !== removedId);
|
||||
const ids = visibleIds();
|
||||
if (!ids.length) return state();
|
||||
currentId = ids[Math.min(removedIndex, ids.length - 1)];
|
||||
return state();
|
||||
}
|
||||
|
||||
const session = {start, current:state, next:()=>move(1), previous:()=>move(-1), removeCurrentAndNext};
|
||||
session.attach = (qs, dependencies) => {
|
||||
const openCapture = async captureId => {
|
||||
const resumed = await dependencies.captures.resume(captureId, dependencies.getLogin());
|
||||
dependencies.issueCapture.saveDraft(resumed);
|
||||
dependencies.setResumedId(captureId);
|
||||
await dependencies.openSheet();
|
||||
dependencies.attachment[resumed.attachment ? 'restore' : 'clear'](resumed.attachment);
|
||||
dependencies.setFilingMode(true);
|
||||
session.render();
|
||||
};
|
||||
return Object.assign(session, {
|
||||
nextCapture() { const state = session.current(); return state ? openCapture(state.id) : null; },
|
||||
render() {
|
||||
const state = session.current();
|
||||
qs('#draft-filing-session').hidden = !state;
|
||||
if (!state) return;
|
||||
qs('#draft-filing-progress').textContent = `Draft ${state.position} of ${state.total}`;
|
||||
qs('#draft-filing-remaining').textContent = state.remaining ? `${state.remaining} remaining` : 'Last Draft';
|
||||
qs('#previous-draft').disabled = qs('#skip-draft').disabled = state.total < 2;
|
||||
qs('#submit-new-issue').textContent = state.remaining ? 'File & next' : 'File final Draft';
|
||||
},
|
||||
bind() {
|
||||
[['#previous-draft','previous'],['#skip-draft','next']].forEach(([selector, method]) =>
|
||||
qs(selector).addEventListener('click', async () => {
|
||||
const state = session[method]();
|
||||
if (state) await openCapture(state.id);
|
||||
})
|
||||
);
|
||||
},
|
||||
async advance(completedId) {
|
||||
const state = session.removeCurrentAndNext(completedId);
|
||||
if (state) { await openCapture(state.id); return true; }
|
||||
qs('#draft-filing-session').hidden = true;
|
||||
qs('#submit-new-issue').textContent = 'Create & assign to me';
|
||||
return false;
|
||||
},
|
||||
});
|
||||
};
|
||||
return session;
|
||||
}
|
||||
|
||||
if (typeof module !== 'undefined' && module.exports) module.exports = createDraftFilingSession;
|
||||
@@ -550,6 +550,14 @@
|
||||
<div class="small" id="create-issue-attachment-status" aria-live="polite"></div>
|
||||
</section>
|
||||
<section class="create-issue-filing" id="create-issue-filing" hidden>
|
||||
<aside class="draft-filing-session" id="draft-filing-session" aria-live="polite" hidden>
|
||||
<strong id="draft-filing-progress"></strong>
|
||||
<span class="small" id="draft-filing-remaining"></span>
|
||||
<div class="draft-filing-session-actions">
|
||||
<button id="previous-draft" type="button">Previous</button>
|
||||
<button id="skip-draft" type="button">Skip</button>
|
||||
</div>
|
||||
</aside>
|
||||
<div class="create-issue-repository-picker">
|
||||
<label for="create-issue-repository-search">Repository search
|
||||
<input id="create-issue-repository-search" type="search" maxlength="80" placeholder="Search accessible repositories" autocomplete="off" />
|
||||
@@ -870,6 +878,7 @@
|
||||
<script src="static/widgets.js"></script>
|
||||
<script src="static/drafts.js"></script>
|
||||
<script src="static/unfiled-captures.js"></script>
|
||||
<script src="static/draft-filing-session.js"></script>
|
||||
<script src="static/draft-capacity-dialog.js"></script>
|
||||
<script src="static/outbox-coordinator.js"></script>
|
||||
<script src="static/background-issue-sync.js"></script>
|
||||
|
||||
@@ -21,6 +21,7 @@ const SHELL = [
|
||||
BASE + 'static/widgets.js',
|
||||
BASE + 'static/drafts.js',
|
||||
BASE + 'static/unfiled-captures.js',
|
||||
BASE + 'static/draft-filing-session.js',
|
||||
BASE + 'static/draft-capacity-dialog.js',
|
||||
BASE + 'static/outbox-coordinator.js',
|
||||
BASE + 'static/issue-outbox.js',
|
||||
|
||||
Reference in new issue
Block a user