hh-shipments 0.3.0 → 0.3.2
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 +50 -7
- package/public/index.html +3 -1
- package/public/styles.css +2 -2
package/package.json
CHANGED
package/public/app.js
CHANGED
|
@@ -180,21 +180,65 @@ $('photo-input').addEventListener('change', async (e) => {
|
|
|
180
180
|
await handlePhotoFile(f);
|
|
181
181
|
});
|
|
182
182
|
|
|
183
|
-
//
|
|
184
|
-
|
|
183
|
+
// Shared: pull image files out of a paste event's clipboard data
|
|
184
|
+
function pastedImageFiles(e) {
|
|
185
185
|
const dt = e.clipboardData;
|
|
186
|
-
if (!dt) return;
|
|
187
186
|
const files = [];
|
|
187
|
+
if (!dt) return files;
|
|
188
188
|
if (dt.items) for (const it of dt.items) {
|
|
189
189
|
if (it.type && it.type.startsWith('image/')) { const f = it.getAsFile(); if (f) files.push(f); }
|
|
190
190
|
}
|
|
191
191
|
if (!files.length && dt.files) for (const f of dt.files) {
|
|
192
192
|
if (f.type && f.type.startsWith('image/')) files.push(f);
|
|
193
193
|
}
|
|
194
|
-
|
|
194
|
+
return files;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Paste-text tab: pasting a screenshot runs OCR and extracts tracking numbers;
|
|
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
|
+
|
|
210
|
+
$('tab-paste').addEventListener('paste', async (e) => {
|
|
211
|
+
const files = pastedImageFiles(e);
|
|
212
|
+
if (!files.length) return; // text paste — let it land in the textarea
|
|
195
213
|
e.preventDefault();
|
|
196
|
-
$('paste-
|
|
197
|
-
for (const f of files) await
|
|
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>`;
|
|
241
|
+
}
|
|
198
242
|
});
|
|
199
243
|
|
|
200
244
|
async function loadPending() {
|
|
@@ -314,7 +358,6 @@ function urlB64(s) {
|
|
|
314
358
|
function activateTab(name) {
|
|
315
359
|
document.querySelectorAll('.tab').forEach((t) => t.classList.toggle('active', t.dataset.tab === name));
|
|
316
360
|
['paste', 'photo', 'manual'].forEach((n) => ($('tab-' + n).hidden = n !== name));
|
|
317
|
-
if (name === 'photo') { try { $('paste-zone').focus({ preventScroll: true }); } catch (_) {} }
|
|
318
361
|
}
|
|
319
362
|
document.querySelectorAll('.tab').forEach((t) => (t.onclick = () => activateTab(t.dataset.tab)));
|
|
320
363
|
document.querySelectorAll('#tabbar button').forEach((b) => {
|
package/public/index.html
CHANGED
|
@@ -62,12 +62,14 @@
|
|
|
62
62
|
</div>
|
|
63
63
|
<div id="tab-paste" class="tabpanel">
|
|
64
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>
|
|
67
|
+
<div id="ocr-status-paste" class="muted"></div>
|
|
65
68
|
<button id="btn-quick" class="primary">Extract & add</button>
|
|
66
69
|
<div id="quick-result"></div>
|
|
67
70
|
</div>
|
|
68
71
|
<div id="tab-photo" class="tabpanel" hidden>
|
|
69
72
|
<label class="filebtn">📷 Choose screenshot<input id="photo-input" type="file" accept="image/*" hidden></label>
|
|
70
|
-
<div id="paste-zone" class="pastezone" contenteditable="true" spellcheck="false" aria-label="Paste a screenshot here">📋 <span class="muted">or tap here, then paste your screenshot</span></div>
|
|
71
73
|
<div id="ocr-status" class="muted"></div>
|
|
72
74
|
<div id="photo-result"></div>
|
|
73
75
|
<h3>Pending screenshots</h3>
|
package/public/styles.css
CHANGED
|
@@ -42,8 +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
|
-
.
|
|
46
|
-
.
|
|
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; }
|
|
47
47
|
.statusline { font-size: 17px; font-weight: 600; margin: 8px 0; }
|
|
48
48
|
.timeline { border-left: 2px solid #d1d1d6; margin-left: 6px; padding-left: 14px; }
|
|
49
49
|
.timeline .ev { margin-bottom: 12px; font-size: 14px; }
|