cdnhost 2.5.8 → 2.6.0
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 +32 -223
- package/ws_cdnhtml.js +4 -2
- package/ws_css.css +10 -73
- package/ws_lg.js +1 -1
package/package.json
CHANGED
package/ws_cdn.js
CHANGED
|
@@ -1,15 +1,9 @@
|
|
|
1
1
|
|
|
2
2
|
document.addEventListener('DOMContentLoaded', function() {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
{ code: 'fr', name: 'Français' }, { code: 'pt', name: 'Português' }, { code: 'bn', name: 'বাংলা' },
|
|
8
|
-
{ code: 'ja', name: '日本語' }, { code: 'ru', name: 'Русский' }, { code: 'zh', name: '简体中文' },
|
|
9
|
-
{ code: 'th', name: 'ไทย' }, { code: 'vi', name: 'Tiếng Việt' }, { code: 'id', name: 'Indonesia' },
|
|
10
|
-
{ code: 'tr', name: 'Türkçe' }, { code: 'ur', name: 'اردو' }
|
|
11
|
-
];
|
|
12
|
-
|
|
3
|
+
if (window.FHLWidgetInitialized) return;
|
|
4
|
+
window.FHLWidgetInitialized = true;
|
|
5
|
+
|
|
6
|
+
const languages = [ { code: 'ko', name: '한국어' }, { code: 'en', name: 'English' }, { code: 'es', name: 'Español' }, { code: 'hi', name: 'हिन्दी' }, { code: 'ar', name: 'العربية' }, { code: 'de', name: 'Deutsch' }, { code: 'fr', name: 'Français' }, { code: 'pt', name: 'Português' }, { code: 'bn', name: 'বাংলা' }, { code: 'ja', name: '日本語' }, { code: 'ru', name: 'Русский' }, { code: 'zh', name: '简体中文' }, { code: 'th', name: 'ไทย' }, { code: 'vi', name: 'Tiếng Việt' }, { code: 'id', name: 'Indonesia' }, { code: 'tr', name: 'Türkçe' }, { code: 'ur', name: 'اردو' }];
|
|
13
7
|
let fullApiItemsData = null;
|
|
14
8
|
let currentDisplayItems = [];
|
|
15
9
|
let currentPage = 1;
|
|
@@ -98,14 +92,7 @@
|
|
|
98
92
|
});
|
|
99
93
|
};
|
|
100
94
|
|
|
101
|
-
const showToast = (message) => {
|
|
102
|
-
clearTimeout(toastTimeout);
|
|
103
|
-
toastElement.textContent = message;
|
|
104
|
-
toastElement.classList.add('visible');
|
|
105
|
-
toastTimeout = setTimeout(() => {
|
|
106
|
-
toastElement.classList.remove('visible');
|
|
107
|
-
}, 2500);
|
|
108
|
-
};
|
|
95
|
+
const showToast = (message) => { clearTimeout(toastTimeout); toastElement.textContent = message; toastElement.classList.add('visible'); toastTimeout = setTimeout(() => { toastElement.classList.remove('visible'); }, 2500); };
|
|
109
96
|
|
|
110
97
|
const applyLanguage = (langCode) => {
|
|
111
98
|
if (typeof translations === 'undefined') {
|
|
@@ -166,32 +153,22 @@
|
|
|
166
153
|
|
|
167
154
|
|
|
168
155
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
const createIconItem = (item, type = 'default') => {
|
|
156
|
+
const createIconItem = (item, type = 'default') => {
|
|
174
157
|
const link = document.createElement('a');
|
|
175
158
|
|
|
176
|
-
// ✨ [수정됨] 검색 결과 링크에도 GET 파라미터를 추가하도록 로직 변경
|
|
177
159
|
if (type === 'search') {
|
|
178
160
|
const paramString = shortcutManager.getAsParam();
|
|
179
161
|
let finalUrl = item.url;
|
|
180
162
|
if (paramString) {
|
|
181
163
|
try {
|
|
182
|
-
// URL 객체를 사용하여 기존 파라미터는 유지하고 새 파라미터 추가/수정
|
|
183
164
|
let newUrl = new URL(item.url);
|
|
184
165
|
const [key, value] = paramString.split('=');
|
|
185
166
|
newUrl.searchParams.set(key, value);
|
|
186
167
|
finalUrl = newUrl.toString();
|
|
187
|
-
} catch (e) {
|
|
188
|
-
// URL이 유효하지 않은 경우(예: javascript:void(0)) 원본 URL을 그대로 사용
|
|
189
|
-
console.error('Invalid URL for search result link:', item.url);
|
|
190
|
-
}
|
|
168
|
+
} catch (e) { console.error('Invalid URL for search result link:', item.url); }
|
|
191
169
|
}
|
|
192
170
|
link.href = finalUrl;
|
|
193
171
|
} else {
|
|
194
|
-
// 검색 결과가 아닌 다른 아이콘들은 updateAllWidgetLinks 함수가 처리하므로 원본 URL 사용
|
|
195
172
|
link.href = item.url;
|
|
196
173
|
}
|
|
197
174
|
|
|
@@ -200,11 +177,7 @@ const createIconItem = (item, type = 'default') => {
|
|
|
200
177
|
|
|
201
178
|
if (type === 'search') {
|
|
202
179
|
link.addEventListener('mousedown', () => {
|
|
203
|
-
fetch('update_view_count.php', {
|
|
204
|
-
method: 'POST',
|
|
205
|
-
headers: { 'Content-Type': 'application/json' },
|
|
206
|
-
body: JSON.stringify({ url: item.url })
|
|
207
|
-
}).catch(console.error);
|
|
180
|
+
fetch('https://isai.kr/update_view_count.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ url: item.url }) }).catch(console.error);
|
|
208
181
|
});
|
|
209
182
|
link.target = '_blank';
|
|
210
183
|
}
|
|
@@ -304,49 +277,10 @@ const createIconItem = (item, type = 'default') => {
|
|
|
304
277
|
|
|
305
278
|
|
|
306
279
|
|
|
307
|
-
const appendNextPage = () => {
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
const endIndex = currentPage * ITEMS_PER_PAGE;
|
|
312
|
-
if (startIndex >= currentDisplayItems.length) {
|
|
313
|
-
isLoadingNextPage = false;
|
|
314
|
-
return;
|
|
315
|
-
}
|
|
316
|
-
const itemsToAppend = currentDisplayItems.slice(startIndex, endIndex);
|
|
317
|
-
itemsToAppend.forEach(item => {
|
|
318
|
-
searchResultsContainer.appendChild(createIconItem(item, 'search'));
|
|
319
|
-
});
|
|
320
|
-
currentPage++;
|
|
321
|
-
isLoadingNextPage = false;
|
|
322
|
-
};
|
|
323
|
-
|
|
324
|
-
const renderInitialResults = (items) => {
|
|
325
|
-
searchResultsContainer.innerHTML = '';
|
|
326
|
-
currentPage = 1;
|
|
327
|
-
currentDisplayItems = items;
|
|
328
|
-
appendNextPage();
|
|
329
|
-
};
|
|
330
|
-
|
|
331
|
-
const openSearch = async () => {
|
|
332
|
-
widgetContainer.classList.add('search-mode');
|
|
333
|
-
widgetWrapper.classList.add('search-mode');
|
|
334
|
-
searchInput.focus();
|
|
335
|
-
if (fullApiItemsData === null) {
|
|
336
|
-
try {
|
|
337
|
-
const response = await fetch('appapi2.php');
|
|
338
|
-
if (!response.ok) throw new Error('API 응답 오류');
|
|
339
|
-
fullApiItemsData = await response.json();
|
|
340
|
-
} catch (error) {
|
|
341
|
-
console.error('전체 앱 목록 로드 실패:', error);
|
|
342
|
-
searchResultsContainer.innerHTML = '<p>목록 로드 실패</p>';
|
|
343
|
-
fullApiItemsData = [];
|
|
344
|
-
return;
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
renderInitialResults(fullApiItemsData);
|
|
348
|
-
};
|
|
349
|
-
|
|
280
|
+
const appendNextPage = () => { if (isLoadingNextPage) return; isLoadingNextPage = true; const startIndex = (currentPage - 1) * ITEMS_PER_PAGE; const endIndex = currentPage * ITEMS_PER_PAGE; if (startIndex >= currentDisplayItems.length) { isLoadingNextPage = false; return; } const itemsToAppend = currentDisplayItems.slice(startIndex, endIndex); itemsToAppend.forEach(item => { searchResultsContainer.appendChild(createIconItem(item, 'search')); }); currentPage++; isLoadingNextPage = false; };
|
|
281
|
+
const renderInitialResults = (items) => { searchResultsContainer.innerHTML = ''; currentPage = 1; currentDisplayItems = items; appendNextPage(); };
|
|
282
|
+
const openSearch = async () => { widgetContainer.classList.add('search-mode'); widgetWrapper.classList.add('search-mode'); searchInput.focus(); if (fullApiItemsData === null) { try { const response = await fetch('https://isai.kr/appapi2.php'); if (!response.ok) throw new Error('API 응답 오류'); fullApiItemsData = await response.json(); } catch (error) { console.error('전체 앱 목록 로드 실패:', error); searchResultsContainer.innerHTML = '<p>목록 로드 실패</p>'; fullApiItemsData = []; return; } } renderInitialResults(fullApiItemsData); };
|
|
283
|
+
|
|
350
284
|
const initialRender = () => {
|
|
351
285
|
const t = translations[currentLang] || translations['en'];
|
|
352
286
|
const initialItemsData = [
|
|
@@ -360,120 +294,34 @@ const createIconItem = (item, type = 'default') => {
|
|
|
360
294
|
{ name: t.psychology, url: 'https://simpong.oduc.kr/', img: 'http://cdn.jsdelivr.net/npm/cdnhost@2.2.0/_simpong.png' },
|
|
361
295
|
{ name: t.ads, url: 'https://gig.snapp.im/', icon: 'ph-currency-circle-dollar' },
|
|
362
296
|
];
|
|
363
|
-
|
|
364
297
|
listContainer.innerHTML = '';
|
|
365
298
|
const storedItems = shortcutManager.get();
|
|
366
299
|
let combinedItems = [...initialItemsData];
|
|
367
300
|
combinedItems.splice(2, 0, ...storedItems);
|
|
368
|
-
|
|
369
|
-
combinedItems.forEach(item => {
|
|
370
|
-
const isLocal = storedItems.some(stored => stored.url === item.url);
|
|
371
|
-
const itemType = isLocal && !item.icon && !item.img ? 'local' : 'default';
|
|
372
|
-
listContainer.appendChild(createIconItem(item, itemType));
|
|
373
|
-
});
|
|
374
|
-
|
|
301
|
+
combinedItems.forEach(item => { const isLocal = storedItems.some(stored => stored.url === item.url); const itemType = isLocal && !item.icon && !item.img ? 'local' : 'default'; listContainer.appendChild(createIconItem(item, itemType)); });
|
|
375
302
|
checkScrollability();
|
|
376
303
|
updateAllWidgetLinks();
|
|
377
304
|
};
|
|
378
305
|
|
|
379
|
-
const updateScrollIndicator = () => {
|
|
380
|
-
|
|
381
|
-
const maxScrollLeft = listContainer.scrollWidth - listContainer.clientWidth;
|
|
382
|
-
if (maxScrollLeft <= 0) return;
|
|
383
|
-
const scrollFraction = scrollLeft / maxScrollLeft;
|
|
384
|
-
const trackWidth = indicatorTrack.clientWidth;
|
|
385
|
-
const indicatorWidth = scrollIndicator.clientWidth;
|
|
386
|
-
const maxIndicatorLeft = trackWidth - indicatorWidth;
|
|
387
|
-
const indicatorLeft = scrollFraction * maxIndicatorLeft;
|
|
388
|
-
scrollIndicator.style.transform = `translateY(-50%) translateX(${indicatorLeft}px)`;
|
|
389
|
-
};
|
|
390
|
-
|
|
391
|
-
const checkScrollability = () => {
|
|
392
|
-
const isScrollable = listContainer.scrollWidth > listContainer.clientWidth;
|
|
393
|
-
widgetContainer.classList.toggle('scrollable', isScrollable);
|
|
394
|
-
if (isScrollable) updateScrollIndicator();
|
|
395
|
-
};
|
|
396
|
-
|
|
306
|
+
const updateScrollIndicator = () => { const scrollLeft = listContainer.scrollLeft; const maxScrollLeft = listContainer.scrollWidth - listContainer.clientWidth; if (maxScrollLeft <= 0) return; const scrollFraction = scrollLeft / maxScrollLeft; const trackWidth = indicatorTrack.clientWidth; const indicatorWidth = scrollIndicator.clientWidth; const maxIndicatorLeft = trackWidth - indicatorWidth; const indicatorLeft = scrollFraction * maxIndicatorLeft; scrollIndicator.style.transform = `translateY(-50%) translateX(${indicatorLeft}px)`; };
|
|
307
|
+
const checkScrollability = () => { const isScrollable = listContainer.scrollWidth > listContainer.clientWidth; widgetContainer.classList.toggle('scrollable', isScrollable); if (isScrollable) updateScrollIndicator(); };
|
|
397
308
|
let isDragging = false;
|
|
398
|
-
const handleDragMove = (e) => {
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
const maxScrollLeft = listContainer.scrollWidth - listContainer.clientWidth;
|
|
403
|
-
let positionRatio = (e.clientX - trackRect.left) / trackRect.width;
|
|
404
|
-
positionRatio = Math.max(0, Math.min(1, positionRatio));
|
|
405
|
-
listContainer.scrollLeft = positionRatio * maxScrollLeft;
|
|
406
|
-
};
|
|
407
|
-
const handleDragEnd = () => {
|
|
408
|
-
if (!isDragging) return;
|
|
409
|
-
isDragging = false;
|
|
410
|
-
document.removeEventListener('mousemove', handleDragMove);
|
|
411
|
-
document.removeEventListener('mouseup', handleDragEnd);
|
|
412
|
-
};
|
|
413
|
-
|
|
414
|
-
indicatorTrack.addEventListener('mousedown', (e) => {
|
|
415
|
-
isDragging = true;
|
|
416
|
-
handleDragMove(e);
|
|
417
|
-
document.addEventListener('mousemove', handleDragMove);
|
|
418
|
-
document.addEventListener('mouseup', handleDragEnd);
|
|
419
|
-
});
|
|
420
|
-
|
|
421
|
-
const closeSearch = () => {
|
|
422
|
-
widgetContainer.classList.remove('search-mode');
|
|
423
|
-
widgetWrapper.classList.remove('search-mode');
|
|
424
|
-
searchInput.value = '';
|
|
425
|
-
searchResultsContainer.innerHTML = '';
|
|
426
|
-
};
|
|
427
|
-
|
|
309
|
+
const handleDragMove = (e) => { if (!isDragging) return; e.preventDefault(); const trackRect = indicatorTrack.getBoundingClientRect(); const maxScrollLeft = listContainer.scrollWidth - listContainer.clientWidth; let positionRatio = (e.clientX - trackRect.left) / trackRect.width; positionRatio = Math.max(0, Math.min(1, positionRatio)); listContainer.scrollLeft = positionRatio * maxScrollLeft; };
|
|
310
|
+
const handleDragEnd = () => { if (!isDragging) return; isDragging = false; document.removeEventListener('mousemove', handleDragMove); document.removeEventListener('mouseup', handleDragEnd); };
|
|
311
|
+
indicatorTrack.addEventListener('mousedown', (e) => { isDragging = true; handleDragMove(e); document.addEventListener('mousemove', handleDragMove); document.addEventListener('mouseup', handleDragEnd); });
|
|
312
|
+
const closeSearch = () => { widgetContainer.classList.remove('search-mode'); widgetWrapper.classList.remove('search-mode'); searchInput.value = ''; searchResultsContainer.innerHTML = ''; };
|
|
428
313
|
let hideTimeout = null;
|
|
429
314
|
widgetContainer.addEventListener('mouseenter', () => { clearTimeout(hideTimeout); });
|
|
430
315
|
widgetContainer.addEventListener('mouseleave', () => { hideTimeout = setTimeout(() => { widgetContainer.classList.add('hidden'); }, 6000); });
|
|
431
|
-
|
|
432
|
-
document.addEventListener('click', (e) => {
|
|
433
|
-
if (!languagePanel.contains(e.target) && !languageBtn.contains(e.target)) {
|
|
434
|
-
languagePanel.classList.remove('visible');
|
|
435
|
-
}
|
|
436
|
-
if (!widgetContainer.classList.contains('hidden') || widgetContainer.contains(e.target)) {
|
|
437
|
-
return;
|
|
438
|
-
}
|
|
439
|
-
widgetContainer.classList.remove('hidden');
|
|
440
|
-
clearTimeout(hideTimeout);
|
|
441
|
-
hideTimeout = setTimeout(() => {
|
|
442
|
-
widgetContainer.classList.add('hidden');
|
|
443
|
-
}, 6000);
|
|
444
|
-
});
|
|
445
|
-
|
|
316
|
+
document.addEventListener('click', (e) => { if (!languagePanel.contains(e.target) && !languageBtn.contains(e.target)) { languagePanel.classList.remove('visible'); } if (!widgetContainer.classList.contains('hidden') || widgetContainer.contains(e.target)) { return; } widgetContainer.classList.remove('hidden'); clearTimeout(hideTimeout); hideTimeout = setTimeout(() => { widgetContainer.classList.add('hidden'); }, 6000); });
|
|
446
317
|
listContainer.addEventListener('scroll', updateScrollIndicator);
|
|
447
318
|
window.addEventListener('resize', checkScrollability);
|
|
448
|
-
|
|
449
319
|
searchTrigger.addEventListener('click', openSearch);
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
languagePanel.classList.toggle('visible');
|
|
454
|
-
});
|
|
455
|
-
|
|
456
|
-
searchInput.addEventListener('input', function() {
|
|
457
|
-
if (!fullApiItemsData) return;
|
|
458
|
-
const searchTerm = this.value.toLowerCase().trim();
|
|
459
|
-
const filteredItems = fullApiItemsData.filter(item => item.name.toLowerCase().includes(searchTerm));
|
|
460
|
-
renderInitialResults(filteredItems);
|
|
461
|
-
});
|
|
462
|
-
|
|
463
|
-
searchResultsContainer.addEventListener('scroll', () => {
|
|
464
|
-
const isAtBottom = searchResultsContainer.scrollTop + searchResultsContainer.clientHeight >= searchResultsContainer.scrollHeight - 10;
|
|
465
|
-
if (isAtBottom) {
|
|
466
|
-
appendNextPage();
|
|
467
|
-
}
|
|
468
|
-
});
|
|
469
|
-
|
|
320
|
+
languageBtn.addEventListener('click', (e) => { e.stopPropagation(); languagePanel.classList.toggle('visible'); });
|
|
321
|
+
searchInput.addEventListener('input', function() { if (!fullApiItemsData) return; const searchTerm = this.value.toLowerCase().trim(); const filteredItems = fullApiItemsData.filter(item => item.name.toLowerCase().includes(searchTerm)); renderInitialResults(filteredItems); });
|
|
322
|
+
searchResultsContainer.addEventListener('scroll', () => { const isAtBottom = searchResultsContainer.scrollTop + searchResultsContainer.clientHeight >= searchResultsContainer.scrollHeight - 10; if (isAtBottom) { appendNextPage(); } });
|
|
470
323
|
searchClose.addEventListener('click', closeSearch);
|
|
471
|
-
|
|
472
|
-
document.addEventListener('keydown', (e) => {
|
|
473
|
-
if (e.key === 'Escape' && widgetWrapper.classList.contains('search-mode')) {
|
|
474
|
-
closeSearch();
|
|
475
|
-
}
|
|
476
|
-
});
|
|
324
|
+
document.addEventListener('keydown', (e) => { if (e.key === 'Escape' && widgetWrapper.classList.contains('search-mode')) { closeSearch(); } });
|
|
477
325
|
|
|
478
326
|
const addCurrentPageToWidget = async (e) => {
|
|
479
327
|
e.preventDefault();
|
|
@@ -481,32 +329,19 @@ const createIconItem = (item, type = 'default') => {
|
|
|
481
329
|
const baseUrl = getProcessedUrl(window.location.href);
|
|
482
330
|
let iconName = document.title || t.currentPage;
|
|
483
331
|
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
|
-
}
|
|
332
|
+
const response = await fetch(`https://isai.kr/get_title.php?url=${encodeURIComponent(baseUrl)}`);
|
|
333
|
+
if (response.ok) { const data = await response.json(); if (data.title) { iconName = data.title; } }
|
|
489
334
|
} catch (error) { console.error('Failed to fetch title:', error); }
|
|
490
|
-
|
|
491
335
|
const newIcon = { name: iconName, url: baseUrl };
|
|
492
336
|
let storedItems = shortcutManager.get();
|
|
493
337
|
const isAlreadyAdded = storedItems.some(item => getProcessedUrl(item.url) === getProcessedUrl(newIcon.url));
|
|
494
|
-
|
|
495
338
|
if (!isAlreadyAdded) {
|
|
496
|
-
if (storedItems.length >= 10) {
|
|
497
|
-
storedItems.shift();
|
|
498
|
-
}
|
|
339
|
+
if (storedItems.length >= 10) { storedItems.shift(); }
|
|
499
340
|
storedItems.push(newIcon);
|
|
500
341
|
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
|
-
|
|
342
|
+
try { await fetch('https://isai.kr/register_app.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(newIcon) }); } catch (error) { console.error('DB 등록/갱신 실패:', error); }
|
|
506
343
|
initialRender();
|
|
507
|
-
if (widgetWrapper.classList.contains('search-mode')) {
|
|
508
|
-
closeSearch();
|
|
509
|
-
}
|
|
344
|
+
if (widgetWrapper.classList.contains('search-mode')) { closeSearch(); }
|
|
510
345
|
} else {
|
|
511
346
|
showToast(t.alreadyAdded);
|
|
512
347
|
}
|
|
@@ -516,41 +351,15 @@ const createIconItem = (item, type = 'default') => {
|
|
|
516
351
|
e.preventDefault();
|
|
517
352
|
const textToCopy = "<script src='https://cdn.jsdelivr.net/npm/cdnhost@latest/ws_cdn.js'><\/script>";
|
|
518
353
|
const t = translations[currentLang] || translations['en'];
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
const textArea = document.createElement("textarea");
|
|
522
|
-
textArea.value = textToCopy;
|
|
523
|
-
textArea.style.position = "fixed";
|
|
524
|
-
textArea.style.top = 0;
|
|
525
|
-
textArea.style.left = "-9999px";
|
|
526
|
-
document.body.appendChild(textArea);
|
|
527
|
-
textArea.focus();
|
|
528
|
-
textArea.select();
|
|
529
|
-
try {
|
|
530
|
-
document.execCommand('copy');
|
|
531
|
-
showToast(t.codeCopied);
|
|
532
|
-
} catch (err) {
|
|
533
|
-
console.error('Fallback: Oops, unable to copy', err);
|
|
534
|
-
}
|
|
535
|
-
document.body.removeChild(textArea);
|
|
536
|
-
};
|
|
537
|
-
|
|
538
|
-
if (navigator.clipboard && window.isSecureContext) {
|
|
539
|
-
navigator.clipboard.writeText(textToCopy).then(() => {
|
|
540
|
-
showToast(t.codeCopied);
|
|
541
|
-
}).catch(() => {
|
|
542
|
-
fallbackCopy();
|
|
543
|
-
});
|
|
544
|
-
} else {
|
|
545
|
-
fallbackCopy();
|
|
546
|
-
}
|
|
354
|
+
const fallbackCopy = () => { const textArea = document.createElement("textarea"); textArea.value = textToCopy; textArea.style.position = "fixed"; textArea.style.top = 0; textArea.style.left = "-9999px"; document.body.appendChild(textArea); textArea.focus(); textArea.select(); try { document.execCommand('copy'); showToast(t.codeCopied); } catch (err) { console.error('Fallback: Oops, unable to copy', err); } document.body.removeChild(textArea); };
|
|
355
|
+
if (navigator.clipboard && window.isSecureContext) { navigator.clipboard.writeText(textToCopy).then(() => { showToast(t.codeCopied); }).catch(() => { fallbackCopy(); }); } else { fallbackCopy(); }
|
|
547
356
|
};
|
|
548
357
|
|
|
549
358
|
addToHomeBtn.addEventListener('click', addCurrentPageToWidget);
|
|
550
359
|
searchAddBtn.addEventListener('click', copyWidgetScript);
|
|
551
360
|
|
|
552
361
|
shortcutManager.syncFromURL();
|
|
553
|
-
shortcutManager.get();
|
|
362
|
+
shortcutManager.get();
|
|
554
363
|
|
|
555
364
|
populateLanguagePanel();
|
|
556
365
|
applyLanguage(currentLang);
|
package/ws_cdnhtml.js
CHANGED
|
@@ -5,6 +5,7 @@ const appContainer = document.getElementById('app');
|
|
|
5
5
|
// 이렇게 하면 모든 id, class, 속성들이 완벽하게 보존됩니다.
|
|
6
6
|
const widgetHTML = `
|
|
7
7
|
|
|
8
|
+
|
|
8
9
|
<div id="fhl-toast-notification" class="fhl-toast"></div>
|
|
9
10
|
<div class="fhl-widget-container" id="fhl-widget-container">
|
|
10
11
|
<div class="fhl-indicator-track" id="fhl-indicator-track">
|
|
@@ -25,8 +26,8 @@ const widgetHTML = `
|
|
|
25
26
|
<div class="fhl-search-content">
|
|
26
27
|
<div class="fhl-search-results" id="fhl-search-results"></div>
|
|
27
28
|
<div class="fhl-fixed-panel">
|
|
28
|
-
<a href="#" id="fhl-search-add-btn" class="fhl-fixed-item"><i class="ph-bold ph-
|
|
29
|
-
<a href="
|
|
29
|
+
<a href="#" id="fhl-search-add-btn" class="fhl-fixed-item"><i class="ph-bold ph-download-simple"></i></a>
|
|
30
|
+
<a href="#" id="fhl-fixed-image" class="fhl-fixed-item"><i class="ph-bold ph-image"></i></a>
|
|
30
31
|
<a href="https://zoai.oduc.kr/ko/character/select" id="fhl-fixed-chat" class="fhl-fixed-item"><i class="ph-bold ph-smiley"></i></a>
|
|
31
32
|
<a href="https://gig.snapp.im/" id="fhl-fixed-ad" class="fhl-fixed-item"><i class="ph-bold ph-megaphone"></i></a>
|
|
32
33
|
</div>
|
|
@@ -36,6 +37,7 @@ const widgetHTML = `
|
|
|
36
37
|
<div id="fhl-language-panel" class="fhl-language-panel"></div>
|
|
37
38
|
</div>
|
|
38
39
|
|
|
40
|
+
|
|
39
41
|
`;
|
|
40
42
|
|
|
41
43
|
// 3. appContainer가 실제로 존재하는지 확인한 후,
|
package/ws_css.css
CHANGED
|
@@ -1,42 +1,24 @@
|
|
|
1
1
|
|
|
2
2
|
.fhl-widget-wrapper *, .fhl-widget-wrapper *::before, .fhl-widget-wrapper *::after { box-sizing: border-box; }
|
|
3
|
-
.fhl-widget-container {
|
|
4
|
-
position: fixed; z-index: 1000; bottom: 20px; left: 50%;
|
|
5
|
-
transform: translateX(-50%) translateY(0); display: flex; flex-direction: column; align-items: center;
|
|
6
|
-
width: calc(100% - 30px); max-width: 460px; pointer-events: auto;
|
|
7
|
-
transition: opacity 0.4s ease-in-out, transform 0.4s ease-in-out;
|
|
8
|
-
}
|
|
3
|
+
.fhl-widget-container { position: fixed; z-index: 1000; bottom: 20px; left: 50%; transform: translateX(-50%) translateY(0); display: flex; flex-direction: column; align-items: center; width: calc(100% - 30px); max-width: 460px; pointer-events: auto; transition: opacity 0.4s ease-in-out, transform 0.4s ease-in-out; }
|
|
9
4
|
.fhl-widget-container.hidden { opacity: 0; transform: translateX(-50%) translateY(20px); pointer-events: none; }
|
|
10
5
|
.fhl-indicator-track { position: relative; width: 100px; height: 14px; margin-bottom: 8px; cursor: pointer; background-color: transparent; transition: opacity 0.3s ease; opacity: 0; pointer-events: auto; }
|
|
11
6
|
.fhl-widget-container.scrollable .fhl-indicator-track { opacity: 1; }
|
|
12
7
|
.fhl-widget-container.search-mode .fhl-indicator-track { display: none; }
|
|
13
8
|
.fhl-scroll-indicator { position: absolute; top: 50%; left: 0; width: 12px; height: 12px; background-color: #555; border-radius: 50%; transform: translateY(-50%); transition: transform 0.1s linear; pointer-events: none; }
|
|
14
|
-
.fhl-widget-wrapper {
|
|
15
|
-
position: relative; display: flex; align-items: center; width: 100%; max-width: 460px; height: 52px; padding: 7px 10px; border-radius: 26px;
|
|
16
|
-
background-color: rgba(255, 255, 255, 0.9); backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px);
|
|
17
|
-
box-shadow: 0 5px 20px rgba(0, 0, 0, 0.2); font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
18
|
-
transition: all 0.3s ease-in-out; pointer-events: auto;
|
|
19
|
-
overflow: visible;
|
|
20
|
-
}
|
|
9
|
+
.fhl-widget-wrapper { position: relative; display: flex; align-items: center; width: 100%; max-width: 460px; height: 52px; padding: 7px 10px; border-radius: 26px; background-color: rgba(255, 255, 255, 0.9); backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px); box-shadow: 0 5px 20px rgba(0, 0, 0, 0.2); font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; transition: all 0.3s ease-in-out; pointer-events: auto; overflow: visible; }
|
|
21
10
|
.fhl-icon-display { position: relative; flex-shrink: 0; display: flex; justify-content: center; align-items: center; width: 38px; height: 38px; color: #333; background-color: rgba(0, 0, 0, 0.05); border: none; border-radius: 50%; text-decoration: none; cursor: pointer; padding: 0; transition: background-color 0.2s ease; }
|
|
22
11
|
.fhl-icon-display:hover { background-color: rgba(0, 0, 0, 0.1); }
|
|
23
12
|
.fhl-icon-display i { font-size: 22px; }
|
|
24
13
|
.fhl-list-container .fhl-icon-display { display: inline-flex; width: auto; padding: 0 8px 0 0; border-radius: 20px; background-color: transparent; }
|
|
25
14
|
.fhl-list-container .fhl-icon-display:hover { background-color: rgba(0, 0, 0, 0.08); }
|
|
26
15
|
.fhl-list-container .fhl-icon-display:hover .fhl-icon-circle { background-color: transparent; }
|
|
27
|
-
.fhl-item-name {
|
|
28
|
-
font-size: 14px; font-weight: 500; white-space: nowrap; overflow: hidden;
|
|
29
|
-
max-width: 0; opacity: 0; transition: max-width 0.3s ease-in-out, opacity 0.2s ease-in-out 0.05s;
|
|
30
|
-
}
|
|
16
|
+
.fhl-item-name { font-size: 14px; font-weight: 500; white-space: nowrap; overflow: hidden; max-width: 0; opacity: 0; transition: max-width 0.3s ease-in-out, opacity 0.2s ease-in-out 0.05s; }
|
|
31
17
|
.fhl-list-container .fhl-icon-display:hover .fhl-item-name { max-width: 150px; opacity: 1; }
|
|
32
18
|
.fhl-icon-circle { flex-shrink: 0; display: flex; justify-content: center; align-items: center; width: 38px; height: 38px; background-color: rgba(0, 0, 0, 0.05); border-radius: 50%; transition: background-color 0.2s ease; overflow: hidden; }
|
|
33
19
|
.fhl-icon-circle img { width: 24px; height: 24px; object-fit: cover; border-radius: 50%; }
|
|
34
20
|
.fhl-icon-bar { display: flex; align-items: center; gap: 8px; width: 100%; transition: opacity 0.3s ease; }
|
|
35
|
-
.fhl-list-container {
|
|
36
|
-
padding:0 8px; flex-grow: 1; display: flex; align-items: center; gap: 6px; overflow-x: auto; -ms-overflow-style: none; scrollbar-width: none; scroll-snap-type: x mandatory;
|
|
37
|
-
mask-image: linear-gradient(to right, transparent, black 20px, black calc(100% - 20px), transparent); -webkit-mask-image: linear-gradient(to right, transparent, black 20px, black calc(100% - 20px), transparent);
|
|
38
|
-
padding-bottom: 30px; margin-bottom: -30px; overflow-y: visible;
|
|
39
|
-
}
|
|
21
|
+
.fhl-list-container { padding:0 8px; flex-grow: 1; display: flex; align-items: center; gap: 6px; overflow-x: auto; -ms-overflow-style: none; scrollbar-width: none; scroll-snap-type: x mandatory; mask-image: linear-gradient(to right, transparent, black 20px, black calc(100% - 20px), transparent); -webkit-mask-image: linear-gradient(to right, transparent, black 20px, black calc(100% - 20px), transparent); padding-bottom: 30px; margin-bottom: -30px; overflow-y: visible; }
|
|
40
22
|
.fhl-list-container::-webkit-scrollbar { display: none; }
|
|
41
23
|
.fhl-list-container > .fhl-icon-display { scroll-snap-align: center; }
|
|
42
24
|
.fhl-widget-wrapper.search-mode { height: 280px; align-items: flex-start; border-radius: 20px; }
|
|
@@ -57,58 +39,13 @@
|
|
|
57
39
|
.fhl-fixed-item { display: flex; align-items: center; justify-content: center; width: 42px; height: 42px; border-radius: 10px; text-decoration: none; color: #444; cursor: pointer; transition: background-color 0.2s ease; }
|
|
58
40
|
.fhl-fixed-item:hover { background-color: rgba(0, 0, 0, 0.08); }
|
|
59
41
|
.fhl-fixed-item i { font-size: 24px; }
|
|
60
|
-
.fhl-item-controls {
|
|
61
|
-
position: absolute; bottom: -20px; left: 50%; transform: translateX(-50%); display: flex; gap: 4px;
|
|
62
|
-
background-color: rgba(0,0,0,0.7); border-radius: 10px; padding: 2px 4px; opacity: 0; visibility: hidden;
|
|
63
|
-
transition: all 0.2s ease; pointer-events: none; z-index: 20;
|
|
64
|
-
}
|
|
42
|
+
.fhl-item-controls { position: absolute; bottom: -20px; left: 50%; transform: translateX(-50%); display: flex; gap: 4px; background-color: rgba(0,0,0,0.7); border-radius: 10px; padding: 2px 4px; opacity: 0; visibility: hidden; transition: all 0.2s ease; pointer-events: none; z-index: 20; }
|
|
65
43
|
.fhl-list-container .fhl-icon-display:hover .fhl-item-controls { opacity: 1; visibility: visible; pointer-events: auto; bottom: -13px; }
|
|
66
44
|
.fhl-control-btn { background: none; border: none; color: white; cursor: pointer; font-size: 16px; line-height: 1; padding: 2px; font-family: monospace; }
|
|
67
45
|
.fhl-control-btn:hover { color: #007bff; }
|
|
68
|
-
.fhl-toast {
|
|
69
|
-
position: fixed; bottom: 90px; left: 50%; transform: translateX(-50%) translateY(10px);
|
|
70
|
-
background-color: rgba(0, 0, 0, 0.8); color: #fff; padding: 8px 16px; border-radius: 18px;
|
|
71
|
-
font-size: 14px; z-index: 1001; opacity: 0; visibility: hidden; pointer-events: none;
|
|
72
|
-
transition: opacity 0.3s ease, transform 0.3s ease, visibility 0.3s;
|
|
73
|
-
}
|
|
46
|
+
.fhl-toast { position: fixed; bottom: 90px; left: 50%; transform: translateX(-50%) translateY(10px); background-color: rgba(0, 0, 0, 0.8); color: #fff; padding: 8px 16px; border-radius: 18px; font-size: 14px; z-index: 1001; opacity: 0; visibility: hidden; pointer-events: none; transition: opacity 0.3s ease, transform 0.3s ease, visibility 0.3s; }
|
|
74
47
|
.fhl-toast.visible { opacity: 1; visibility: visible; transform: translateX(-50%) translateY(0); }
|
|
75
|
-
.fhl-language-panel {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
transform: translateX(-50%);
|
|
80
|
-
margin-bottom: 15px;
|
|
81
|
-
background-color: rgba(255, 255, 255, 0.95);
|
|
82
|
-
backdrop-filter: blur(10px);
|
|
83
|
-
-webkit-backdrop-filter: blur(10px);
|
|
84
|
-
border-radius: 12px;
|
|
85
|
-
box-shadow: 0 4px 15px rgba(0,0,0,0.2);
|
|
86
|
-
padding: 8px;
|
|
87
|
-
display: grid;
|
|
88
|
-
grid-template-columns: 1fr 1fr;
|
|
89
|
-
gap: 4px;
|
|
90
|
-
opacity: 0;
|
|
91
|
-
visibility: hidden;
|
|
92
|
-
pointer-events: none;
|
|
93
|
-
transition: opacity 0.3s ease, transform 0.3s ease;
|
|
94
|
-
transform-origin: bottom center;
|
|
95
|
-
}
|
|
96
|
-
.fhl-language-panel.visible {
|
|
97
|
-
opacity: 1;
|
|
98
|
-
visibility: visible;
|
|
99
|
-
pointer-events: auto;
|
|
100
|
-
}
|
|
101
|
-
.fhl-language-item {
|
|
102
|
-
display: block;
|
|
103
|
-
padding: 6px 12px;
|
|
104
|
-
font-size: 14px;
|
|
105
|
-
color: #333;
|
|
106
|
-
text-decoration: none;
|
|
107
|
-
border-radius: 8px;
|
|
108
|
-
transition: background-color 0.2s ease;
|
|
109
|
-
cursor: pointer;
|
|
110
|
-
text-align: center;
|
|
111
|
-
}
|
|
112
|
-
.fhl-language-item:hover {
|
|
113
|
-
background-color: rgba(0,0,0,0.1);
|
|
114
|
-
}
|
|
48
|
+
.fhl-language-panel { position: absolute; bottom: 100%; left: 50%; transform: translateX(-50%); margin-bottom: 15px; background-color: rgba(255, 255, 255, 0.95); backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px); border-radius: 12px; box-shadow: 0 4px 15px rgba(0,0,0,0.2); padding: 8px; display: grid; grid-template-columns: 1fr 1fr; gap: 4px; opacity: 0; visibility: hidden; pointer-events: none; transition: opacity 0.3s ease, transform 0.3s ease; transform-origin: bottom center; }
|
|
49
|
+
.fhl-language-panel.visible { opacity: 1; visibility: visible; pointer-events: auto; }
|
|
50
|
+
.fhl-language-item { display: block; padding: 6px 12px; font-size: 14px; color: #333; text-decoration: none; border-radius: 8px; transition: background-color 0.2s ease; cursor: pointer; text-align: center; }
|
|
51
|
+
.fhl-language-item:hover { background-color: rgba(0,0,0,0.1); }
|
package/ws_lg.js
CHANGED
|
@@ -16,5 +16,5 @@ const translations = {
|
|
|
16
16
|
vi: { search: 'Tìm kiếm', question: 'Câu hỏi', forum: 'Diễn đàn', blog: 'Blog', characterChat: 'Trò chuyện nhân vật', translate: 'Dịch', tarot: 'Tarot', psychology: 'Tâm lý học', ads: 'Quảng cáo', openSearch: 'Mở tìm kiếm', addToWidget: 'Thêm vào tiện ích', closeSearch: 'Đóng tìm kiếm', changeLanguage: 'Đổi ngôn ngữ', images: 'Hình ảnh', searchPlaceholder: 'Tìm kiếm...', alreadyAdded: 'Trang này đã được thêm vào tiện ích.', currentPage: 'Trang hiện tại', codeCopied: 'Đã sao chép mã. Vui lòng dán vào trang web của bạn.' },
|
|
17
17
|
id: { search: 'Cari', question: 'Pertanyaan', forum: 'Forum', blog: 'Blog', characterChat: 'Obrolan Karakter', translate: 'Terjemahkan', tarot: 'Tarot', psychology: 'Psikologi', ads: 'Iklan', openSearch: 'Buka Pencarian', addToWidget: 'Tambah ke Widget', closeSearch: 'Tutup Pencarian', changeLanguage: 'Ubah Bahasa', images: 'Gambar', searchPlaceholder: 'Cari...', alreadyAdded: 'Halaman ini sudah ditambahkan ke widget.', currentPage: 'Halaman Saat Ini', codeCopied: 'Kode disalin. Silakan tempel di situs Anda.' },
|
|
18
18
|
tr: { search: 'Ara', question: 'Soru', forum: 'Forum', blog: 'Blog', characterChat: 'Karakter Sohbeti', translate: 'Çevir', tarot: 'Tarot', psychology: 'Psikoloji', ads: 'Reklamlar', openSearch: 'Aramayı Aç', addToWidget: 'Widget\'a Ekle', closeSearch: 'Aramayı Kapat', changeLanguage: 'Dili Değiştir', images: 'Görseller', searchPlaceholder: 'Ara...', alreadyAdded: 'Bu sayfa zaten widget\'a eklenmiş.', currentPage: 'Mevcut Sayfa', codeCopied: 'Kod kopyalandı. Lütfen sitenize yapıştırın.' },
|
|
19
|
-
ur: { search: 'تلاش', question: 'سوال', forum: 'فورم', blog: 'بلاگ', characterChat: 'کردار چیٹ', translate: 'ترجمہ', tarot: 'ٹیرو', psychology: 'نفسیات', ads: 'اشتہارات', openSearch: 'تلاش کھولیں', addToWidget: 'ویجیٹ میں شامل کریں', closeSearch: 'تلاش بند کریں', changeLanguage: 'زبان تبدیل کریں', images: 'تصاویر', searchPlaceholder: 'تلاش کریں...', alreadyAdded: 'یہ صفحہ پہلے ہی
|
|
19
|
+
ur: { search: 'تلاش', question: 'سوال', forum: 'فورم', blog: 'بلاگ', characterChat: 'کردار چیٹ', translate: 'ترجمہ', tarot: 'ٹیرو', psychology: 'نفسیات', ads: 'اشتہارات', openSearch: 'تلاش کھولیں', addToWidget: 'ویجیٹ میں شامل کریں', closeSearch: 'تلاش بند کریں', changeLanguage: 'زبان تبدیل کریں', images: 'تصاویر', searchPlaceholder: 'تلاش کریں...', alreadyAdded: 'یہ صفحہ پہلے ہی ویجیট میں شامل ہے۔', currentPage: 'موجودہ صفحہ', codeCopied: 'کوڈ کاپی ہوگیا ہے۔ براہ کرم اسے اپنی سائٹ پر چسپاں کریں۔' }
|
|
20
20
|
};
|