feat: convert browser captures to link files
This commit is contained in:
parent
6fa0dd350d
commit
b46a973fd3
|
|
@ -159,6 +159,10 @@
|
||||||
return (base || 'Browser_Capture') + '.md';
|
return (base || 'Browser_Capture') + '.md';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function safeLinkFilename(title) {
|
||||||
|
return safeNoteFilename(title).replace(/\.md$/, '.url');
|
||||||
|
}
|
||||||
|
|
||||||
function captureToMarkdown(capture) {
|
function captureToMarkdown(capture) {
|
||||||
var title = noteTitle(capture);
|
var title = noteTitle(capture);
|
||||||
var lines = ['# ' + title, ''];
|
var lines = ['# ' + title, ''];
|
||||||
|
|
@ -173,6 +177,10 @@
|
||||||
return lines.join('\n');
|
return lines.join('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function captureToUrlShortcut(capture) {
|
||||||
|
return '[InternetShortcut]\nURL=' + text(capture && capture.url).trim() + '\n';
|
||||||
|
}
|
||||||
|
|
||||||
function eventPayload(event) {
|
function eventPayload(event) {
|
||||||
if (!event || !event.payload) return {};
|
if (!event || !event.payload) return {};
|
||||||
return event.payload;
|
return event.payload;
|
||||||
|
|
@ -459,6 +467,46 @@
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function createLinkFromCapture(capture) {
|
||||||
|
if (!capture || !capture.workspaceRootPath || !capture.url) return Promise.resolve();
|
||||||
|
if (!api || !api.files || typeof api.files.writeText !== 'function') {
|
||||||
|
statusText = 'Could not create link: files API unavailable';
|
||||||
|
statusClass = 'error';
|
||||||
|
render();
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
var title = noteTitle(capture);
|
||||||
|
var linkPath = capture.workspaceRootPath + '/Links/' + safeLinkFilename(title);
|
||||||
|
statusText = 'Creating link...';
|
||||||
|
statusClass = '';
|
||||||
|
render();
|
||||||
|
return api.files.writeText(linkPath, captureToUrlShortcut(capture), {
|
||||||
|
createIfMissing: true,
|
||||||
|
overwrite: false
|
||||||
|
}).then(function () {
|
||||||
|
if (api.events && typeof api.events.publish === 'function') {
|
||||||
|
return api.events.publish('browser.capture.converted', {
|
||||||
|
captureId: capture.captureId,
|
||||||
|
conversionType: 'link',
|
||||||
|
linkPath: linkPath,
|
||||||
|
workspaceRootPath: capture.workspaceRootPath,
|
||||||
|
title: title,
|
||||||
|
url: capture.url || '',
|
||||||
|
sourcePluginId: PLUGIN_ID
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}).then(function () {
|
||||||
|
statusText = 'Created link: ' + linkPath;
|
||||||
|
statusClass = '';
|
||||||
|
return removeCapture(capture.captureId);
|
||||||
|
}).catch(function (err) {
|
||||||
|
statusText = 'Could not create link: ' + (err && err.message ? err.message : String(err));
|
||||||
|
statusClass = 'error';
|
||||||
|
render();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function renderList() {
|
function renderList() {
|
||||||
listEl.innerHTML = '';
|
listEl.innerHTML = '';
|
||||||
if (captures.length === 0) {
|
if (captures.length === 0) {
|
||||||
|
|
@ -521,6 +569,16 @@
|
||||||
createNoteFromCapture(capture);
|
createNoteFromCapture(capture);
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
if (capture.url) {
|
||||||
|
actionButtons.push(el('button', {
|
||||||
|
className: 'browser-inbox-btn',
|
||||||
|
'data-browser-inbox-action': 'create-link',
|
||||||
|
textContent: 'Create Link',
|
||||||
|
onClick: function () {
|
||||||
|
createLinkFromCapture(capture);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
actionButtons.push(el('button', {
|
actionButtons.push(el('button', {
|
||||||
className: 'browser-inbox-btn danger',
|
className: 'browser-inbox-btn danger',
|
||||||
|
|
|
||||||
|
|
@ -481,6 +481,71 @@ async function mountWithApi(api, props = { workspaceNode: { name: 'Project' }, w
|
||||||
}
|
}
|
||||||
component.unmount && component.unmount(failedConversionView.container);
|
component.unmount && component.unmount(failedConversionView.container);
|
||||||
|
|
||||||
|
const linkConversionApi = makeApi({
|
||||||
|
'captures:workspace:Project': [{
|
||||||
|
captureId: 'convert-link',
|
||||||
|
capturedAt: '2026-06-29T01:20:00.000Z',
|
||||||
|
kind: 'link',
|
||||||
|
url: 'https://example.com/article',
|
||||||
|
title: 'Example Article',
|
||||||
|
domain: 'example.com',
|
||||||
|
workspaceRootPath: 'Project',
|
||||||
|
workspaceName: 'Project',
|
||||||
|
}],
|
||||||
|
});
|
||||||
|
const linkConversionView = await mountWithApi(linkConversionApi);
|
||||||
|
const createLinkButton = walk(linkConversionView.container, (node) => node.getAttribute && node.getAttribute('data-browser-inbox-action') === 'create-link');
|
||||||
|
if (!createLinkButton) throw new Error('create link button was not rendered');
|
||||||
|
createLinkButton.click();
|
||||||
|
await flush();
|
||||||
|
if (linkConversionApi.fileWrites.length !== 1) throw new Error(`expected one link write, got ${linkConversionApi.fileWrites.length}`);
|
||||||
|
const linkWrite = linkConversionApi.fileWrites[0];
|
||||||
|
if (linkWrite.relativePath !== 'Project/Links/Example_Article.url') {
|
||||||
|
throw new Error(`link path mismatch: ${linkWrite.relativePath}`);
|
||||||
|
}
|
||||||
|
if (linkWrite.options.createIfMissing !== true || linkWrite.options.overwrite !== false) {
|
||||||
|
throw new Error(`link write options mismatch: ${JSON.stringify(linkWrite.options)}`);
|
||||||
|
}
|
||||||
|
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 (linkConversionApi.getStoredCaptures(projectKey).some((capture) => capture.captureId === 'convert-link')) {
|
||||||
|
throw new Error('converted link capture was not removed from queue');
|
||||||
|
}
|
||||||
|
const convertedLinkEvent = linkConversionApi.publishedEvents.find((event) => event.name === 'browser.capture.converted');
|
||||||
|
if (!convertedLinkEvent) throw new Error('browser.capture.converted link event was not published');
|
||||||
|
if (convertedLinkEvent.payload.conversionType !== 'link') throw new Error('converted link event conversionType mismatch');
|
||||||
|
if (convertedLinkEvent.payload.linkPath !== 'Project/Links/Example_Article.url') throw new Error('converted link event linkPath mismatch');
|
||||||
|
component.unmount && component.unmount(linkConversionView.container);
|
||||||
|
|
||||||
|
const failedLinkApi = makeApi({
|
||||||
|
'captures:workspace:Project': [{
|
||||||
|
captureId: 'convert-link-conflict',
|
||||||
|
capturedAt: '2026-06-29T01:30:00.000Z',
|
||||||
|
kind: 'link',
|
||||||
|
url: 'https://example.com/existing-link',
|
||||||
|
title: 'Existing Link',
|
||||||
|
domain: 'example.com',
|
||||||
|
workspaceRootPath: 'Project',
|
||||||
|
workspaceName: 'Project',
|
||||||
|
}],
|
||||||
|
});
|
||||||
|
const failedLinkView = await mountWithApi(failedLinkApi);
|
||||||
|
const failedCreateLinkButton = walk(failedLinkView.container, (node) => node.getAttribute && node.getAttribute('data-browser-inbox-action') === 'create-link');
|
||||||
|
if (!failedCreateLinkButton) throw new Error('create link button for failed conversion was not rendered');
|
||||||
|
failedLinkApi.failNextWrite('link already exists');
|
||||||
|
failedCreateLinkButton.click();
|
||||||
|
await flush();
|
||||||
|
if (!failedLinkApi.getStoredCaptures(projectKey).some((capture) => capture.captureId === 'convert-link-conflict')) {
|
||||||
|
throw new Error('failed link conversion removed capture from queue');
|
||||||
|
}
|
||||||
|
if (!failedLinkView.container.textContent.includes('Could not create link')) {
|
||||||
|
throw new Error('failed link conversion did not render an error status');
|
||||||
|
}
|
||||||
|
if (failedLinkApi.publishedEvents.some((event) => event.name === 'browser.capture.converted')) {
|
||||||
|
throw new Error('failed link conversion published converted event');
|
||||||
|
}
|
||||||
|
component.unmount && component.unmount(failedLinkView.container);
|
||||||
|
|
||||||
console.log('browser inbox plugin smoke passed');
|
console.log('browser inbox plugin smoke passed');
|
||||||
})().catch((err) => {
|
})().catch((err) => {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue