183 lines
6.0 KiB
JavaScript
183 lines
6.0 KiB
JavaScript
function createProgressiveCapture({
|
||
document,
|
||
storage,
|
||
getLogin = () => '',
|
||
createCaptures = globalThis.createUnfiledCaptures,
|
||
requestFullWorkspace = async () => {
|
||
const lifecycle = await globalThis.stackchainWorkspaceLifecycle;
|
||
return lifecycle?.optionalReady;
|
||
},
|
||
createId,
|
||
now,
|
||
}) {
|
||
const button = document.querySelector('[data-mobile-task="new"]');
|
||
const root = document.querySelector('#create-issue-sheet');
|
||
const title = document.querySelector('#create-issue-title');
|
||
const body = document.querySelector('#create-issue-body');
|
||
const heading = document.querySelector('#create-issue-heading');
|
||
const save = document.querySelector('#save-unfiled-issue');
|
||
const file = document.querySelector('#file-new-issue');
|
||
const cancel = document.querySelector('#cancel-new-issue');
|
||
const status = document.querySelector('#my-work-action-status');
|
||
const captures = createCaptures({
|
||
storage,
|
||
getCaptureLogin:getLogin,
|
||
getCurrentLogin:getLogin,
|
||
...(createId ? {createId} : {}),
|
||
...(now ? {now} : {}),
|
||
});
|
||
const listeners = [];
|
||
let started = false;
|
||
let saving = null;
|
||
let saved = false;
|
||
let handedOff = false;
|
||
const checkpointKey = 'stackchain.progressive-capture.v1';
|
||
|
||
function listen(element, name, callback) {
|
||
element?.addEventListener?.(name, callback);
|
||
listeners.push([element, name, callback]);
|
||
}
|
||
|
||
function close() {
|
||
root?.classList.remove('open');
|
||
}
|
||
|
||
function readCheckpoints() {
|
||
try {
|
||
const record = JSON.parse(storage?.getItem(checkpointKey) || 'null');
|
||
if (record?.version !== 1 || !Array.isArray(record.items)) return [];
|
||
return record.items.filter(item => item && typeof item.ownerLogin === 'string' &&
|
||
typeof item.title === 'string' && typeof item.body === 'string');
|
||
} catch (_error) { return []; }
|
||
}
|
||
|
||
function readCheckpoint() {
|
||
const login = String(getLogin() || '').trim();
|
||
if (!login) return null;
|
||
return readCheckpoints().find(item => item.ownerLogin === login) || null;
|
||
}
|
||
|
||
function checkpoint() {
|
||
const ownerLogin = String(getLogin() || '').trim();
|
||
if (!ownerLogin) return false;
|
||
try {
|
||
const items = readCheckpoints().filter(item => item.ownerLogin !== ownerLogin);
|
||
items.push({
|
||
ownerLogin,
|
||
title:String(title?.value || '').slice(0, 255),
|
||
body:String(body?.value || '').slice(0, 10000),
|
||
});
|
||
storage?.setItem(checkpointKey, JSON.stringify({
|
||
version:1, items,
|
||
}));
|
||
return true;
|
||
} catch (_error) { return false; }
|
||
}
|
||
|
||
function clearCheckpoint(expected) {
|
||
const current = readCheckpoint();
|
||
if (!current || current.title !== expected.title || current.body !== expected.body) return false;
|
||
try {
|
||
const ownerLogin = String(getLogin() || '').trim();
|
||
const items = readCheckpoints().filter(item => item.ownerLogin !== ownerLogin);
|
||
if (items.length) storage?.setItem(checkpointKey, JSON.stringify({version:1, items}));
|
||
else storage?.removeItem(checkpointKey);
|
||
return true;
|
||
} catch (_error) { return false; }
|
||
}
|
||
|
||
function open(event) {
|
||
saved = false;
|
||
const recovered = readCheckpoint();
|
||
if (recovered) {
|
||
if (title) title.value = recovered.title;
|
||
if (body) body.value = recovered.body;
|
||
if (status) status.textContent = 'Unfinished capture restored from this phone.';
|
||
}
|
||
if (heading) heading.textContent = 'Capture work';
|
||
root?.classList.add('open');
|
||
title?.focus?.();
|
||
}
|
||
|
||
async function saveDraft() {
|
||
if (saved || saving) return saving;
|
||
save.disabled = true;
|
||
const draft = {title:title?.value || '', body:body?.value || ''};
|
||
saving = Promise.resolve().then(() => captures.save(draft)).then(() => {
|
||
saved = true;
|
||
clearCheckpoint(draft);
|
||
if (title) title.value = '';
|
||
if (body) body.value = '';
|
||
close();
|
||
if (status) status.textContent = 'Saved to Drafts. Choose a repository when you’re ready to file it.';
|
||
return true;
|
||
}).catch(error => {
|
||
if (status) status.textContent = error.message;
|
||
title?.focus?.();
|
||
return false;
|
||
}).finally(() => {
|
||
save.disabled = false;
|
||
saving = null;
|
||
});
|
||
return saving;
|
||
}
|
||
|
||
async function fileNow() {
|
||
if (file.disabled) return false;
|
||
file.disabled = true;
|
||
if (status) status.textContent = 'Loading filing tools…';
|
||
try {
|
||
await requestFullWorkspace();
|
||
return true;
|
||
} catch (_error) {
|
||
file.disabled = false;
|
||
if (status) status.textContent = 'Filing tools unavailable. Your capture is still editable; retry when connected.';
|
||
title?.focus?.();
|
||
return false;
|
||
}
|
||
}
|
||
|
||
function start() {
|
||
if (started) return false;
|
||
started = true;
|
||
root?.classList.add('progressive-capture');
|
||
listen(button, 'click', open);
|
||
listen(save, 'click', saveDraft);
|
||
listen(file, 'click', fileNow);
|
||
listen(cancel, 'click', close);
|
||
listen(title, 'input', checkpoint);
|
||
listen(body, 'input', checkpoint);
|
||
return true;
|
||
}
|
||
|
||
function handoff() {
|
||
if (handedOff) return null;
|
||
handedOff = true;
|
||
const state = {
|
||
open:Boolean(root?.classList.contains('open')),
|
||
title:title?.value || '', body:body?.value || '',
|
||
};
|
||
state.complete = () => clearCheckpoint(state);
|
||
root?.classList.remove('progressive-capture');
|
||
file.disabled = false;
|
||
listeners.forEach(([element, name, callback]) => element?.removeEventListener?.(name, callback));
|
||
listeners.length = 0;
|
||
return state;
|
||
}
|
||
|
||
return {start, handoff};
|
||
}
|
||
|
||
if (typeof window !== 'undefined' && typeof document !== 'undefined' &&
|
||
typeof createUnfiledCaptures === 'function' &&
|
||
window.matchMedia?.('(max-width: 600px)').matches === true) {
|
||
window.stackchainProgressiveCapture = createProgressiveCapture({
|
||
document,
|
||
storage:window.localStorage,
|
||
getLogin:() => window.stackchainProgressiveMyWork?.login?.() || '',
|
||
});
|
||
window.stackchainProgressiveCapture.start();
|
||
}
|
||
|
||
if (typeof module !== 'undefined' && module.exports) module.exports = createProgressiveCapture;
|