fix: make browser inbox mutations durable
This commit is contained in:
parent
fe97c5ddcc
commit
69a1938ff3
|
|
@ -12,6 +12,7 @@
|
||||||
var LEGACY_KEY = 'captures';
|
var LEGACY_KEY = 'captures';
|
||||||
var GLOBAL_KEY = 'captures:global';
|
var GLOBAL_KEY = 'captures:global';
|
||||||
var WORKSPACE_PREFIX = 'captures:workspace:';
|
var WORKSPACE_PREFIX = 'captures:workspace:';
|
||||||
|
var MUTATION_EVENT = 'browser-inbox.storage.mutate';
|
||||||
|
|
||||||
function injectStyles() {
|
function injectStyles() {
|
||||||
if (document.getElementById('browser-inbox-style-injected')) return;
|
if (document.getElementById('browser-inbox-style-injected')) return;
|
||||||
|
|
@ -29,7 +30,9 @@
|
||||||
'.browser-inbox-filters{display:flex;align-items:center;gap:.35rem;min-width:0;flex:1;flex-wrap:wrap}',
|
'.browser-inbox-filters{display:flex;align-items:center;gap:.35rem;min-width:0;flex:1;flex-wrap:wrap}',
|
||||||
'.browser-inbox-input,.browser-inbox-select{box-sizing:border-box;min-height:1.85rem;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-secondary,#b7c0d4);font:inherit;font-size:.76rem;padding:.25rem .42rem}',
|
'.browser-inbox-input,.browser-inbox-select{box-sizing:border-box;min-height:1.85rem;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-secondary,#b7c0d4);font:inherit;font-size:.76rem;padding:.25rem .42rem}',
|
||||||
'.browser-inbox-input{width:min(15rem,100%)}',
|
'.browser-inbox-input{width:min(15rem,100%)}',
|
||||||
'.browser-inbox-select{max-width:12rem}',
|
'.browser-inbox-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.65rem}',
|
||||||
|
'.browser-inbox-select option{background:var(--vt-color-surface,#15152c);color:var(--vt-color-text-primary,#f4f7fb)}',
|
||||||
|
'.browser-inbox-input:focus,.browser-inbox-select:focus{outline:none;border-color:var(--vt-color-accent,#4ecca3);box-shadow:0 0 0 1px var(--vt-color-accent,#4ecca3)}',
|
||||||
'.browser-inbox-spacer{flex:1}',
|
'.browser-inbox-spacer{flex:1}',
|
||||||
'.browser-inbox-btn{font-size:.78rem;padding:.32rem .65rem;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);cursor:pointer}',
|
'.browser-inbox-btn{font-size:.78rem;padding:.32rem .65rem;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);cursor:pointer}',
|
||||||
'.browser-inbox-btn:hover{background:var(--vt-color-surface-hover,#1b2440);border-color:var(--vt-color-accent,#4ecca3);color:var(--vt-color-text-primary,#f4f7fb)}',
|
'.browser-inbox-btn:hover{background:var(--vt-color-surface-hover,#1b2440);border-color:var(--vt-color-accent,#4ecca3);color:var(--vt-color-text-primary,#f4f7fb)}',
|
||||||
|
|
@ -473,11 +476,32 @@
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function persist() {
|
function publishMutation(action, payload, verify, verifySettings) {
|
||||||
if (!api || !api.settings || typeof api.settings.write !== 'function') return Promise.resolve();
|
if (!api || !api.events || typeof api.events.publish !== 'function') {
|
||||||
return api.settings.write(GLOBAL_KEY, storageCaptures(sortCaptures(captures))).catch(function (err) {
|
statusText = 'Could not save inbox: events API unavailable';
|
||||||
|
statusClass = 'error';
|
||||||
|
render();
|
||||||
|
return Promise.resolve(false);
|
||||||
|
}
|
||||||
|
return api.events.publish(MUTATION_EVENT, Object.assign({ action: action }, payload || {})).then(function () {
|
||||||
|
if (!api.settings || typeof api.settings.read !== 'function') throw new Error('settings API unavailable');
|
||||||
|
return api.settings.read();
|
||||||
|
}).then(function (settings) {
|
||||||
|
settings = settings || {};
|
||||||
|
if (typeof verifySettings === 'function' && !verifySettings(settings)) {
|
||||||
|
throw new Error('the stored inbox did not reach the expected state');
|
||||||
|
}
|
||||||
|
return loadStored(true);
|
||||||
|
}).then(function () {
|
||||||
|
if (typeof verify === 'function' && !verify()) {
|
||||||
|
throw new Error('the stored capture did not change');
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}).catch(function (err) {
|
||||||
statusText = 'Could not save inbox: ' + (err && err.message ? err.message : String(err));
|
statusText = 'Could not save inbox: ' + (err && err.message ? err.message : String(err));
|
||||||
statusClass = 'error';
|
statusClass = 'error';
|
||||||
|
render();
|
||||||
|
return false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -486,30 +510,14 @@
|
||||||
captureIds.forEach(function (captureId) {
|
captureIds.forEach(function (captureId) {
|
||||||
ids[captureId] = true;
|
ids[captureId] = true;
|
||||||
});
|
});
|
||||||
captures = captures.filter(function (item) {
|
return publishMutation('delete', { captureIds: captureIds }, function () {
|
||||||
return !ids[item.captureId];
|
return !captures.some(function (capture) { return ids[capture.captureId]; });
|
||||||
});
|
}).then(function (saved) {
|
||||||
if (ids[selectedId]) selectedId = '';
|
if (!saved) return;
|
||||||
if (!api || !api.settings || typeof api.settings.read !== 'function' || typeof api.settings.write !== 'function') {
|
if (ids[selectedId]) selectedId = '';
|
||||||
return persist().then(function () {
|
|
||||||
statusText = successText;
|
|
||||||
statusClass = '';
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return api.settings.read().then(function (settings) {
|
|
||||||
var keys = globalCaptureKeys(settings || {});
|
|
||||||
return Promise.all(keys.map(function (key) {
|
|
||||||
var next = normalizeStoredCaptures((settings || {})[key], key).filter(function (item) {
|
|
||||||
return !ids[item.captureId];
|
|
||||||
});
|
|
||||||
return api.settings.write(key, storageCaptures(next));
|
|
||||||
}));
|
|
||||||
}).then(function () {
|
|
||||||
statusText = successText;
|
statusText = successText;
|
||||||
statusClass = '';
|
statusClass = '';
|
||||||
}).catch(function (err) {
|
render();
|
||||||
statusText = 'Could not update inbox: ' + (err && err.message ? err.message : String(err));
|
|
||||||
statusClass = 'error';
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -534,11 +542,13 @@
|
||||||
return item.captureId === capture.captureId;
|
return item.captureId === capture.captureId;
|
||||||
});
|
});
|
||||||
if (existing) return Promise.resolve();
|
if (existing) return Promise.resolve();
|
||||||
captures = sortCaptures([capture].concat(captures));
|
|
||||||
selectedId = capture.captureId;
|
selectedId = capture.captureId;
|
||||||
statusText = 'Capture received';
|
statusText = 'Capture received';
|
||||||
statusClass = '';
|
statusClass = '';
|
||||||
return persist().then(render);
|
statusText = 'Could not load received capture from storage';
|
||||||
|
statusClass = 'error';
|
||||||
|
render();
|
||||||
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
function applyDomainBinding(capture) {
|
function applyDomainBinding(capture) {
|
||||||
|
|
@ -552,17 +562,20 @@
|
||||||
|
|
||||||
function assignWorkspace(captureId, workspaceRoot) {
|
function assignWorkspace(captureId, workspaceRoot) {
|
||||||
workspaceRoot = cleanWorkspace(workspaceRoot);
|
workspaceRoot = cleanWorkspace(workspaceRoot);
|
||||||
captures = captures.map(function (capture) {
|
return publishMutation('assign', {
|
||||||
if (capture.captureId !== captureId) return capture;
|
captureId: captureId,
|
||||||
return Object.assign({}, capture, {
|
workspaceRootPath: workspaceRoot
|
||||||
workspaceRootPath: workspaceRoot,
|
}, function () {
|
||||||
workspaceName: workspaceRoot
|
return captures.some(function (capture) {
|
||||||
|
return capture.captureId === captureId && cleanWorkspace(capture.workspaceRootPath) === workspaceRoot;
|
||||||
});
|
});
|
||||||
|
}).then(function (saved) {
|
||||||
|
if (!saved) return;
|
||||||
|
if (workspaceRoot && workspaceOptions.indexOf(workspaceRoot) === -1) workspaceOptions.push(workspaceRoot);
|
||||||
|
statusText = workspaceRoot ? 'Capture assigned to ' + workspaceRoot : 'Capture is unassigned';
|
||||||
|
statusClass = '';
|
||||||
|
render();
|
||||||
});
|
});
|
||||||
if (workspaceRoot && workspaceOptions.indexOf(workspaceRoot) === -1) workspaceOptions.push(workspaceRoot);
|
|
||||||
statusText = workspaceRoot ? 'Capture assigned to ' + workspaceRoot : 'Capture is unassigned';
|
|
||||||
statusClass = '';
|
|
||||||
return persist().then(render);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeCapture(captureId) {
|
function removeCapture(captureId) {
|
||||||
|
|
@ -570,13 +583,19 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
function setProcessed(captureId, processed) {
|
function setProcessed(captureId, processed) {
|
||||||
captures = captures.map(function (capture) {
|
return publishMutation('processed', {
|
||||||
if (capture.captureId !== captureId) return capture;
|
captureId: captureId,
|
||||||
return Object.assign({}, capture, { processed: processed === true });
|
processed: processed === true
|
||||||
|
}, function () {
|
||||||
|
return captures.some(function (capture) {
|
||||||
|
return capture.captureId === captureId && capture.processed === (processed === true);
|
||||||
|
});
|
||||||
|
}).then(function (saved) {
|
||||||
|
if (!saved) return;
|
||||||
|
statusText = processed ? 'Capture marked processed' : 'Capture marked unprocessed';
|
||||||
|
statusClass = '';
|
||||||
|
render();
|
||||||
});
|
});
|
||||||
statusText = processed ? 'Capture marked processed' : 'Capture marked unprocessed';
|
|
||||||
statusClass = '';
|
|
||||||
return persist().then(render);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function createNoteFromCapture(capture) {
|
function createNoteFromCapture(capture) {
|
||||||
|
|
@ -884,7 +903,7 @@
|
||||||
renderDetail();
|
renderDetail();
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadStored() {
|
function loadStored(skipMigration) {
|
||||||
if (!api || !api.settings || typeof api.settings.read !== 'function') return Promise.resolve();
|
if (!api || !api.settings || typeof api.settings.read !== 'function') return Promise.resolve();
|
||||||
return api.settings.read().then(function (settings) {
|
return api.settings.read().then(function (settings) {
|
||||||
domainBindings = normalizeDomainBindings((settings || {}).domainBindings);
|
domainBindings = normalizeDomainBindings((settings || {}).domainBindings);
|
||||||
|
|
@ -896,10 +915,28 @@
|
||||||
if (key !== GLOBAL_KEY && stored.length > 0) hasLegacyCaptures = true;
|
if (key !== GLOBAL_KEY && stored.length > 0) hasLegacyCaptures = true;
|
||||||
all = all.concat(stored);
|
all = all.concat(stored);
|
||||||
});
|
});
|
||||||
captures = sortCaptures(all);
|
captures = sortCaptures(all).map(applyDomainBinding);
|
||||||
if (!selectedId && captures[0]) selectedId = captures[0].captureId;
|
if (!selectedId && captures[0]) selectedId = captures[0].captureId;
|
||||||
// Keep legacy records readable, then mirror the canonical state into the global queue.
|
// Read legacy records once, then ask the backend to migrate them atomically.
|
||||||
return hasLegacyCaptures ? persist() : undefined;
|
if (!hasLegacyCaptures || skipMigration) return undefined;
|
||||||
|
var expectedIds = captures.map(function (capture) { return capture.captureId; });
|
||||||
|
return publishMutation('migrate', {}, function () {
|
||||||
|
return expectedIds.every(function (captureId) {
|
||||||
|
return captures.some(function (capture) { return capture.captureId === captureId; });
|
||||||
|
});
|
||||||
|
}, function (migratedSettings) {
|
||||||
|
var canonicalIds = normalizeStoredCaptures(migratedSettings[GLOBAL_KEY], GLOBAL_KEY).map(function (capture) {
|
||||||
|
return capture.captureId;
|
||||||
|
});
|
||||||
|
var legacyIsEmpty = globalCaptureKeys(migratedSettings).filter(function (key) {
|
||||||
|
return key !== GLOBAL_KEY;
|
||||||
|
}).every(function (key) {
|
||||||
|
return normalizeStoredCaptures(migratedSettings[key], key).length === 0;
|
||||||
|
});
|
||||||
|
return legacyIsEmpty && expectedIds.every(function (captureId) {
|
||||||
|
return canonicalIds.indexOf(captureId) !== -1;
|
||||||
|
});
|
||||||
|
});
|
||||||
}).catch(function (err) {
|
}).catch(function (err) {
|
||||||
statusText = 'Could not load inbox: ' + (err && err.message ? err.message : String(err));
|
statusText = 'Could not load inbox: ' + (err && err.message ? err.message : String(err));
|
||||||
statusClass = 'error';
|
statusClass = 'error';
|
||||||
|
|
@ -925,7 +962,34 @@
|
||||||
if (!api || !api.events || typeof api.events.subscribe !== 'function') return Promise.resolve();
|
if (!api || !api.events || typeof api.events.subscribe !== 'function') return Promise.resolve();
|
||||||
return Promise.all(CAPTURE_EVENTS.map(function (eventName) {
|
return Promise.all(CAPTURE_EVENTS.map(function (eventName) {
|
||||||
return api.events.subscribe(eventName, function (event) {
|
return api.events.subscribe(eventName, function (event) {
|
||||||
return addCapture(captureFromEvent(event));
|
var received = captureFromEvent(event);
|
||||||
|
return loadStored(true).then(function () {
|
||||||
|
var stored = captures.find(function (capture) {
|
||||||
|
return capture.captureId === received.captureId;
|
||||||
|
});
|
||||||
|
if (!stored) return addCapture(received);
|
||||||
|
if (!received.workspaceRootPath && stored.workspaceRootPath) {
|
||||||
|
return publishMutation('assign', {
|
||||||
|
captureId: stored.captureId,
|
||||||
|
workspaceRootPath: stored.workspaceRootPath
|
||||||
|
}, function () {
|
||||||
|
return captures.some(function (capture) {
|
||||||
|
return capture.captureId === stored.captureId && capture.workspaceRootPath === stored.workspaceRootPath;
|
||||||
|
});
|
||||||
|
}).then(function (saved) {
|
||||||
|
if (!saved) return;
|
||||||
|
selectedId = received.captureId;
|
||||||
|
statusText = 'Capture received';
|
||||||
|
statusClass = '';
|
||||||
|
render();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
selectedId = received.captureId;
|
||||||
|
statusText = 'Capture received';
|
||||||
|
statusClass = '';
|
||||||
|
render();
|
||||||
|
return undefined;
|
||||||
|
});
|
||||||
}).then(function (unsubscribe) {
|
}).then(function (unsubscribe) {
|
||||||
if (typeof unsubscribe === 'function') unsubscribers.push(unsubscribe);
|
if (typeof unsubscribe === 'function') unsubscribers.push(unsubscribe);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -152,6 +152,48 @@ function makeApi(initialSettings = {}) {
|
||||||
receiverToken: 'initial-browser-token',
|
receiverToken: 'initial-browser-token',
|
||||||
};
|
};
|
||||||
let nextWriteError = null;
|
let nextWriteError = null;
|
||||||
|
function backendCaptures() {
|
||||||
|
const keys = ['captures:global', 'captures', ...Object.keys(settings).filter((key) => key.startsWith('captures:workspace:'))];
|
||||||
|
const seen = new Set();
|
||||||
|
const captures = [];
|
||||||
|
for (const key of keys) {
|
||||||
|
for (const original of Array.isArray(settings[key]) ? settings[key] : []) {
|
||||||
|
if (!original || !original.captureId || seen.has(original.captureId)) continue;
|
||||||
|
seen.add(original.captureId);
|
||||||
|
const capture = { ...original };
|
||||||
|
if (!capture.workspaceRootPath && key.startsWith('captures:workspace:')) {
|
||||||
|
capture.workspaceRootPath = decodeURIComponent(key.slice('captures:workspace:'.length));
|
||||||
|
capture.workspaceName = capture.workspaceRootPath;
|
||||||
|
}
|
||||||
|
captures.push(capture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { keys, captures };
|
||||||
|
}
|
||||||
|
function backendWriteCaptures(captures, keys) {
|
||||||
|
settings['captures:global'] = captures.slice(0, 100);
|
||||||
|
keys.forEach((key) => {
|
||||||
|
if (key !== 'captures:global') settings[key] = [];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function backendMutate(payload) {
|
||||||
|
const { keys, captures } = backendCaptures();
|
||||||
|
const ids = new Set([payload.captureId, ...(payload.captureIds || [])].filter(Boolean));
|
||||||
|
const next = captures.flatMap((capture) => {
|
||||||
|
if (!ids.has(capture.captureId)) return [capture];
|
||||||
|
if (payload.action === 'delete') return [];
|
||||||
|
if (payload.action === 'assign') return [{ ...capture, workspaceRootPath: payload.workspaceRootPath || '', workspaceName: payload.workspaceRootPath || '' }];
|
||||||
|
if (payload.action === 'processed') return [{ ...capture, processed: payload.processed === true }];
|
||||||
|
return [capture];
|
||||||
|
});
|
||||||
|
backendWriteCaptures(next, keys);
|
||||||
|
}
|
||||||
|
function backendAppend(event) {
|
||||||
|
const { keys, captures } = backendCaptures();
|
||||||
|
const payload = { ...(event.payload || {}) };
|
||||||
|
const next = [payload, ...captures.filter((capture) => capture.captureId !== payload.captureId)];
|
||||||
|
backendWriteCaptures(next, keys);
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
settings,
|
settings,
|
||||||
handlers,
|
handlers,
|
||||||
|
|
@ -165,9 +207,13 @@ function makeApi(initialSettings = {}) {
|
||||||
events: {
|
events: {
|
||||||
publish: async (name, payload) => {
|
publish: async (name, payload) => {
|
||||||
publishedEvents.push({ name, payload });
|
publishedEvents.push({ name, payload });
|
||||||
|
if (name === 'browser-inbox.storage.mutate') backendMutate(payload || {});
|
||||||
},
|
},
|
||||||
subscribe: async (name, handler) => {
|
subscribe: async (name, handler) => {
|
||||||
handlers[name] = handler;
|
handlers[name] = async (event) => {
|
||||||
|
if (name.startsWith('browser.capture.')) backendAppend(event);
|
||||||
|
return handler(event);
|
||||||
|
};
|
||||||
return () => {
|
return () => {
|
||||||
unsubscribed.push(name);
|
unsubscribed.push(name);
|
||||||
delete handlers[name];
|
delete handlers[name];
|
||||||
|
|
@ -214,7 +260,7 @@ function makeApi(initialSettings = {}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function flush() {
|
async function flush() {
|
||||||
for (let i = 0; i < 8; i += 1) await Promise.resolve();
|
for (let i = 0; i < 16; i += 1) await Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function mountWithApi(api, props = { workspaceNode: { name: 'Project' }, workspaceRootPath: 'Project' }, document = makeDocument()) {
|
async function mountWithApi(api, props = { workspaceNode: { name: 'Project' }, workspaceRootPath: 'Project' }, document = makeDocument()) {
|
||||||
|
|
@ -237,6 +283,14 @@ async function mountSettingsWithApi(api, document = makeDocument()) {
|
||||||
const settingsComponents = loadComponents(makeDocument());
|
const settingsComponents = loadComponents(makeDocument());
|
||||||
if (!settingsComponents.BrowserInboxSettings) throw new Error('BrowserInboxSettings was not registered');
|
if (!settingsComponents.BrowserInboxSettings) throw new Error('BrowserInboxSettings was not registered');
|
||||||
|
|
||||||
|
const styleDocument = makeDocument();
|
||||||
|
const styledView = await mountWithApi(makeApi(), {}, styleDocument);
|
||||||
|
const injectedStyles = styleDocument.head.children.map((node) => node.textContent).join('\n');
|
||||||
|
if (!injectedStyles.includes('.browser-inbox-select{') || !injectedStyles.includes('appearance:none')) {
|
||||||
|
throw new Error('Browser Inbox selects do not use the application select styling');
|
||||||
|
}
|
||||||
|
styledView.component.unmount && styledView.component.unmount(styledView.container);
|
||||||
|
|
||||||
const api = makeApi();
|
const api = makeApi();
|
||||||
const settingsView = await mountSettingsWithApi(makeApi());
|
const settingsView = await mountSettingsWithApi(makeApi());
|
||||||
const receiverURLInput = walk(settingsView.container, (node) => node.getAttribute && node.getAttribute('data-browser-inbox-pairing-url') === '');
|
const receiverURLInput = walk(settingsView.container, (node) => node.getAttribute && node.getAttribute('data-browser-inbox-pairing-url') === '');
|
||||||
|
|
@ -414,6 +468,9 @@ async function mountSettingsWithApi(api, document = makeDocument()) {
|
||||||
if (!walk(legacyGlobal.container, (node) => node.getAttribute && node.getAttribute('data-browser-capture-id') === 'legacy-project-capture')) {
|
if (!walk(legacyGlobal.container, (node) => node.getAttribute && node.getAttribute('data-browser-capture-id') === 'legacy-project-capture')) {
|
||||||
throw new Error('legacy workspace capture was not rendered in global view');
|
throw new Error('legacy workspace capture was not rendered in global view');
|
||||||
}
|
}
|
||||||
|
if (legacyApi.getStoredCaptures('captures').length !== 0) {
|
||||||
|
throw new Error('legacy captures were not removed after canonical migration');
|
||||||
|
}
|
||||||
component.unmount && component.unmount(legacyGlobal.container);
|
component.unmount && component.unmount(legacyGlobal.container);
|
||||||
|
|
||||||
const legacyProject = await mountWithApi(legacyApi, { workspaceNode: { name: 'Project' }, workspaceRootPath: 'Project' });
|
const legacyProject = await mountWithApi(legacyApi, { workspaceNode: { name: 'Project' }, workspaceRootPath: 'Project' });
|
||||||
|
|
@ -612,7 +669,7 @@ async function mountSettingsWithApi(api, document = makeDocument()) {
|
||||||
if (!noteWrite.content.includes('# Example Article')) throw new Error('note content missing heading');
|
if (!noteWrite.content.includes('# Example Article')) throw new Error('note content missing heading');
|
||||||
if (!noteWrite.content.includes('Source: https://example.com/article')) throw new Error('note content missing source URL');
|
if (!noteWrite.content.includes('Source: https://example.com/article')) throw new Error('note content missing source URL');
|
||||||
if (!noteWrite.content.includes('Selected text from the page')) throw new Error('note content missing selected text');
|
if (!noteWrite.content.includes('Selected text from the page')) throw new Error('note content missing selected text');
|
||||||
if (conversionApi.getStoredCaptures(projectKey).some((capture) => capture.captureId === 'convert-selection')) {
|
if (conversionApi.getStoredCaptures(globalKey).some((capture) => capture.captureId === 'convert-selection')) {
|
||||||
throw new Error('converted capture was not removed from queue');
|
throw new Error('converted capture was not removed from queue');
|
||||||
}
|
}
|
||||||
const convertedEvent = conversionApi.publishedEvents.find((event) => event.name === 'browser.capture.converted');
|
const convertedEvent = conversionApi.publishedEvents.find((event) => event.name === 'browser.capture.converted');
|
||||||
|
|
@ -639,7 +696,7 @@ async function mountSettingsWithApi(api, document = makeDocument()) {
|
||||||
failedConversionApi.failNextWrite('file already exists');
|
failedConversionApi.failNextWrite('file already exists');
|
||||||
failedCreateNoteButton.click();
|
failedCreateNoteButton.click();
|
||||||
await flush();
|
await flush();
|
||||||
if (!failedConversionApi.getStoredCaptures(projectKey).some((capture) => capture.captureId === 'convert-conflict')) {
|
if (!failedConversionApi.getStoredCaptures(globalKey).some((capture) => capture.captureId === 'convert-conflict')) {
|
||||||
throw new Error('failed conversion removed capture from queue');
|
throw new Error('failed conversion removed capture from queue');
|
||||||
}
|
}
|
||||||
if (!failedConversionView.container.textContent.includes('Could not create note')) {
|
if (!failedConversionView.container.textContent.includes('Could not create note')) {
|
||||||
|
|
@ -677,7 +734,7 @@ async function mountSettingsWithApi(api, document = makeDocument()) {
|
||||||
}
|
}
|
||||||
if (!linkWrite.content.includes('[InternetShortcut]')) throw new Error('link content missing InternetShortcut header');
|
if (!linkWrite.content.includes('[InternetShortcut]')) throw new Error('link content missing InternetShortcut header');
|
||||||
if (!linkWrite.content.includes('URL=https://example.com/article')) throw new Error('link content missing URL');
|
if (!linkWrite.content.includes('URL=https://example.com/article')) throw new Error('link content missing URL');
|
||||||
if (linkConversionApi.getStoredCaptures(projectKey).some((capture) => capture.captureId === 'convert-link')) {
|
if (linkConversionApi.getStoredCaptures(globalKey).some((capture) => capture.captureId === 'convert-link')) {
|
||||||
throw new Error('converted link capture was not removed from queue');
|
throw new Error('converted link capture was not removed from queue');
|
||||||
}
|
}
|
||||||
const convertedLinkEvent = linkConversionApi.publishedEvents.find((event) => event.name === 'browser.capture.converted');
|
const convertedLinkEvent = linkConversionApi.publishedEvents.find((event) => event.name === 'browser.capture.converted');
|
||||||
|
|
@ -704,7 +761,7 @@ async function mountSettingsWithApi(api, document = makeDocument()) {
|
||||||
failedLinkApi.failNextWrite('link already exists');
|
failedLinkApi.failNextWrite('link already exists');
|
||||||
failedCreateLinkButton.click();
|
failedCreateLinkButton.click();
|
||||||
await flush();
|
await flush();
|
||||||
if (!failedLinkApi.getStoredCaptures(projectKey).some((capture) => capture.captureId === 'convert-link-conflict')) {
|
if (!failedLinkApi.getStoredCaptures(globalKey).some((capture) => capture.captureId === 'convert-link-conflict')) {
|
||||||
throw new Error('failed link conversion removed capture from queue');
|
throw new Error('failed link conversion removed capture from queue');
|
||||||
}
|
}
|
||||||
if (!failedLinkView.container.textContent.includes('Could not create link')) {
|
if (!failedLinkView.container.textContent.includes('Could not create link')) {
|
||||||
|
|
@ -745,7 +802,7 @@ async function mountSettingsWithApi(api, document = makeDocument()) {
|
||||||
if (fileWrite.options.createIfMissing !== true || fileWrite.options.overwrite !== false) {
|
if (fileWrite.options.createIfMissing !== true || fileWrite.options.overwrite !== false) {
|
||||||
throw new Error(`file write options mismatch: ${JSON.stringify(fileWrite.options)}`);
|
throw new Error(`file write options mismatch: ${JSON.stringify(fileWrite.options)}`);
|
||||||
}
|
}
|
||||||
if (fileConversionApi.getStoredCaptures(projectKey).some((capture) => capture.captureId === 'convert-file')) {
|
if (fileConversionApi.getStoredCaptures(globalKey).some((capture) => capture.captureId === 'convert-file')) {
|
||||||
throw new Error('converted file capture was not removed from queue');
|
throw new Error('converted file capture was not removed from queue');
|
||||||
}
|
}
|
||||||
const convertedFileEvent = fileConversionApi.publishedEvents.find((event) => event.name === 'browser.capture.converted');
|
const convertedFileEvent = fileConversionApi.publishedEvents.find((event) => event.name === 'browser.capture.converted');
|
||||||
|
|
@ -811,7 +868,7 @@ async function mountSettingsWithApi(api, document = makeDocument()) {
|
||||||
failedFileApi.failNextWrite('file already exists');
|
failedFileApi.failNextWrite('file already exists');
|
||||||
failedCreateFileButton.click();
|
failedCreateFileButton.click();
|
||||||
await flush();
|
await flush();
|
||||||
if (!failedFileApi.getStoredCaptures(projectKey).some((capture) => capture.captureId === 'convert-file-conflict')) {
|
if (!failedFileApi.getStoredCaptures(globalKey).some((capture) => capture.captureId === 'convert-file-conflict')) {
|
||||||
throw new Error('failed file conversion removed capture from queue');
|
throw new Error('failed file conversion removed capture from queue');
|
||||||
}
|
}
|
||||||
if (!failedFileView.container.textContent.includes('Could not create file')) {
|
if (!failedFileView.container.textContent.includes('Could not create file')) {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue