hh-shipments 0.3.1 → 0.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/public/app.js +38 -9
- package/public/index.html +3 -1
- package/public/styles.css +2 -0
- package/public/sw.js +11 -3
- package/server.js +9 -0
package/package.json
CHANGED
package/public/app.js
CHANGED
|
@@ -196,19 +196,48 @@ function pastedImageFiles(e) {
|
|
|
196
196
|
|
|
197
197
|
// Paste-text tab: pasting a screenshot runs OCR and extracts tracking numbers;
|
|
198
198
|
// pasting plain text behaves normally
|
|
199
|
+
async function handlePastedImageFile(f) {
|
|
200
|
+
try {
|
|
201
|
+
const text = await ocrFile(f, $('ocr-status-paste'));
|
|
202
|
+
const fd = new FormData();
|
|
203
|
+
fd.append('image', f);
|
|
204
|
+
const up = await fetch('/api/intake/image', { method: 'POST', body: fd });
|
|
205
|
+
if (!up.ok) throw new Error('upload failed');
|
|
206
|
+
await runQuick(text, $('quick-result'));
|
|
207
|
+
} catch (err) { $('ocr-status-paste').textContent = ''; $('quick-result').innerHTML = `<p class="error">${esc(err.message)}</p>`; }
|
|
208
|
+
}
|
|
209
|
+
|
|
199
210
|
$('tab-paste').addEventListener('paste', async (e) => {
|
|
200
211
|
const files = pastedImageFiles(e);
|
|
201
212
|
if (!files.length) return; // text paste — let it land in the textarea
|
|
202
213
|
e.preventDefault();
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
214
|
+
$('paste-box').innerHTML = '📋 <span class="muted">or tap here, then paste a screenshot</span>';
|
|
215
|
+
for (const f of files) await handlePastedImageFile(f);
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
// Fallback: read the image straight off the clipboard on tap (iOS sometimes
|
|
219
|
+
// won't offer image-paste inside a plain text box)
|
|
220
|
+
$('btn-clipboard').addEventListener('click', async () => {
|
|
221
|
+
if (!navigator.clipboard || !navigator.clipboard.read) {
|
|
222
|
+
$('quick-result').innerHTML = '<p class="error">This browser can\'t read the clipboard — copy the screenshot, tap the dashed box above, long-press → Paste.</p>';
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
try {
|
|
226
|
+
$('ocr-status-paste').textContent = 'Reading clipboard…';
|
|
227
|
+
const items = await navigator.clipboard.read();
|
|
228
|
+
const files = [];
|
|
229
|
+
for (const item of items) for (const type of item.types) {
|
|
230
|
+
if (type.startsWith('image/')) {
|
|
231
|
+
const blob = await item.getType(type);
|
|
232
|
+
files.push(new File([blob], 'clipboard.' + type.split('/')[1], { type }));
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
$('ocr-status-paste').textContent = '';
|
|
236
|
+
if (!files.length) { $('quick-result').innerHTML = '<p class="muted">No image on the clipboard — copy a screenshot first, then tap this again.</p>'; return; }
|
|
237
|
+
for (const f of files) await handlePastedImageFile(f);
|
|
238
|
+
} catch (err) {
|
|
239
|
+
$('ocr-status-paste').textContent = '';
|
|
240
|
+
$('quick-result').innerHTML = `<p class="error">Couldn't read the clipboard (${esc(err.message)}). Tap the dashed box above, long-press → Paste instead.</p>`;
|
|
212
241
|
}
|
|
213
242
|
});
|
|
214
243
|
|
package/public/index.html
CHANGED
|
@@ -61,7 +61,9 @@
|
|
|
61
61
|
<button class="tab" data-tab="manual">Manual</button>
|
|
62
62
|
</div>
|
|
63
63
|
<div id="tab-paste" class="tabpanel">
|
|
64
|
-
<textarea id="paste-text" rows="6" placeholder="Paste the seller's message
|
|
64
|
+
<textarea id="paste-text" rows="6" placeholder="Paste the seller's message here…"></textarea>
|
|
65
|
+
<div id="paste-box" class="pastebox" contenteditable="true" spellcheck="false">📋 <span class="muted">or tap here, then paste a screenshot</span></div>
|
|
66
|
+
<div class="row" style="margin-top:8px"><button id="btn-clipboard" class="secondary">📋 Grab screenshot from clipboard</button></div>
|
|
65
67
|
<div id="ocr-status-paste" class="muted"></div>
|
|
66
68
|
<button id="btn-quick" class="primary">Extract & add</button>
|
|
67
69
|
<div id="quick-result"></div>
|
package/public/styles.css
CHANGED
|
@@ -42,6 +42,8 @@ h2 { font-size: 17px; margin: 18px 0 8px; }
|
|
|
42
42
|
.tab.active { background: var(--blue); color: #fff; }
|
|
43
43
|
.tabpanel { margin-bottom: 12px; }
|
|
44
44
|
.filebtn { display: block; text-align: center; background: #e5e5ea; border-radius: 12px; padding: 16px; font-weight: 600; cursor: pointer; }
|
|
45
|
+
.pastebox { margin-top: 8px; border: 2px dashed #c7c7cc; border-radius: 12px; padding: 14px; text-align: center; outline: none; caret-color: transparent; min-height: 20px; }
|
|
46
|
+
.pastebox:focus { border-color: #007aff; background: #f2f8ff; }
|
|
45
47
|
.statusline { font-size: 17px; font-weight: 600; margin: 8px 0; }
|
|
46
48
|
.timeline { border-left: 2px solid #d1d1d6; margin-left: 6px; padding-left: 14px; }
|
|
47
49
|
.timeline .ev { margin-bottom: 12px; font-size: 14px; }
|
package/public/sw.js
CHANGED
|
@@ -1,11 +1,19 @@
|
|
|
1
|
-
/* Service worker: app-shell cache + web-push handling
|
|
2
|
-
|
|
1
|
+
/* Service worker: app-shell cache + web-push handling.
|
|
2
|
+
CACHE name includes the app version (injected by the server), so every
|
|
3
|
+
deploy installs a fresh shell and deletes the old one. */
|
|
4
|
+
const CACHE = 'hh-ship-__APP_VERSION__';
|
|
3
5
|
const SHELL = ['/', '/index.html', '/styles.css', '/app.js', '/extract.js', '/manifest.webmanifest'];
|
|
4
6
|
|
|
5
7
|
self.addEventListener('install', (e) => {
|
|
6
8
|
e.waitUntil(caches.open(CACHE).then((c) => c.addAll(SHELL)).then(() => self.skipWaiting()));
|
|
7
9
|
});
|
|
8
|
-
self.addEventListener('activate', (e) => {
|
|
10
|
+
self.addEventListener('activate', (e) => {
|
|
11
|
+
e.waitUntil(
|
|
12
|
+
caches.keys()
|
|
13
|
+
.then((keys) => Promise.all(keys.filter((k) => k !== CACHE).map((k) => caches.delete(k))))
|
|
14
|
+
.then(() => self.clients.claim())
|
|
15
|
+
);
|
|
16
|
+
});
|
|
9
17
|
self.addEventListener('fetch', (e) => {
|
|
10
18
|
if (e.request.method !== 'GET' || !e.request.url.startsWith(self.location.origin)) return;
|
|
11
19
|
if (e.request.url.includes('/api/')) return; // never cache API
|
package/server.js
CHANGED
|
@@ -569,6 +569,15 @@ cron.schedule('0 8 * * *', async () => {
|
|
|
569
569
|
});
|
|
570
570
|
|
|
571
571
|
/* ---------------- static frontend ---------------- */
|
|
572
|
+
// Serve sw.js dynamically so the cache name tracks the app version —
|
|
573
|
+
// every deploy busts the old app-shell cache on the next visit.
|
|
574
|
+
const APP_VERSION = require('./package.json').version;
|
|
575
|
+
app.get('/sw.js', (req, res) => {
|
|
576
|
+
fs.readFile(path.join(__dirname, 'public', 'sw.js'), 'utf8', (err, src) => {
|
|
577
|
+
if (err) return res.sendStatus(404);
|
|
578
|
+
res.type('application/javascript').set('Cache-Control', 'no-cache').send(src.replace(/__APP_VERSION__/g, APP_VERSION));
|
|
579
|
+
});
|
|
580
|
+
});
|
|
572
581
|
app.use(express.static(path.join(__dirname, 'public')));
|
|
573
582
|
app.get('*', (req, res) => res.sendFile(path.join(__dirname, 'public', 'index.html')));
|
|
574
583
|
|