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 CHANGED
@@ -18,7 +18,7 @@
18
18
  "test": "node test/run-tests.js && node test/run-carriers-tests.js"
19
19
  },
20
20
  "type": "commonjs",
21
- "version": "0.3.1",
21
+ "version": "0.3.3",
22
22
  "bin": {
23
23
  "hh-shipments": "server.js"
24
24
  },
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
- for (const f of files) {
204
- try {
205
- const text = await ocrFile(f, $('ocr-status-paste'));
206
- const fd = new FormData();
207
- fd.append('image', f);
208
- const up = await fetch('/api/intake/image', { method: 'POST', body: fd });
209
- if (!up.ok) throw new Error('upload failed');
210
- await runQuick(text, $('quick-result'));
211
- } catch (err) { $('quick-result').innerHTML = `<p class="error">${esc(err.message)}</p>`; }
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 or a tracking-number screenshot here…"></textarea>
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 &amp; 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
- const CACHE = 'hh-ship-1';
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) => { e.waitUntil(self.clients.claim()); });
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