cdnhost 2.5.6 → 2.5.8
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/ws_cdn.js +159 -32
package/package.json
CHANGED
package/ws_cdn.js
CHANGED
|
@@ -31,13 +31,73 @@
|
|
|
31
31
|
const languageBtn = document.getElementById('fhl-language-btn');
|
|
32
32
|
const languagePanel = document.getElementById('fhl-language-panel');
|
|
33
33
|
|
|
34
|
-
const
|
|
34
|
+
const fixedImageBtn = document.getElementById('fhl-fixed-image');
|
|
35
35
|
const fixedChatBtn = document.getElementById('fhl-fixed-chat');
|
|
36
36
|
const fixedAdBtn = document.getElementById('fhl-fixed-ad');
|
|
37
37
|
|
|
38
38
|
let toastTimeout = null;
|
|
39
39
|
let currentLang = localStorage.getItem('fhl-widget-lang') || 'ko';
|
|
40
40
|
|
|
41
|
+
const shortcutManager = {
|
|
42
|
+
key: 'fhl-custom-icons-with-expiry',
|
|
43
|
+
get: () => {
|
|
44
|
+
const rawData = localStorage.getItem(shortcutManager.key);
|
|
45
|
+
if (!rawData) return [];
|
|
46
|
+
try {
|
|
47
|
+
const data = JSON.parse(rawData);
|
|
48
|
+
const thirtyDaysInMs = 30 * 24 * 60 * 60 * 1000;
|
|
49
|
+
if (Date.now() - data.timestamp > thirtyDaysInMs) {
|
|
50
|
+
localStorage.removeItem(shortcutManager.key);
|
|
51
|
+
return [];
|
|
52
|
+
}
|
|
53
|
+
return data.shortcuts || [];
|
|
54
|
+
} catch (e) {
|
|
55
|
+
localStorage.removeItem(shortcutManager.key);
|
|
56
|
+
return [];
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
set: (shortcuts) => {
|
|
60
|
+
const dataToStore = { timestamp: Date.now(), shortcuts: shortcuts };
|
|
61
|
+
localStorage.setItem(shortcutManager.key, JSON.stringify(dataToStore));
|
|
62
|
+
},
|
|
63
|
+
syncFromURL: () => {
|
|
64
|
+
const params = new URLSearchParams(window.location.search);
|
|
65
|
+
const shortcutsParam = params.get('shortcuts');
|
|
66
|
+
if (shortcutsParam) {
|
|
67
|
+
try {
|
|
68
|
+
const decodedData = atob(shortcutsParam);
|
|
69
|
+
JSON.parse(decodedData);
|
|
70
|
+
localStorage.setItem(shortcutManager.key, decodedData);
|
|
71
|
+
} catch (e) {
|
|
72
|
+
console.error("Failed to parse shortcuts from URL:", e);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
getAsParam: () => {
|
|
77
|
+
const rawData = localStorage.getItem(shortcutManager.key);
|
|
78
|
+
if (!rawData) return '';
|
|
79
|
+
return `shortcuts=${btoa(rawData)}`;
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const updateAllWidgetLinks = () => {
|
|
84
|
+
const paramString = shortcutManager.getAsParam();
|
|
85
|
+
if (!paramString) return;
|
|
86
|
+
const allLinks = document.querySelectorAll('.fhl-icon-display, .fhl-fixed-item');
|
|
87
|
+
allLinks.forEach(link => {
|
|
88
|
+
if (link.tagName === 'A') {
|
|
89
|
+
const originalHref = link.getAttribute('href');
|
|
90
|
+
if (!originalHref || originalHref.startsWith('#')) return;
|
|
91
|
+
try {
|
|
92
|
+
let newUrl = new URL(originalHref);
|
|
93
|
+
const [key, value] = paramString.split('=');
|
|
94
|
+
newUrl.searchParams.set(key, value);
|
|
95
|
+
link.href = newUrl.toString();
|
|
96
|
+
} catch (e) { console.error('Invalid URL for link update:', originalHref); }
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
};
|
|
100
|
+
|
|
41
101
|
const showToast = (message) => {
|
|
42
102
|
clearTimeout(toastTimeout);
|
|
43
103
|
toastElement.textContent = message;
|
|
@@ -61,7 +121,7 @@
|
|
|
61
121
|
searchClose.setAttribute('aria-label', t.closeSearch);
|
|
62
122
|
languageBtn.setAttribute('aria-label', t.changeLanguage);
|
|
63
123
|
searchAddBtn.setAttribute('aria-label', 'Copy Widget Code');
|
|
64
|
-
|
|
124
|
+
fixedImageBtn.setAttribute('aria-label', t.images);
|
|
65
125
|
fixedChatBtn.setAttribute('aria-label', t.characterChat);
|
|
66
126
|
fixedAdBtn.setAttribute('aria-label', t.ads);
|
|
67
127
|
|
|
@@ -102,9 +162,39 @@
|
|
|
102
162
|
}
|
|
103
163
|
};
|
|
104
164
|
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
|
|
105
173
|
const createIconItem = (item, type = 'default') => {
|
|
106
174
|
const link = document.createElement('a');
|
|
107
|
-
|
|
175
|
+
|
|
176
|
+
// ✨ [수정됨] 검색 결과 링크에도 GET 파라미터를 추가하도록 로직 변경
|
|
177
|
+
if (type === 'search') {
|
|
178
|
+
const paramString = shortcutManager.getAsParam();
|
|
179
|
+
let finalUrl = item.url;
|
|
180
|
+
if (paramString) {
|
|
181
|
+
try {
|
|
182
|
+
// URL 객체를 사용하여 기존 파라미터는 유지하고 새 파라미터 추가/수정
|
|
183
|
+
let newUrl = new URL(item.url);
|
|
184
|
+
const [key, value] = paramString.split('=');
|
|
185
|
+
newUrl.searchParams.set(key, value);
|
|
186
|
+
finalUrl = newUrl.toString();
|
|
187
|
+
} catch (e) {
|
|
188
|
+
// URL이 유효하지 않은 경우(예: javascript:void(0)) 원본 URL을 그대로 사용
|
|
189
|
+
console.error('Invalid URL for search result link:', item.url);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
link.href = finalUrl;
|
|
193
|
+
} else {
|
|
194
|
+
// 검색 결과가 아닌 다른 아이콘들은 updateAllWidgetLinks 함수가 처리하므로 원본 URL 사용
|
|
195
|
+
link.href = item.url;
|
|
196
|
+
}
|
|
197
|
+
|
|
108
198
|
link.className = 'fhl-icon-display';
|
|
109
199
|
link.setAttribute('aria-label', item.name);
|
|
110
200
|
|
|
@@ -122,16 +212,16 @@ const createIconItem = (item, type = 'default') => {
|
|
|
122
212
|
const iconCircle = document.createElement('div');
|
|
123
213
|
iconCircle.className = 'fhl-icon-circle';
|
|
124
214
|
|
|
125
|
-
if (item.
|
|
215
|
+
if (item.icon) {
|
|
216
|
+
const icon = document.createElement('i');
|
|
217
|
+
icon.className = `ph-bold ${item.icon}`;
|
|
218
|
+
iconCircle.appendChild(icon);
|
|
219
|
+
} else if (item.img) {
|
|
126
220
|
const customImage = document.createElement('img');
|
|
127
221
|
customImage.src = item.img;
|
|
128
222
|
customImage.alt = item.name;
|
|
129
223
|
customImage.onerror = () => { customImage.style.display = 'none'; };
|
|
130
224
|
iconCircle.appendChild(customImage);
|
|
131
|
-
} else if (item.icon) {
|
|
132
|
-
const icon = document.createElement('i');
|
|
133
|
-
icon.className = `ph-bold ${item.icon}`;
|
|
134
|
-
iconCircle.appendChild(icon);
|
|
135
225
|
} else {
|
|
136
226
|
const favicon = document.createElement('img');
|
|
137
227
|
favicon.src = `https://www.google.com/s2/favicons?sz=64&domain_url=${item.url}`;
|
|
@@ -153,16 +243,9 @@ const createIconItem = (item, type = 'default') => {
|
|
|
153
243
|
currentTooltip.classList.add('visible');
|
|
154
244
|
const widgetRect = widgetWrapper.getBoundingClientRect();
|
|
155
245
|
const tooltipRect = currentTooltip.getBoundingClientRect();
|
|
156
|
-
let offsetX = 0;
|
|
157
|
-
|
|
158
|
-
if (
|
|
159
|
-
offsetX = widgetRect.left - tooltipRect.left + padding;
|
|
160
|
-
} else if (tooltipRect.right > widgetRect.right) {
|
|
161
|
-
offsetX = widgetRect.right - tooltipRect.right - padding;
|
|
162
|
-
}
|
|
163
|
-
if (offsetX !== 0) {
|
|
164
|
-
currentTooltip.style.transform = `translateX(calc(-50% + ${offsetX}px))`;
|
|
165
|
-
}
|
|
246
|
+
let offsetX = 0; const padding = 5;
|
|
247
|
+
if (tooltipRect.left < widgetRect.left) { offsetX = widgetRect.left - tooltipRect.left + padding; } else if (tooltipRect.right > widgetRect.right) { offsetX = widgetRect.right - tooltipRect.right - padding; }
|
|
248
|
+
if (offsetX !== 0) { currentTooltip.style.transform = `translateX(calc(-50% + ${offsetX}px))`; }
|
|
166
249
|
});
|
|
167
250
|
link.addEventListener('mouseleave', (e) => {
|
|
168
251
|
const currentTooltip = e.currentTarget.querySelector('.fhl-item-tooltip');
|
|
@@ -185,11 +268,10 @@ const createIconItem = (item, type = 'default') => {
|
|
|
185
268
|
deleteBtn.innerHTML = '×';
|
|
186
269
|
deleteBtn.title = '삭제';
|
|
187
270
|
deleteBtn.addEventListener('click', (e) => {
|
|
188
|
-
e.preventDefault();
|
|
189
|
-
|
|
190
|
-
let storedItems = JSON.parse(localStorage.getItem('fhl-custom-icons')) || [];
|
|
271
|
+
e.preventDefault(); e.stopPropagation();
|
|
272
|
+
let storedItems = shortcutManager.get();
|
|
191
273
|
storedItems = storedItems.filter(stored => stored.url !== item.url);
|
|
192
|
-
|
|
274
|
+
shortcutManager.set(storedItems);
|
|
193
275
|
initialRender();
|
|
194
276
|
});
|
|
195
277
|
const moveBtn = document.createElement('button');
|
|
@@ -197,15 +279,14 @@ const createIconItem = (item, type = 'default') => {
|
|
|
197
279
|
moveBtn.innerHTML = '>';
|
|
198
280
|
moveBtn.title = '오른쪽으로 이동';
|
|
199
281
|
moveBtn.addEventListener('click', (e) => {
|
|
200
|
-
e.preventDefault();
|
|
201
|
-
|
|
202
|
-
let storedItems = JSON.parse(localStorage.getItem('fhl-custom-icons')) || [];
|
|
282
|
+
e.preventDefault(); e.stopPropagation();
|
|
283
|
+
let storedItems = shortcutManager.get();
|
|
203
284
|
const currentIndex = storedItems.findIndex(stored => stored.url === item.url);
|
|
204
285
|
if (currentIndex > -1) {
|
|
205
286
|
const newIndex = (currentIndex + 1) % storedItems.length;
|
|
206
287
|
const [movedItem] = storedItems.splice(currentIndex, 1);
|
|
207
288
|
storedItems.splice(newIndex, 0, movedItem);
|
|
208
|
-
|
|
289
|
+
shortcutManager.set(storedItems);
|
|
209
290
|
initialRender();
|
|
210
291
|
}
|
|
211
292
|
});
|
|
@@ -216,6 +297,13 @@ const createIconItem = (item, type = 'default') => {
|
|
|
216
297
|
return link;
|
|
217
298
|
};
|
|
218
299
|
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
|
|
219
307
|
const appendNextPage = () => {
|
|
220
308
|
if (isLoadingNextPage) return;
|
|
221
309
|
isLoadingNextPage = true;
|
|
@@ -274,14 +362,18 @@ const createIconItem = (item, type = 'default') => {
|
|
|
274
362
|
];
|
|
275
363
|
|
|
276
364
|
listContainer.innerHTML = '';
|
|
277
|
-
const storedItems =
|
|
365
|
+
const storedItems = shortcutManager.get();
|
|
278
366
|
let combinedItems = [...initialItemsData];
|
|
279
367
|
combinedItems.splice(2, 0, ...storedItems);
|
|
368
|
+
|
|
280
369
|
combinedItems.forEach(item => {
|
|
281
370
|
const isLocal = storedItems.some(stored => stored.url === item.url);
|
|
282
|
-
|
|
371
|
+
const itemType = isLocal && !item.icon && !item.img ? 'local' : 'default';
|
|
372
|
+
listContainer.appendChild(createIconItem(item, itemType));
|
|
283
373
|
});
|
|
374
|
+
|
|
284
375
|
checkScrollability();
|
|
376
|
+
updateAllWidgetLinks();
|
|
285
377
|
};
|
|
286
378
|
|
|
287
379
|
const updateScrollIndicator = () => {
|
|
@@ -383,14 +475,46 @@ const createIconItem = (item, type = 'default') => {
|
|
|
383
475
|
}
|
|
384
476
|
});
|
|
385
477
|
|
|
478
|
+
const addCurrentPageToWidget = async (e) => {
|
|
479
|
+
e.preventDefault();
|
|
480
|
+
const t = translations[currentLang] || translations['en'];
|
|
481
|
+
const baseUrl = getProcessedUrl(window.location.href);
|
|
482
|
+
let iconName = document.title || t.currentPage;
|
|
483
|
+
try {
|
|
484
|
+
const response = await fetch(`get_title.php?url=${encodeURIComponent(baseUrl)}`);
|
|
485
|
+
if (response.ok) {
|
|
486
|
+
const data = await response.json();
|
|
487
|
+
if (data.title) { iconName = data.title; }
|
|
488
|
+
}
|
|
489
|
+
} catch (error) { console.error('Failed to fetch title:', error); }
|
|
386
490
|
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
491
|
+
const newIcon = { name: iconName, url: baseUrl };
|
|
492
|
+
let storedItems = shortcutManager.get();
|
|
493
|
+
const isAlreadyAdded = storedItems.some(item => getProcessedUrl(item.url) === getProcessedUrl(newIcon.url));
|
|
494
|
+
|
|
495
|
+
if (!isAlreadyAdded) {
|
|
496
|
+
if (storedItems.length >= 10) {
|
|
497
|
+
storedItems.shift();
|
|
498
|
+
}
|
|
499
|
+
storedItems.push(newIcon);
|
|
500
|
+
shortcutManager.set(storedItems);
|
|
501
|
+
|
|
502
|
+
try {
|
|
503
|
+
await fetch('register_app.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(newIcon) });
|
|
504
|
+
} catch (error) { console.error('DB 등록/갱신 실패:', error); }
|
|
505
|
+
|
|
506
|
+
initialRender();
|
|
507
|
+
if (widgetWrapper.classList.contains('search-mode')) {
|
|
508
|
+
closeSearch();
|
|
509
|
+
}
|
|
510
|
+
} else {
|
|
511
|
+
showToast(t.alreadyAdded);
|
|
512
|
+
}
|
|
513
|
+
};
|
|
390
514
|
|
|
391
515
|
const copyWidgetScript = (e) => {
|
|
392
516
|
e.preventDefault();
|
|
393
|
-
const textToCopy = "<script src='https://
|
|
517
|
+
const textToCopy = "<script src='https://cdn.jsdelivr.net/npm/cdnhost@latest/ws_cdn.js'><\/script>";
|
|
394
518
|
const t = translations[currentLang] || translations['en'];
|
|
395
519
|
|
|
396
520
|
const fallbackCopy = () => {
|
|
@@ -425,6 +549,9 @@ const createIconItem = (item, type = 'default') => {
|
|
|
425
549
|
addToHomeBtn.addEventListener('click', addCurrentPageToWidget);
|
|
426
550
|
searchAddBtn.addEventListener('click', copyWidgetScript);
|
|
427
551
|
|
|
552
|
+
shortcutManager.syncFromURL();
|
|
553
|
+
shortcutManager.get();
|
|
554
|
+
|
|
428
555
|
populateLanguagePanel();
|
|
429
556
|
applyLanguage(currentLang);
|
|
430
557
|
widgetContainer.classList.add('hidden');
|