feat: capture text files for browser inbox

This commit is contained in:
2026-06-29 09:57:21 +08:00
parent f0b66c05ff
commit 8970333d13
9 changed files with 89 additions and 8 deletions
+5 -1
View File
@@ -46,7 +46,11 @@
title: tab && tab.title || '',
selectionText: info.selectionText || '',
linkUrl: info.linkUrl || '',
linkText: info.selectionText || ''
linkText: info.selectionText || '',
fileName: info.fileName || '',
fileMime: info.fileMime || '',
fileSize: info.fileSize || 0,
fileText: info.fileText || ''
});
}
+7
View File
@@ -50,6 +50,7 @@ h1 {
}
.panel,
.file-picker,
.settings {
border: 1px solid #273244;
border-radius: 6px;
@@ -66,6 +67,7 @@ h1 {
}
.row span,
.file-picker label,
.settings label,
.hint {
color: #94a3b8;
@@ -124,6 +126,11 @@ input {
color: #e5e7eb;
}
input[type="file"] {
cursor: pointer;
font-size: 12px;
}
.hint {
margin: 0;
font-size: 12px;
+6
View File
@@ -32,9 +32,15 @@
<section class="actions">
<button id="capture-page">Send Page</button>
<button id="capture-file">Send File</button>
<button id="retry">Retry Pending</button>
</section>
<section class="file-picker">
<label for="file-input">Text file</label>
<input id="file-input" type="file">
</section>
<section class="settings">
<label for="receiver-input">Receiver URL</label>
<input id="receiver-input" type="url" spellcheck="false">
+27
View File
@@ -6,8 +6,10 @@
var receiverStateEl = document.getElementById('receiver-state');
var receiverUrlEl = document.getElementById('receiver-url');
var receiverInputEl = document.getElementById('receiver-input');
var fileInputEl = document.getElementById('file-input');
var pendingCountEl = document.getElementById('pending-count');
var statusDotEl = document.getElementById('status-dot');
var MAX_FILE_TEXT_LENGTH = 2 * 1024 * 1024;
function setStatus(text) {
statusEl.textContent = text;
@@ -67,6 +69,31 @@
send({ type: 'verstak.capture', kind: 'page' });
});
document.getElementById('capture-file').addEventListener('click', function () {
var file = fileInputEl.files && fileInputEl.files[0];
if (!file) {
setStatus('Choose a text file first');
return;
}
if (file.size > MAX_FILE_TEXT_LENGTH) {
setStatus('File is too large for text capture');
return;
}
setStatus('Reading file...');
file.text().then(function (content) {
send({
type: 'verstak.capture',
kind: 'file',
fileName: file.name,
fileMime: file.type || 'text/plain',
fileSize: file.size,
fileText: content
});
}).catch(function (err) {
setStatus(err && err.message ? err.message : String(err));
});
});
document.getElementById('retry').addEventListener('click', function () {
send({ type: 'verstak.capture', action: 'retryPending' });
});
+21 -1
View File
@@ -3,6 +3,7 @@
var CAPTURE_SCHEMA_VERSION = 1;
var DEFAULT_RECEIVER_URL = 'http://127.0.0.1:47731/api/browser-inbox/v1/captures';
var MAX_FILE_TEXT_LENGTH = 2 * 1024 * 1024;
function nowIso() {
return new Date().toISOString();
@@ -20,6 +21,12 @@
return text;
}
function cleanTextContent(value, maxLength) {
var text = String(value == null ? '' : value);
if (maxLength && text.length > maxLength) return text.slice(0, maxLength);
return text;
}
function hostname(url) {
try {
return new URL(url).hostname;
@@ -59,6 +66,16 @@
text: cleanString(input.linkText || input.selectionText || '', 512)
};
}
if (kind === 'file') {
var fileText = cleanTextContent(input.fileText || '', MAX_FILE_TEXT_LENGTH);
var fileSize = Number(input.fileSize);
payload.file = {
name: cleanString(input.fileName || '', 255),
mime: cleanString(input.fileMime || '', 128),
size: Number.isFinite(fileSize) && fileSize >= 0 ? fileSize : fileText.length,
text: fileText
};
}
if (input.context) payload.context = input.context;
return payload;
}
@@ -68,16 +85,19 @@
if (payload.schemaVersion !== CAPTURE_SCHEMA_VERSION) throw new Error('unsupported schemaVersion');
if (!payload.captureId) throw new Error('captureId is required');
if (!payload.capturedAt) throw new Error('capturedAt is required');
if (['page', 'selection', 'link'].indexOf(payload.kind) === -1) throw new Error('unsupported kind');
if (['page', 'selection', 'link', 'file'].indexOf(payload.kind) === -1) throw new Error('unsupported kind');
if (!payload.page || !payload.page.url) throw new Error('page.url is required');
if (payload.kind === 'selection' && (!payload.selection || !payload.selection.text)) throw new Error('selection.text is required');
if (payload.kind === 'link' && (!payload.link || !payload.link.url)) throw new Error('link.url is required');
if (payload.kind === 'file' && (!payload.file || !payload.file.name)) throw new Error('file.name is required');
if (payload.kind === 'file' && (!payload.file || !payload.file.text)) throw new Error('file.text is required');
return true;
}
var api = {
CAPTURE_SCHEMA_VERSION: CAPTURE_SCHEMA_VERSION,
DEFAULT_RECEIVER_URL: DEFAULT_RECEIVER_URL,
MAX_FILE_TEXT_LENGTH: MAX_FILE_TEXT_LENGTH,
buildCapture: buildCapture,
validateCapture: validateCapture
};