@modernman00/shared-js-lib 1.2.82 โ 1.2.87
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/AddressAutocomplete.js +473 -0
- package/CardCanvasGenerator.js +367 -0
- package/OfflineQueue.js +278 -0
- package/OtpInputManager.js +429 -0
- package/PwaInstall.js +165 -0
- package/PwaManager.js +342 -0
- package/ShareManager.js +462 -0
- package/config/version.php +1 -1
- package/index.js +9 -1
- package/package.json +12 -3
- package/release-history.md +37 -0
- package/templates/sw.template.js +239 -0
package/ShareManager.js
ADDED
|
@@ -0,0 +1,462 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ShareManager.js
|
|
3
|
+
* Universal Social Share Manager with native Web Share API and glassmorphic fallback modal.
|
|
4
|
+
* Part of @modernman00/shared-js-lib
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { CardCanvasGenerator } from './CardCanvasGenerator.js';
|
|
8
|
+
|
|
9
|
+
export class ShareManager {
|
|
10
|
+
/**
|
|
11
|
+
* Smart share dispatcher: attempts native share if supported, otherwise opens the universal modal.
|
|
12
|
+
* @param {Object} options
|
|
13
|
+
* @param {string} [options.title] - Share title
|
|
14
|
+
* @param {string} [options.text] - Share description / caption
|
|
15
|
+
* @param {string} [options.url] - URL to share (defaults to window.location.href)
|
|
16
|
+
* @param {Blob|File} [options.file] - Optional image file or blob to attach to native share
|
|
17
|
+
* @param {string} [options.imageUrl] - Preview image URL for the fallback modal
|
|
18
|
+
* @param {Object} [options.cardOptions] - Options to pass to CardCanvasGenerator if generating dynamically
|
|
19
|
+
* @param {boolean} [options.forceModal=false] - Force opening the modal even if native share is supported
|
|
20
|
+
* @returns {Promise<boolean>} True if shared or modal opened successfully
|
|
21
|
+
*/
|
|
22
|
+
static async share(options = {}) {
|
|
23
|
+
const url = options.url || (typeof window !== 'undefined' ? window.location.href : '');
|
|
24
|
+
const title = options.title || (typeof document !== 'undefined' ? document.title : 'Share');
|
|
25
|
+
const text = options.text || '';
|
|
26
|
+
|
|
27
|
+
// If forced modal or running in an environment without navigator.share
|
|
28
|
+
if (!options.forceModal && typeof navigator !== 'undefined' && typeof navigator.share === 'function') {
|
|
29
|
+
try {
|
|
30
|
+
const shareData = { title, text, url };
|
|
31
|
+
|
|
32
|
+
// Attach file if provided and supported
|
|
33
|
+
if (options.file && typeof navigator.canShare === 'function') {
|
|
34
|
+
const testFile = options.file instanceof File
|
|
35
|
+
? options.file
|
|
36
|
+
: new File([options.file], 'share-card.png', { type: options.file.type || 'image/png' });
|
|
37
|
+
|
|
38
|
+
if (navigator.canShare({ files: [testFile] })) {
|
|
39
|
+
shareData.files = [testFile];
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
await navigator.share(shareData);
|
|
44
|
+
return true;
|
|
45
|
+
} catch (err) {
|
|
46
|
+
// If user cancelled, don't force modal; return false
|
|
47
|
+
if (err && (err.name === 'AbortError' || err.message?.includes('abort') || err.message?.includes('cancel'))) {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
// If native share failed for another reason, fallback to modal
|
|
51
|
+
console.warn('[ShareManager] Native share failed, falling back to modal:', err);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return this.openModal({ ...options, url, title, text });
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Opens the glassmorphic Universal Share Modal
|
|
60
|
+
* @param {Object} options
|
|
61
|
+
* @returns {HTMLElement} The modal DOM element
|
|
62
|
+
*/
|
|
63
|
+
static openModal(options = {}) {
|
|
64
|
+
if (typeof document === 'undefined') {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const modalId = 'shared-lib-universal-share-modal';
|
|
69
|
+
let modal = document.getElementById(modalId);
|
|
70
|
+
if (!modal) {
|
|
71
|
+
modal = this._createModalElement(modalId);
|
|
72
|
+
document.body.appendChild(modal);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
this._populateModal(modal, options);
|
|
76
|
+
|
|
77
|
+
// Show modal with smooth fade
|
|
78
|
+
modal.style.display = 'flex';
|
|
79
|
+
requestAnimationFrame(() => {
|
|
80
|
+
modal.style.opacity = '1';
|
|
81
|
+
const dialog = modal.querySelector('.share-dialog');
|
|
82
|
+
if (dialog) {
|
|
83
|
+
dialog.style.transform = 'scale(1) translateY(0)';
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
return modal;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Closes the active share modal
|
|
92
|
+
*/
|
|
93
|
+
static closeModal() {
|
|
94
|
+
if (typeof document === 'undefined') return;
|
|
95
|
+
const modal = document.getElementById('shared-lib-universal-share-modal');
|
|
96
|
+
if (!modal) return;
|
|
97
|
+
|
|
98
|
+
modal.style.opacity = '0';
|
|
99
|
+
const dialog = modal.querySelector('.share-dialog');
|
|
100
|
+
if (dialog) {
|
|
101
|
+
dialog.style.transform = 'scale(0.96) translateY(12px)';
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
setTimeout(() => {
|
|
105
|
+
modal.style.display = 'none';
|
|
106
|
+
}, 200);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Copies an image Blob to the user's system clipboard using ClipboardItem
|
|
111
|
+
* @param {Blob} imageBlob
|
|
112
|
+
* @returns {Promise<boolean>}
|
|
113
|
+
*/
|
|
114
|
+
static async copyImageToClipboard(imageBlob) {
|
|
115
|
+
if (typeof navigator === 'undefined' || !navigator.clipboard || !window.ClipboardItem) {
|
|
116
|
+
throw new Error('ClipboardItem image copying is not supported in this browser.');
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const item = new window.ClipboardItem({ [imageBlob.type || 'image/png']: imageBlob });
|
|
120
|
+
await navigator.clipboard.write([item]);
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Downloads a Blob or DataURL as a file
|
|
126
|
+
* @param {Blob|string} source
|
|
127
|
+
* @param {string} [filename='share-card.png']
|
|
128
|
+
*/
|
|
129
|
+
static downloadFile(source, filename = 'share-card.png') {
|
|
130
|
+
if (typeof document === 'undefined') return;
|
|
131
|
+
const link = document.createElement('a');
|
|
132
|
+
let url = '';
|
|
133
|
+
|
|
134
|
+
if (source instanceof Blob) {
|
|
135
|
+
url = typeof URL.createObjectURL === 'function' ? URL.createObjectURL(source) : '';
|
|
136
|
+
} else {
|
|
137
|
+
url = source;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
link.href = url;
|
|
141
|
+
link.download = filename;
|
|
142
|
+
document.body.appendChild(link);
|
|
143
|
+
link.click();
|
|
144
|
+
document.body.removeChild(link);
|
|
145
|
+
|
|
146
|
+
if (source instanceof Blob && typeof URL.revokeObjectURL === 'function') {
|
|
147
|
+
setTimeout(() => URL.revokeObjectURL(url), 5000);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// --- Internal DOM & Modal Construction ---
|
|
152
|
+
|
|
153
|
+
static _createModalElement(id) {
|
|
154
|
+
const modal = document.createElement('div');
|
|
155
|
+
modal.id = id;
|
|
156
|
+
modal.setAttribute('role', 'dialog');
|
|
157
|
+
modal.setAttribute('aria-modal', 'true');
|
|
158
|
+
modal.setAttribute('aria-label', 'Share');
|
|
159
|
+
|
|
160
|
+
// Overlay styles
|
|
161
|
+
modal.style.position = 'fixed';
|
|
162
|
+
modal.style.top = '0';
|
|
163
|
+
modal.style.left = '0';
|
|
164
|
+
modal.style.width = '100vw';
|
|
165
|
+
modal.style.height = '100vh';
|
|
166
|
+
modal.style.backgroundColor = 'rgba(5, 8, 16, 0.75)';
|
|
167
|
+
modal.style.backdropFilter = 'blur(10px)';
|
|
168
|
+
modal.style.webkitBackdropFilter = 'blur(10px)';
|
|
169
|
+
modal.style.zIndex = '99999';
|
|
170
|
+
modal.style.display = 'none';
|
|
171
|
+
modal.style.alignItems = 'center';
|
|
172
|
+
modal.style.justifyContent = 'center';
|
|
173
|
+
modal.style.opacity = '0';
|
|
174
|
+
modal.style.transition = 'opacity 0.2s cubic-bezier(0.16, 1, 0.3, 1)';
|
|
175
|
+
modal.style.padding = '16px';
|
|
176
|
+
modal.style.boxSizing = 'border-box';
|
|
177
|
+
|
|
178
|
+
// Click backdrop to close
|
|
179
|
+
modal.addEventListener('click', (e) => {
|
|
180
|
+
if (e.target === modal) {
|
|
181
|
+
this.closeModal();
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
// ESC key to close
|
|
186
|
+
document.addEventListener('keydown', (e) => {
|
|
187
|
+
if (e.key === 'Escape' && modal.style.display !== 'none') {
|
|
188
|
+
this.closeModal();
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
return modal;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
static _populateModal(modal, options) {
|
|
196
|
+
const url = options.url || window.location.href;
|
|
197
|
+
const title = options.title || document.title;
|
|
198
|
+
const encodedUrl = encodeURIComponent(url);
|
|
199
|
+
const encodedTitle = encodeURIComponent(title);
|
|
200
|
+
|
|
201
|
+
// Escape user-supplied strings for safety
|
|
202
|
+
const safeTitle = this._escapeHtml(title);
|
|
203
|
+
|
|
204
|
+
modal.innerHTML = `
|
|
205
|
+
<div class="share-dialog" style="
|
|
206
|
+
background: #0f172a;
|
|
207
|
+
color: #ffffff;
|
|
208
|
+
border: 1px solid rgba(255, 255, 255, 0.12);
|
|
209
|
+
border-radius: 20px;
|
|
210
|
+
max-width: 580px;
|
|
211
|
+
width: 100%;
|
|
212
|
+
max-height: 90vh;
|
|
213
|
+
overflow-y: auto;
|
|
214
|
+
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.7);
|
|
215
|
+
padding: 24px;
|
|
216
|
+
box-sizing: border-box;
|
|
217
|
+
transform: scale(0.96) translateY(12px);
|
|
218
|
+
transition: transform 0.2s cubic-bezier(0.16, 1, 0.3, 1);
|
|
219
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
220
|
+
">
|
|
221
|
+
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
|
|
222
|
+
<div>
|
|
223
|
+
<h3 style="margin: 0; font-size: 1.25rem; font-weight: 700; color: #ffffff;">Share</h3>
|
|
224
|
+
<p style="margin: 4px 0 0 0; font-size: 0.85rem; color: #94a3b8;">Distribute graphics & link with your network</p>
|
|
225
|
+
</div>
|
|
226
|
+
<button type="button" class="btn-close-modal" aria-label="Close" style="
|
|
227
|
+
background: rgba(255, 255, 255, 0.08);
|
|
228
|
+
border: none;
|
|
229
|
+
color: #94a3b8;
|
|
230
|
+
width: 44px;
|
|
231
|
+
height: 44px;
|
|
232
|
+
border-radius: 50%;
|
|
233
|
+
cursor: pointer;
|
|
234
|
+
font-size: 1.25rem;
|
|
235
|
+
display: flex;
|
|
236
|
+
align-items: center;
|
|
237
|
+
justify-content: center;
|
|
238
|
+
transition: all 0.15s ease;
|
|
239
|
+
">×</button>
|
|
240
|
+
</div>
|
|
241
|
+
|
|
242
|
+
<div class="card-preview-area" style="
|
|
243
|
+
border-radius: 12px;
|
|
244
|
+
overflow: hidden;
|
|
245
|
+
margin-bottom: 20px;
|
|
246
|
+
background: #090d16;
|
|
247
|
+
border: 1px solid rgba(255, 255, 255, 0.08);
|
|
248
|
+
display: flex;
|
|
249
|
+
align-items: center;
|
|
250
|
+
justify-content: center;
|
|
251
|
+
min-height: 180px;
|
|
252
|
+
">
|
|
253
|
+
<!-- Live Canvas or Image Preview rendered below -->
|
|
254
|
+
</div>
|
|
255
|
+
|
|
256
|
+
<div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; margin-bottom: 20px;">
|
|
257
|
+
<button type="button" class="btn-action-download" style="
|
|
258
|
+
background: rgba(255, 255, 255, 0.05);
|
|
259
|
+
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
260
|
+
border-radius: 10px;
|
|
261
|
+
color: #ffffff;
|
|
262
|
+
padding: 10px 8px;
|
|
263
|
+
font-size: 0.8rem;
|
|
264
|
+
font-weight: 600;
|
|
265
|
+
cursor: pointer;
|
|
266
|
+
min-height: 44px;
|
|
267
|
+
display: flex;
|
|
268
|
+
align-items: center;
|
|
269
|
+
justify-content: center;
|
|
270
|
+
gap: 6px;
|
|
271
|
+
">
|
|
272
|
+
<span>โฌ Download Card</span>
|
|
273
|
+
</button>
|
|
274
|
+
<button type="button" class="btn-action-copy-img" style="
|
|
275
|
+
background: rgba(255, 255, 255, 0.05);
|
|
276
|
+
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
277
|
+
border-radius: 10px;
|
|
278
|
+
color: #ffffff;
|
|
279
|
+
padding: 10px 8px;
|
|
280
|
+
font-size: 0.8rem;
|
|
281
|
+
font-weight: 600;
|
|
282
|
+
cursor: pointer;
|
|
283
|
+
min-height: 44px;
|
|
284
|
+
display: flex;
|
|
285
|
+
align-items: center;
|
|
286
|
+
justify-content: center;
|
|
287
|
+
gap: 6px;
|
|
288
|
+
">
|
|
289
|
+
<span>๐ผ Copy Image</span>
|
|
290
|
+
</button>
|
|
291
|
+
<button type="button" class="btn-action-copy-link" style="
|
|
292
|
+
background: rgba(16, 185, 129, 0.15);
|
|
293
|
+
border: 1px solid rgba(16, 185, 129, 0.4);
|
|
294
|
+
border-radius: 10px;
|
|
295
|
+
color: #10b981;
|
|
296
|
+
padding: 10px 8px;
|
|
297
|
+
font-size: 0.8rem;
|
|
298
|
+
font-weight: 600;
|
|
299
|
+
cursor: pointer;
|
|
300
|
+
min-height: 44px;
|
|
301
|
+
display: flex;
|
|
302
|
+
align-items: center;
|
|
303
|
+
justify-content: center;
|
|
304
|
+
gap: 6px;
|
|
305
|
+
">
|
|
306
|
+
<span class="copy-link-text">๐ Copy Link</span>
|
|
307
|
+
</button>
|
|
308
|
+
</div>
|
|
309
|
+
|
|
310
|
+
<div style="font-size: 0.78rem; text-transform: uppercase; letter-spacing: 0.05em; color: #64748b; font-weight: 700; margin-bottom: 10px;">
|
|
311
|
+
Share directly to platforms
|
|
312
|
+
</div>
|
|
313
|
+
|
|
314
|
+
<div style="display: grid; grid-template-columns: repeat(6, 1fr); gap: 10px;">
|
|
315
|
+
<a href="https://www.linkedin.com/sharing/share-offsite/?url=${encodedUrl}" target="_blank" rel="noopener noreferrer" style="
|
|
316
|
+
background: #0a66c2; color: #fff; text-decoration: none; border-radius: 10px; min-height: 48px;
|
|
317
|
+
display: flex; flex-direction: column; align-items: center; justify-content: center; font-size: 0.72rem; font-weight: 600;
|
|
318
|
+
">LinkedIn</a>
|
|
319
|
+
|
|
320
|
+
<a href="https://twitter.com/intent/tweet?text=${encodedTitle}&url=${encodedUrl}" target="_blank" rel="noopener noreferrer" style="
|
|
321
|
+
background: #000000; color: #fff; text-decoration: none; border: 1px solid rgba(255,255,255,0.2); border-radius: 10px; min-height: 48px;
|
|
322
|
+
display: flex; flex-direction: column; align-items: center; justify-content: center; font-size: 0.72rem; font-weight: 600;
|
|
323
|
+
">X / Twitter</a>
|
|
324
|
+
|
|
325
|
+
<a href="https://api.whatsapp.com/send?text=${encodedTitle}%20${encodedUrl}" target="_blank" rel="noopener noreferrer" style="
|
|
326
|
+
background: #25d366; color: #fff; text-decoration: none; border-radius: 10px; min-height: 48px;
|
|
327
|
+
display: flex; flex-direction: column; align-items: center; justify-content: center; font-size: 0.72rem; font-weight: 600;
|
|
328
|
+
">WhatsApp</a>
|
|
329
|
+
|
|
330
|
+
<a href="https://www.facebook.com/sharer/sharer.php?u=${encodedUrl}" target="_blank" rel="noopener noreferrer" style="
|
|
331
|
+
background: #1877f2; color: #fff; text-decoration: none; border-radius: 10px; min-height: 48px;
|
|
332
|
+
display: flex; flex-direction: column; align-items: center; justify-content: center; font-size: 0.72rem; font-weight: 600;
|
|
333
|
+
">Facebook</a>
|
|
334
|
+
|
|
335
|
+
<a href="viber://forward?text=${encodedTitle}%20${encodedUrl}" style="
|
|
336
|
+
background: #7360f2; color: #fff; text-decoration: none; border-radius: 10px; min-height: 48px;
|
|
337
|
+
display: flex; flex-direction: column; align-items: center; justify-content: center; font-size: 0.72rem; font-weight: 600;
|
|
338
|
+
">Viber</a>
|
|
339
|
+
|
|
340
|
+
<a href="sms:?&body=${encodedTitle}%20${encodedUrl}" style="
|
|
341
|
+
background: #334155; color: #fff; text-decoration: none; border-radius: 10px; min-height: 48px;
|
|
342
|
+
display: flex; flex-direction: column; align-items: center; justify-content: center; font-size: 0.72rem; font-weight: 600;
|
|
343
|
+
">SMS</a>
|
|
344
|
+
</div>
|
|
345
|
+
</div>
|
|
346
|
+
`;
|
|
347
|
+
|
|
348
|
+
// Hook Close Button
|
|
349
|
+
const closeBtn = modal.querySelector('.btn-close-modal');
|
|
350
|
+
if (closeBtn) {
|
|
351
|
+
closeBtn.onclick = () => this.closeModal();
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// Hook Preview & Actions
|
|
355
|
+
const previewContainer = modal.querySelector('.card-preview-area');
|
|
356
|
+
let activeBlob = options.file instanceof Blob ? options.file : null;
|
|
357
|
+
|
|
358
|
+
if (options.cardOptions) {
|
|
359
|
+
// Live dynamic canvas rendering
|
|
360
|
+
const canvas = document.createElement('canvas');
|
|
361
|
+
canvas.style.width = '100%';
|
|
362
|
+
canvas.style.height = 'auto';
|
|
363
|
+
canvas.style.display = 'block';
|
|
364
|
+
previewContainer.innerHTML = '';
|
|
365
|
+
previewContainer.appendChild(canvas);
|
|
366
|
+
CardCanvasGenerator.renderToCanvas(canvas, options.cardOptions);
|
|
367
|
+
canvas.toBlob((b) => {
|
|
368
|
+
activeBlob = b;
|
|
369
|
+
}, 'image/png');
|
|
370
|
+
} else if (options.imageUrl) {
|
|
371
|
+
const img = document.createElement('img');
|
|
372
|
+
img.src = options.imageUrl;
|
|
373
|
+
img.alt = safeTitle;
|
|
374
|
+
img.style.maxWidth = '100%';
|
|
375
|
+
img.style.height = 'auto';
|
|
376
|
+
img.style.display = 'block';
|
|
377
|
+
previewContainer.innerHTML = '';
|
|
378
|
+
previewContainer.appendChild(img);
|
|
379
|
+
} else {
|
|
380
|
+
previewContainer.innerHTML = `<div style="padding: 24px; text-align: center; color: #94a3b8; font-size: 0.85rem;">Ready to share: <strong>${safeTitle}</strong></div>`;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// Hook Download Action
|
|
384
|
+
const dlBtn = modal.querySelector('.btn-action-download');
|
|
385
|
+
if (dlBtn) {
|
|
386
|
+
dlBtn.onclick = async () => {
|
|
387
|
+
if (activeBlob) {
|
|
388
|
+
this.downloadFile(activeBlob, 'share-card.png');
|
|
389
|
+
} else if (options.imageUrl) {
|
|
390
|
+
this.downloadFile(options.imageUrl, 'share-card.png');
|
|
391
|
+
} else if (options.cardOptions) {
|
|
392
|
+
const b = await CardCanvasGenerator.generateBlob(options.cardOptions);
|
|
393
|
+
this.downloadFile(b, 'share-card.png');
|
|
394
|
+
}
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// Hook Copy Image Action
|
|
399
|
+
const copyImgBtn = modal.querySelector('.btn-action-copy-img');
|
|
400
|
+
if (copyImgBtn) {
|
|
401
|
+
copyImgBtn.onclick = async () => {
|
|
402
|
+
try {
|
|
403
|
+
let b = activeBlob;
|
|
404
|
+
if (!b && options.cardOptions) {
|
|
405
|
+
b = await CardCanvasGenerator.generateBlob(options.cardOptions);
|
|
406
|
+
}
|
|
407
|
+
if (b) {
|
|
408
|
+
await this.copyImageToClipboard(b);
|
|
409
|
+
copyImgBtn.innerHTML = '<span>โ
Copied!</span>';
|
|
410
|
+
setTimeout(() => {
|
|
411
|
+
copyImgBtn.innerHTML = '<span>๐ผ Copy Image</span>';
|
|
412
|
+
}, 2000);
|
|
413
|
+
} else {
|
|
414
|
+
alert('Image preview not ready to copy.');
|
|
415
|
+
}
|
|
416
|
+
} catch (err) {
|
|
417
|
+
console.warn('[ShareManager] Copy image failed:', err);
|
|
418
|
+
alert('Copy image is not supported in this browser. Please use Download Card instead.');
|
|
419
|
+
}
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// Hook Copy Link Action
|
|
424
|
+
const copyLinkBtn = modal.querySelector('.btn-action-copy-link');
|
|
425
|
+
const copyLinkText = modal.querySelector('.copy-link-text');
|
|
426
|
+
if (copyLinkBtn) {
|
|
427
|
+
copyLinkBtn.onclick = async () => {
|
|
428
|
+
try {
|
|
429
|
+
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
430
|
+
await navigator.clipboard.writeText(url);
|
|
431
|
+
} else {
|
|
432
|
+
const input = document.createElement('input');
|
|
433
|
+
input.value = url;
|
|
434
|
+
document.body.appendChild(input);
|
|
435
|
+
input.select();
|
|
436
|
+
document.execCommand('copy');
|
|
437
|
+
document.body.removeChild(input);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
if (copyLinkText) {
|
|
441
|
+
copyLinkText.textContent = 'โ
Copied!';
|
|
442
|
+
setTimeout(() => {
|
|
443
|
+
copyLinkText.textContent = '๐ Copy Link';
|
|
444
|
+
}, 2000);
|
|
445
|
+
}
|
|
446
|
+
} catch (err) {
|
|
447
|
+
console.warn('[ShareManager] Copy link failed:', err);
|
|
448
|
+
}
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
static _escapeHtml(str) {
|
|
454
|
+
if (!str) return '';
|
|
455
|
+
return String(str)
|
|
456
|
+
.replace(/&/g, '&')
|
|
457
|
+
.replace(/</g, '<')
|
|
458
|
+
.replace(/>/g, '>')
|
|
459
|
+
.replace(/"/g, '"')
|
|
460
|
+
.replace(/'/g, ''');
|
|
461
|
+
}
|
|
462
|
+
}
|
package/config/version.php
CHANGED
package/index.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
export * from './OfflineQueue.js';
|
|
2
|
+
export * from './PwaManager.js';
|
|
3
|
+
export * from './PwaInstall.js';
|
|
1
4
|
export * from './Http.js';
|
|
2
5
|
export * from './UtilityHtml.js';
|
|
3
6
|
export * from './Utility.js';
|
|
@@ -23,4 +26,9 @@ export * from './AcctMgt/update.js';
|
|
|
23
26
|
export * from './AcctMgt/registerHandler.js';
|
|
24
27
|
export * from './AcctMgt/recaptchaToken.js';
|
|
25
28
|
export * from './AcctMgt/AdminAuthForm.js';
|
|
26
|
-
export * from './general.js';
|
|
29
|
+
export * from './general.js';
|
|
30
|
+
export * from './CardCanvasGenerator.js';
|
|
31
|
+
export * from './ShareManager.js';
|
|
32
|
+
export * from './WebAuthnClient.js';
|
|
33
|
+
export * from './OtpInputManager.js';
|
|
34
|
+
export * from './AddressAutocomplete.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@modernman00/shared-js-lib",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.87",
|
|
4
4
|
"description": "Reusable JS utilities for numerous js problems",
|
|
5
5
|
"homepage": "https://github.com/modernman00/shared-js-lib#readme",
|
|
6
6
|
"keywords": [
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"url": "git+https://github.com/modernman00/shared-js-lib.git"
|
|
18
18
|
},
|
|
19
19
|
"license": "MIT",
|
|
20
|
-
"author": "
|
|
20
|
+
"author": "Core Platform Engineering Team",
|
|
21
21
|
"main": "index.js",
|
|
22
22
|
"scripts": {
|
|
23
23
|
"test": "jest"
|
|
@@ -56,11 +56,20 @@
|
|
|
56
56
|
"general.js",
|
|
57
57
|
"config/",
|
|
58
58
|
"README.md",
|
|
59
|
-
"release-history.md"
|
|
59
|
+
"release-history.md",
|
|
60
|
+
"OfflineQueue.js",
|
|
61
|
+
"PwaManager.js",
|
|
62
|
+
"PwaInstall.js",
|
|
63
|
+
"CardCanvasGenerator.js",
|
|
64
|
+
"ShareManager.js",
|
|
65
|
+
"OtpInputManager.js",
|
|
66
|
+
"AddressAutocomplete.js",
|
|
67
|
+
"templates/"
|
|
60
68
|
],
|
|
61
69
|
"devDependencies": {
|
|
62
70
|
"@babel/core": "^7.28.3",
|
|
63
71
|
"@babel/preset-env": "^7.28.3",
|
|
72
|
+
"@babel/runtime": "^8.0.5",
|
|
64
73
|
"@semantic-release/changelog": "^6.0.3",
|
|
65
74
|
"@semantic-release/git": "^10.0.1",
|
|
66
75
|
"@semantic-release/github": "^11.0.4",
|
package/release-history.md
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
|
|
2
|
+
## v1.2.86 โ 2026-10-12 22:38
|
|
3
|
+
### Changelog for v1.2.86
|
|
4
|
+
- ๐ feat: Add AddressAutocomplete class and setupAddressAutocomplete helper for zero-cost UK geocoding (Photon OSM + Postcodes.io)
|
|
5
|
+
- ๐ก๏ธ Add keyboard navigation, defensive textContent escaping (XSS immune), AbortController race protection, and full Jest test suite
|
|
6
|
+
|
|
2
7
|
## v1.2.15 โ 2025-08-18 21:56
|
|
3
8
|
### Changelog for v1.2.15
|
|
4
9
|
|
|
@@ -121,3 +126,35 @@
|
|
|
121
126
|
- ๐งน Auto-commit before release _(by modernman00)_
|
|
122
127
|
- ๐ feat: Enterprise-wide WebAuthn Biometric Login Rollout (FaceID / TouchID) across all 6 applications.
|
|
123
128
|
- ๐ sec: Enforced strict FIDO2 challenge invalidation, origin verification, and defensive degradation per TAT governance.
|
|
129
|
+
|
|
130
|
+
## v1.2.82 โ 2026-09-17 16:57
|
|
131
|
+
### Changelog for v1.2.82
|
|
132
|
+
|
|
133
|
+
- ๐งน Auto-commit before release _(by modernman00)_
|
|
134
|
+
|
|
135
|
+
## v1.2.83 โ 2026-09-21 23:38
|
|
136
|
+
### Changelog for v1.2.83
|
|
137
|
+
|
|
138
|
+
- ๐งน Auto-commit before release _(by modernman00)_
|
|
139
|
+
- ๐ Release v1.2.82 _(by modernman00)_
|
|
140
|
+
- ๐งน Auto-commit before release _(by modernman00)_
|
|
141
|
+
- ๐ Bump version to 1.2.83 _(by modernman00)_
|
|
142
|
+
- feat(pwa): centralize resilient OfflineQueue, PwaManager and standardized Service Worker template _(by modernman00)_
|
|
143
|
+
- feat: enhance release script with interactive npm login and improved workflow _(by modernman00)_
|
|
144
|
+
|
|
145
|
+
## v1.2.84 โ 2026-10-09 16:10
|
|
146
|
+
### Changelog for v1.2.84
|
|
147
|
+
|
|
148
|
+
- ๐งน Auto-commit before release _(by modernman00)_
|
|
149
|
+
- feat(otp): add OtpInputManager supporting iOS QuickType autofill and magic link auto-submit _(by modernman00)_
|
|
150
|
+
|
|
151
|
+
## v1.2.87 โ 2026-10-12 23:28
|
|
152
|
+
### Changelog for v1.2.87
|
|
153
|
+
|
|
154
|
+
- chore(release): 1.2.87 _(by github-actions)_
|
|
155
|
+
- fix(ci): add permissions contents write and continue-on-error for artifact upload _(by modernman00)_
|
|
156
|
+
- fix(ci): convert babel config to cjs and upgrade github actions to v4 with node 20 _(by modernman00)_
|
|
157
|
+
- feat: add AddressAutocomplete component (Photon + Postcodes.io) v1.2.86 _(by modernman00)_
|
|
158
|
+
- feat(pwa): harden PwaManager badging sync, support diverse envelopes, and dispatch pwa:badge-updated event _(by modernman00)_
|
|
159
|
+
- ๐ Release v1.2.84 _(by modernman00)_
|
|
160
|
+
- ๐งน Auto-commit before release _(by modernman00)_
|