cdnhost 2.7.5 → 2.7.7
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/lai128.png +0 -0
- package/lai128.svg +5 -0
- package/lai48.png +0 -0
- package/package.json +1 -1
- package/ws_cdn.js +1 -1
package/lai128.png
CHANGED
|
Binary file
|
package/lai128.svg
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="128" height="128">
|
|
3
|
+
<path d="M0 0 C4.09524968 2.43972321 5.51670856 4.55660292 7 9 C8.71979324 21.83845654 3.06260726 31.74334845 -4 42 C-3.34 42.66 -2.68 43.32 -2 44 C1.3454582 43.81752046 4.01065234 43.57445993 6.9375 41.875 C8.26191293 39.89698696 8.26191293 39.89698696 8 36.8125 C8 31.14812916 9.7327475 26.99139679 12.75 22.25 C17.68843383 17.31156617 21.2146038 15.75541544 28.125 15.6875 C31.88226012 15.78021161 35.32454956 16.25997642 39 17 C40.546875 16.9071875 40.546875 16.9071875 42.125 16.8125 C45 17 45 17 46.64770508 18.68652344 C48.22863889 21.39115041 48.42114545 23.08740357 48.51171875 26.203125 C48.54716797 27.1828125 48.58261719 28.1625 48.61914062 29.171875 C48.64169922 30.18765625 48.66425781 31.2034375 48.6875 32.25 C48.72166016 33.28125 48.75582031 34.3125 48.79101562 35.375 C48.87338093 37.91655812 48.94256834 40.45777626 49 43 C50.83700789 43.77242269 50.83700789 43.77242269 53 44 C54.8342357 42.90806924 54.8342357 42.90806924 56 41 C56.4528383 38.94715037 56.4528383 38.94715037 56.40625 36.69140625 C56.43912109 35.44713867 56.43912109 35.44713867 56.47265625 34.17773438 C56.48167969 33.31470703 56.49070312 32.45167969 56.5 31.5625 C56.59201502 22.80135557 56.59201502 22.80135557 58.30859375 19.4296875 C60 18 60 18 63.5625 17.5625 C64.696875 17.706875 65.83125 17.85125 67 18 C69.19164944 21.28747416 69.28679386 22.48494825 69.4140625 26.32421875 C69.45273438 27.35224609 69.49140625 28.38027344 69.53125 29.43945312 C69.5621875 30.51130859 69.593125 31.58316406 69.625 32.6875 C69.66367188 33.77095703 69.70234375 34.85441406 69.7421875 35.97070312 C69.83650509 38.64696467 69.92210005 41.32321843 70 44 C70.77085938 43.90460938 71.54171875 43.80921875 72.3359375 43.7109375 C73.33882813 43.60007813 74.34171875 43.48921875 75.375 43.375 C76.37273437 43.25898437 77.37046875 43.14296875 78.3984375 43.0234375 C79.68621094 43.01183594 79.68621094 43.01183594 81 43 C83 45 83 45 83.375 48.4375 C83 52 83 52 81.1875 53.875 C76.98910002 56.03417713 73.68437078 56.31758446 69 56 C66.99218823 55.01577854 64.99107248 54.01765927 63 53 C60.55070465 53.22266321 58.92334619 54.03521932 56.6875 55.0625 C53.2421361 56.26437113 50.63314289 56.31869674 47 56 C46.05125 55.443125 45.1025 54.88625 44.125 54.3125 C39.99309227 52.57709875 38.32064605 53.6552949 34.296875 55.21484375 C27.93393093 57.38993177 22.83471284 55.80515041 17 53 C14.5119723 52.07573268 13.48479871 51.80381686 10.9921875 52.8125 C10.29351562 53.204375 9.59484375 53.59625 8.875 54 C3.69096141 56.45559723 -0.48775604 56.45965671 -6 55 C-8.56632883 53.85890022 -10.65713973 52.5788841 -13 51 C-13.886875 51.825 -14.77375 52.65 -15.6875 53.5 C-17.87894978 55.3193168 -18.821014 55.96670028 -21.6875 56.5 C-24 56 -24 56 -25.8125 54.875 C-27.69781629 51.89818481 -27.73403339 50.469976 -27 47 C-24.98851444 45.13034996 -22.9209505 43.55121239 -20.65234375 42.00390625 C-18.64331142 40.04006135 -18.64331142 40.04006135 -19.23828125 36.96484375 C-19.36468994 36.35632568 -19.49109863 35.74780762 -19.62133789 35.12084961 C-20.39325683 30.7974068 -20.22645223 26.37988528 -20.25 22 C-20.270625 21.0409375 -20.29125 20.081875 -20.3125 19.09375 C-20.34454412 13.2617204 -19.84271628 9.13042605 -17 4 C-11.56790129 -1.16049378 -7.06888476 -1.60261184 0 0 Z M-7 9 C-10.57048257 12.57048257 -9.86080854 18.1409525 -10 23 C-9.67032024 26.52348549 -9.67032024 26.52348549 -9 29 C-5.0225484 24.64374349 -3.16823779 18.74788396 -2.8125 12.9375 C-2.874375 12.298125 -2.93625 11.65875 -3 11 C-4.32 10.34 -5.64 9.68 -7 9 Z M22 30 C19.66015558 32.87648225 19.66015558 32.87648225 20.125 36.4375 C21.08135521 40.33123193 21.69754692 41.76861278 25 44 C27.95691709 44.75015195 27.95691709 44.75015195 31 44 C34.02033737 42.06307631 34.02033737 42.06307631 36 39 C36.63664952 34.96788636 36.69818231 32.98262696 34.3125 29.625 C29.52004458 26.25732862 26.64806728 26.48254368 22 30 Z " fill="#000000" transform="translate(36,33)"/>
|
|
4
|
+
<path d="M0 0 C2.4375 1.5625 2.4375 1.5625 4 4 C3.9375 7.625 3.9375 7.625 3 11 C-0.46923881 13.31282587 -1.92205575 13.42478586 -6 13 C-7.875 11.9375 -7.875 11.9375 -9 10 C-9.63316583 4.09045226 -9.63316583 4.09045226 -7.875 1.0625 C-5.06487736 -0.52990283 -3.17235276 -0.45319325 0 0 Z " fill="#000000" transform="translate(102,35)"/>
|
|
5
|
+
</svg>
|
package/lai48.png
CHANGED
|
Binary file
|
package/package.json
CHANGED
package/ws_cdn.js
CHANGED
|
@@ -4,4 +4,4 @@
|
|
|
4
4
|
무단 복제, 수정, 배포를 금지합니다.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
document.addEventListener('DOMContentLoaded', function() { if (window.FHLWidgetInitialized) return; window.FHLWidgetInitialized = true; 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: 'اردو' }]; let currentDisplayItems = []; let currentPage = 1; const ITEMS_PER_PAGE = 30; let isLoadingNextPage = false; let searchTimeout = null; const widgetContainer = document.getElementById('fhl-widget-container'); const widgetWrapper = document.getElementById('fhl-widget-wrapper'); const listContainer = document.getElementById('fhl-list-container'); const searchInput = document.getElementById('fhl-search-input'); const searchResultsContainer = document.getElementById('fhl-search-results'); const searchTrigger = document.getElementById('fhl-search-trigger'); const searchClose = document.getElementById('fhl-search-close'); const indicatorTrack = document.getElementById('fhl-indicator-track'); const scrollIndicator = document.getElementById('fhl-scroll-indicator'); const addToHomeBtn = document.getElementById('fhl-add-to-home-btn'); const searchAddBtn = document.getElementById('fhl-search-add-btn'); const toastElement = document.getElementById('fhl-toast-notification'); const languageBtn = document.getElementById('fhl-language-btn'); const languagePanel = document.getElementById('fhl-language-panel'); const fixedQuestionBtn = document.getElementById('fhl-fixed-question'); const fixedChatBtn = document.getElementById('fhl-fixed-chat'); const fixedAdBtn = document.getElementById('fhl-fixed-ad'); const fixedInfoBtn = document.getElementById('fhl-fixed-info'); let toastTimeout = null; let currentLang = localStorage.getItem('fhl-widget-lang') || 'ko'; const API_BASE_URL = 'https://isai.kr'; const shortcutManager = { key: 'fhl-custom-icons-with-expiry', get: () => { const rawData = localStorage.getItem(shortcutManager.key); if (!rawData) return []; try { const data = JSON.parse(rawData); const thirtyDaysInMs = 30 * 24 * 60 * 60 * 1000; if (Date.now() - data.timestamp > thirtyDaysInMs) { localStorage.removeItem(shortcutManager.key); return []; } return Array.isArray(data.shortcuts) ? data.shortcuts.filter(item => item && item.name && item.url) : []; } catch (e) { localStorage.removeItem(shortcutManager.key); return []; } }, set: (shortcuts) => { const dataToStore = { timestamp: Date.now(), shortcuts: shortcuts }; localStorage.setItem(shortcutManager.key, JSON.stringify(dataToStore)); }, syncFromURL: () => { const params = new URLSearchParams(window.location.search); const shortcutsParam = params.get('shortcuts'); if (shortcutsParam) { try { const decodedData = decodeURIComponent(shortcutsParam); JSON.parse(decodedData); localStorage.setItem(shortcutManager.key, decodedData); } catch (e) { console.error("Failed to parse shortcuts from URL:", e); } } }, getAsParam: () => { const rawData = localStorage.getItem(shortcutManager.key); if (!rawData) return ''; return `shortcuts=${encodeURIComponent(rawData)}`; } }; const updateAllWidgetLinks = () => { const paramString = shortcutManager.getAsParam(); if (!paramString) return; const allLinks = document.querySelectorAll('#fhl-widget-container .fhl-icon-display, #fhl-widget-container .fhl-fixed-item'); allLinks.forEach(link => { if (link.tagName === 'A') { const originalHref = link.getAttribute('href'); if (!originalHref || originalHref.startsWith('#')) return; try { let newUrl = new URL(originalHref); const [key, value] = paramString.split('='); newUrl.searchParams.set(key, value); link.href = newUrl.toString(); } catch (e) { console.error('Invalid URL for link update:', originalHref); } } }); }; const showToast = (message) => { clearTimeout(toastTimeout); toastElement.textContent = message; toastElement.classList.add('visible'); toastTimeout = setTimeout(() => { toastElement.classList.remove('visible'); }, 2500); }; const applyLanguage = (langCode) => { if (typeof translations === 'undefined') { console.error("Translations not loaded! Make sure lang.js is included correctly."); return; } const t = translations[langCode] || translations['en']; document.documentElement.lang = langCode; searchTrigger.setAttribute('aria-label', t.openSearch); addToHomeBtn.setAttribute('aria-label', t.addToWidget); searchClose.setAttribute('aria-label', t.closeSearch); languageBtn.setAttribute('aria-label', t.changeLanguage); searchAddBtn.setAttribute('aria-label', 'Copy Widget Code'); fixedQuestionBtn.setAttribute('aria-label', t.question); fixedChatBtn.setAttribute('aria-label', t.characterChat); fixedAdBtn.setAttribute('aria-label', t.ads); fixedInfoBtn.setAttribute('aria-label', t.info); searchInput.placeholder = t.searchPlaceholder; initialRender(); }; const populateLanguagePanel = () => { languagePanel.innerHTML = ''; languages.forEach(lang => { const langItem = document.createElement('a'); langItem.href = '#'; langItem.className = 'fhl-language-item'; langItem.textContent = lang.name; langItem.dataset.lang = lang.code; langItem.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); const newLang = e.target.dataset.lang; if (newLang !== currentLang) { currentLang = newLang; localStorage.setItem('fhl-widget-lang', currentLang); applyLanguage(currentLang); } languagePanel.classList.remove('visible'); }); languagePanel.appendChild(langItem); }); }; const getProcessedUrl = (fullUrl) => { try { const urlObj = new URL(fullUrl); return `${urlObj.protocol}//${urlObj.hostname}`; } catch (e) { return fullUrl; } }; const createIconItem = (item, type = 'default') => { if (!item || !item.name || !item.url) return document.createDocumentFragment(); const link = document.createElement('a'); if (type === 'search') { const paramString = shortcutManager.getAsParam(); let finalUrl = item.url; if (paramString) { try { let newUrl = new URL(item.url); const [key, value] = paramString.split('='); newUrl.searchParams.set(key, value); finalUrl = newUrl.toString(); } catch (e) { console.error('Invalid URL for search result link:', item.url); } } link.href = finalUrl; } else { link.href = item.url; } link.className = 'fhl-icon-display'; link.setAttribute('aria-label', item.name); if (type === 'search') { link.addEventListener('mousedown', () => { fetch(`${API_BASE_URL}/update_view_count.php`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ url: item.url }) }).catch(console.error); }); link.target = '_blank'; } const iconCircle = document.createElement('div'); iconCircle.className = 'fhl-icon-circle'; if (item.icon) { const icon = document.createElement('i'); icon.className = `ph-bold ${item.icon}`; iconCircle.appendChild(icon); } else if (item.img) { const customImage = document.createElement('img'); customImage.src = item.img; customImage.alt = item.name; customImage.onerror = () => { customImage.style.display = 'none'; }; iconCircle.appendChild(customImage); } else { const favicon = document.createElement('img'); favicon.src = `https://www.google.com/s2/favicons?sz=64&domain_url=${item.url}`; favicon.alt = item.name; favicon.onerror = () => { favicon.style.display = 'none'; }; iconCircle.appendChild(favicon); } link.appendChild(iconCircle); if (type === 'search') { const tooltip = document.createElement('div'); tooltip.className = 'fhl-item-tooltip'; tooltip.textContent = item.name.length > 5 ? item.name.slice(0, 5) + '..' : item.name; link.appendChild(tooltip); link.addEventListener('mouseenter', (e) => { const currentTooltip = e.currentTarget.querySelector('.fhl-item-tooltip'); if (!currentTooltip) return; currentTooltip.classList.add('visible'); const widgetRect = widgetWrapper.getBoundingClientRect(); const tooltipRect = currentTooltip.getBoundingClientRect(); let offsetX = 0; const padding = 5; if (tooltipRect.left < widgetRect.left) { offsetX = widgetRect.left - tooltipRect.left + padding; } else if (tooltipRect.right > widgetRect.right) { offsetX = widgetRect.right - tooltipRect.right - padding; } if (offsetX !== 0) { currentTooltip.style.transform = `translateX(calc(-50% + ${offsetX}px))`; } }); link.addEventListener('mouseleave', (e) => { const currentTooltip = e.currentTarget.querySelector('.fhl-item-tooltip'); if (!currentTooltip) return; currentTooltip.classList.remove('visible'); currentTooltip.style.transform = 'translateX(-50%)'; }); } else { const nameSpan = document.createElement('span'); nameSpan.className = 'fhl-item-name'; nameSpan.textContent = item.name.length > 5 ? item.name.slice(0, 5) + '..' : item.name; link.appendChild(nameSpan); } if (type === 'local') { const controls = document.createElement('div'); controls.className = 'fhl-item-controls'; const deleteBtn = document.createElement('button'); deleteBtn.className = 'fhl-control-btn'; deleteBtn.innerHTML = '×'; deleteBtn.title = '삭제'; deleteBtn.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); let storedItems = shortcutManager.get(); storedItems = storedItems.filter(stored => stored.url !== item.url); shortcutManager.set(storedItems); initialRender(); }); const moveBtn = document.createElement('button'); moveBtn.className = 'fhl-control-btn'; moveBtn.innerHTML = '>'; moveBtn.title = '오른쪽으로 이동'; moveBtn.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); let storedItems = shortcutManager.get(); const currentIndex = storedItems.findIndex(stored => stored.url === item.url); if (currentIndex > -1) { const newIndex = (currentIndex + 1) % storedItems.length; const [movedItem] = storedItems.splice(currentIndex, 1); storedItems.splice(newIndex, 0, movedItem); shortcutManager.set(storedItems); initialRender(); } }); controls.appendChild(deleteBtn); controls.appendChild(moveBtn); link.appendChild(controls); } return link; }; 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; }; const renderInitialResults = (items) => { searchResultsContainer.innerHTML = ''; currentPage = 1; currentDisplayItems = Array.isArray(items) ? items : []; appendNextPage(); }; const performSearch = async (query = '') => { try { const response = await fetch(`${API_BASE_URL}/appapi2.php?query=${encodeURIComponent(query)}`); if (!response.ok) throw new Error('API 응답 오류'); const results = await response.json(); renderInitialResults(results); } catch (error) { console.error('앱 목록 로드 실패:', error); searchResultsContainer.innerHTML = '<p>목록 로드 실패</p>'; } }; const openSearch = () => { widgetContainer.classList.add('search-mode'); widgetWrapper.classList.add('search-mode'); searchInput.focus(); performSearch(); }; const initialRender = () => { const t = translations[currentLang] || translations['en']; const b = [{ name: t.search, url: 'https://isai.kr', icon: 'ph-sparkle' }, { name: t.question, url: 'https://isai.kr/#chat', icon: 'ph-question-mark' }, { name: t.blog, url: 'https://blog.099.kr', icon: 'ph-article-medium' }, { name: t.novel, url: 'https://ranovel.kr/', img: 'https://cdn.jsdelivr.net/npm/cdnhost@2.2.2/__ra.png' }, { name: t.characterChat, url: 'https://zoai.oduc.kr/ko/character/select', icon: 'ph-chats-circle' }, { name: t.translate, url: 'https://translato.isai.kr/', icon: 'ph-translate' }, { name: t.tarot, url: 'https://tarot.isai.kr/', icon: 'ph-cards' }, { name: t.psychology, url: 'https://simpong.oduc.kr/', img: '//cdn.jsdelivr.net/npm/cdnhost@2.2.0/_simpong.png' }, { name: t.forum, url: 'https://logig.im', img: 'https://cdn.jsdelivr.net/npm/cdnhost@2.3.9/_______________logig.png' }, { name: t.ads, url: 'https://gig.snapp.im/', icon: 'ph-currency-circle-dollar' }]; listContainer.innerHTML = ''; const c = shortcutManager.get(); let d = [...b]; d.splice(2, 0, ...c); d.forEach(e => { const f = c.some(g => g.url === e.url); const h = f && !e.icon && !e.img ? 'local' : 'default'; listContainer.appendChild(createIconItem(e, h)); }); checkScrollability(); updateAllWidgetLinks(); }; 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)`; }; const checkScrollability = () => { const isScrollable = listContainer.scrollWidth > listContainer.clientWidth; widgetContainer.classList.toggle('scrollable', isScrollable); if (isScrollable) updateScrollIndicator(); }; let isDragging = false; 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; }; const handleDragEnd = () => { if (!isDragging) return; isDragging = false; document.removeEventListener('mousemove', handleDragMove); document.removeEventListener('mouseup', handleDragEnd); }; indicatorTrack.addEventListener('mousedown', (e) => { isDragging = true; handleDragMove(e); document.addEventListener('mousemove', handleDragMove); document.addEventListener('mouseup', handleDragEnd); }); const closeSearch = () => { widgetContainer.classList.remove('search-mode'); widgetWrapper.classList.remove('search-mode'); searchInput.value = ''; searchResultsContainer.innerHTML = ''; }; let hideTimeout = null; widgetContainer.addEventListener('mouseenter', () => { clearTimeout(hideTimeout); }); widgetContainer.addEventListener('mouseleave', () => { hideTimeout = setTimeout(() => { widgetContainer.classList.add('hidden'); }, 6000); }); document.addEventListener('click', (e) => { if (!languagePanel.contains(e.target) && !languageBtn.contains(e.target)) { languagePanel.classList.remove('visible'); } if (widgetContainer.contains(e.target)) { return; } if (widgetContainer.classList.contains('hidden')) { setTimeout(() => { widgetContainer.classList.remove('hidden'); clearTimeout(hideTimeout); hideTimeout = setTimeout(() => { widgetContainer.classList.add('hidden'); }, 6000); }, 50); } }); listContainer.addEventListener('scroll', updateScrollIndicator); window.addEventListener('resize', checkScrollability); searchTrigger.addEventListener('click', openSearch); languageBtn.addEventListener('click', (e) => { e.stopPropagation(); languagePanel.classList.toggle('visible'); }); searchInput.addEventListener('input', function() { clearTimeout(searchTimeout); const query = this.value.toLowerCase().trim(); searchTimeout = setTimeout(() => { performSearch(query); }, 300); }); searchResultsContainer.addEventListener('scroll', () => { const isAtBottom = searchResultsContainer.scrollTop + searchResultsContainer.clientHeight >= searchResultsContainer.scrollHeight - 10; if (isAtBottom) { appendNextPage(); } }); searchClose.addEventListener('click', closeSearch); document.addEventListener('keydown', (e) => { if (e.key === 'Escape' && widgetWrapper.classList.contains('search-mode')) { closeSearch(); } }); const addCurrentPageToWidget = async (e) => { e.preventDefault(); const t = translations[currentLang] || translations['en']; const baseUrl = getProcessedUrl(window.location.href); let iconName = document.title || t.currentPage; try { const response = await fetch(`${API_BASE_URL}/get_title.php?url=${encodeURIComponent(baseUrl)}`); if (response.ok) { const data = await response.json(); if (data.title) { iconName = data.title; } } } catch (error) { console.error('Failed to fetch title:', error); } const newIcon = { name: iconName, url: baseUrl }; let storedItems = shortcutManager.get(); const isAlreadyAdded = storedItems.some(item => getProcessedUrl(item.url) === getProcessedUrl(newIcon.url)); if (!isAlreadyAdded) { if (storedItems.length >= 10) { storedItems.shift(); } storedItems.push(newIcon); shortcutManager.set(storedItems); try { await fetch(`${API_BASE_URL}/register_app.php`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(newIcon) }); } catch (error) { console.error('DB 등록/갱신 실패:', error); } initialRender(); if (widgetWrapper.classList.contains('search-mode')) { closeSearch(); } } else { showToast(t.alreadyAdded); } }; const copyWidgetScript = (e) => { e.preventDefault(); const textToCopy = "<script src='https://cdn.jsdelivr.net/npm/cdnhost@latest/ws_cdn.js'><\/script>"; const t = translations[currentLang] || translations['en']; 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); }; if (navigator.clipboard && window.isSecureContext) { navigator.clipboard.writeText(textToCopy).then(() => { showToast(t.codeCopied); }).catch(() => { fallbackCopy(); }); } else { fallbackCopy(); } }; addToHomeBtn.addEventListener('click', addCurrentPageToWidget); searchAddBtn.addEventListener('click', copyWidgetScript); shortcutManager.syncFromURL(); shortcutManager.get(); populateLanguagePanel(); applyLanguage(currentLang); });
|
|
7
|
+
document.addEventListener('DOMContentLoaded', function() { if (window.FHLWidgetInitialized) return; window.FHLWidgetInitialized = true; 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: 'اردو' }]; let currentDisplayItems = []; let currentPage = 1; const ITEMS_PER_PAGE = 30; let isLoadingNextPage = false; let searchTimeout = null; const widgetContainer = document.getElementById('fhl-widget-container'); const widgetWrapper = document.getElementById('fhl-widget-wrapper'); const listContainer = document.getElementById('fhl-list-container'); const searchInput = document.getElementById('fhl-search-input'); const searchResultsContainer = document.getElementById('fhl-search-results'); const searchTrigger = document.getElementById('fhl-search-trigger'); const searchClose = document.getElementById('fhl-search-close'); const indicatorTrack = document.getElementById('fhl-indicator-track'); const scrollIndicator = document.getElementById('fhl-scroll-indicator'); const addToHomeBtn = document.getElementById('fhl-add-to-home-btn'); const searchAddBtn = document.getElementById('fhl-search-add-btn'); const toastElement = document.getElementById('fhl-toast-notification'); const languageBtn = document.getElementById('fhl-language-btn'); const languagePanel = document.getElementById('fhl-language-panel'); const fixedQuestionBtn = document.getElementById('fhl-fixed-question'); const fixedChatBtn = document.getElementById('fhl-fixed-chat'); const fixedAdBtn = document.getElementById('fhl-fixed-ad'); const fixedInfoBtn = document.getElementById('fhl-fixed-info'); let toastTimeout = null; let currentLang = localStorage.getItem('fhl-widget-lang') || 'ko'; const API_BASE_URL = 'https://isai.kr'; const shortcutManager = { key: 'fhl-custom-icons-with-expiry', get: () => { const rawData = localStorage.getItem(shortcutManager.key); if (!rawData) return []; try { const data = JSON.parse(rawData); const thirtyDaysInMs = 30 * 24 * 60 * 60 * 1000; if (Date.now() - data.timestamp > thirtyDaysInMs) { localStorage.removeItem(shortcutManager.key); return []; } return Array.isArray(data.shortcuts) ? data.shortcuts.filter(item => item && item.name && item.url) : []; } catch (e) { localStorage.removeItem(shortcutManager.key); return []; } }, set: (shortcuts) => { const dataToStore = { timestamp: Date.now(), shortcuts: shortcuts }; localStorage.setItem(shortcutManager.key, JSON.stringify(dataToStore)); }, syncFromURL: () => { const params = new URLSearchParams(window.location.search); const shortcutsParam = params.get('shortcuts'); if (shortcutsParam) { try { const decodedData = decodeURIComponent(shortcutsParam); JSON.parse(decodedData); localStorage.setItem(shortcutManager.key, decodedData); } catch (e) { console.error("Failed to parse shortcuts from URL:", e); } } }, getAsParam: () => { const rawData = localStorage.getItem(shortcutManager.key); if (!rawData) return ''; return `shortcuts=${encodeURIComponent(rawData)}`; } }; const updateAllWidgetLinks = () => { const paramString = shortcutManager.getAsParam(); if (!paramString) return; const allLinks = document.querySelectorAll('#fhl-widget-container .fhl-icon-display, #fhl-widget-container .fhl-fixed-item'); allLinks.forEach(link => { if (link.tagName === 'A') { const originalHref = link.getAttribute('href'); if (!originalHref || originalHref.startsWith('#')) return; try { let newUrl = new URL(originalHref); const [key, value] = paramString.split('='); newUrl.searchParams.set(key, value); link.href = newUrl.toString(); } catch (e) { console.error('Invalid URL for link update:', originalHref); } } }); }; const showToast = (message) => { clearTimeout(toastTimeout); toastElement.textContent = message; toastElement.classList.add('visible'); toastTimeout = setTimeout(() => { toastElement.classList.remove('visible'); }, 2500); }; const applyLanguage = (langCode) => { if (typeof translations === 'undefined') { console.error("Translations not loaded! Make sure lang.js is included correctly."); return; } const t = translations[langCode] || translations['en']; document.documentElement.lang = langCode; searchTrigger.setAttribute('aria-label', t.openSearch); addToHomeBtn.setAttribute('aria-label', t.addToWidget); searchClose.setAttribute('aria-label', t.closeSearch); languageBtn.setAttribute('aria-label', t.changeLanguage); searchAddBtn.setAttribute('aria-label', 'Copy Widget Code'); fixedQuestionBtn.setAttribute('aria-label', t.question); fixedChatBtn.setAttribute('aria-label', t.characterChat); fixedAdBtn.setAttribute('aria-label', t.ads); fixedInfoBtn.setAttribute('aria-label', t.info); searchInput.placeholder = t.searchPlaceholder; initialRender(); }; const populateLanguagePanel = () => { languagePanel.innerHTML = ''; languages.forEach(lang => { const langItem = document.createElement('a'); langItem.href = '#'; langItem.className = 'fhl-language-item'; langItem.textContent = lang.name; langItem.dataset.lang = lang.code; langItem.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); const newLang = e.target.dataset.lang; if (newLang !== currentLang) { currentLang = newLang; localStorage.setItem('fhl-widget-lang', currentLang); applyLanguage(currentLang); } languagePanel.classList.remove('visible'); }); languagePanel.appendChild(langItem); }); }; const getProcessedUrl = (fullUrl) => { try { const urlObj = new URL(fullUrl); return `${urlObj.protocol}//${urlObj.hostname}`; } catch (e) { return fullUrl; } }; const createIconItem = (item, type = 'default') => { if (!item || !item.name || !item.url) return document.createDocumentFragment(); const link = document.createElement('a'); if (type === 'search') { const paramString = shortcutManager.getAsParam(); let finalUrl = item.url; if (paramString) { try { let newUrl = new URL(item.url); const [key, value] = paramString.split('='); newUrl.searchParams.set(key, value); finalUrl = newUrl.toString(); } catch (e) { console.error('Invalid URL for search result link:', item.url); } } link.href = finalUrl; } else { link.href = item.url; } link.className = 'fhl-icon-display'; link.setAttribute('aria-label', item.name); if (type === 'search') { link.addEventListener('mousedown', () => { fetch(`${API_BASE_URL}/update_view_count.php`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ url: item.url }) }).catch(console.error); }); link.target = '_blank'; } const iconCircle = document.createElement('div'); iconCircle.className = 'fhl-icon-circle'; if (item.icon) { const icon = document.createElement('i'); icon.className = `ph-bold ${item.icon}`; iconCircle.appendChild(icon); } else if (item.img) { const customImage = document.createElement('img'); customImage.src = item.img; customImage.alt = item.name; customImage.onerror = () => { customImage.style.display = 'none'; }; iconCircle.appendChild(customImage); } else { const favicon = document.createElement('img'); favicon.src = `https://www.google.com/s2/favicons?sz=64&domain_url=${item.url}`; favicon.alt = item.name; favicon.onerror = () => { favicon.style.display = 'none'; }; iconCircle.appendChild(favicon); } link.appendChild(iconCircle); if (type === 'search') { const tooltip = document.createElement('div'); tooltip.className = 'fhl-item-tooltip'; tooltip.textContent = item.name.length > 5 ? item.name.slice(0, 5) + '..' : item.name; link.appendChild(tooltip); link.addEventListener('mouseenter', (e) => { const currentTooltip = e.currentTarget.querySelector('.fhl-item-tooltip'); if (!currentTooltip) return; currentTooltip.classList.add('visible'); const widgetRect = widgetWrapper.getBoundingClientRect(); const tooltipRect = currentTooltip.getBoundingClientRect(); let offsetX = 0; const padding = 5; if (tooltipRect.left < widgetRect.left) { offsetX = widgetRect.left - tooltipRect.left + padding; } else if (tooltipRect.right > widgetRect.right) { offsetX = widgetRect.right - tooltipRect.right - padding; } if (offsetX !== 0) { currentTooltip.style.transform = `translateX(calc(-50% + ${offsetX}px))`; } }); link.addEventListener('mouseleave', (e) => { const currentTooltip = e.currentTarget.querySelector('.fhl-item-tooltip'); if (!currentTooltip) return; currentTooltip.classList.remove('visible'); currentTooltip.style.transform = 'translateX(-50%)'; }); } else { const nameSpan = document.createElement('span'); nameSpan.className = 'fhl-item-name'; nameSpan.textContent = item.name.length > 5 ? item.name.slice(0, 5) + '..' : item.name; link.appendChild(nameSpan); } if (type === 'local') { const controls = document.createElement('div'); controls.className = 'fhl-item-controls'; const deleteBtn = document.createElement('button'); deleteBtn.className = 'fhl-control-btn'; deleteBtn.innerHTML = '×'; deleteBtn.title = '삭제'; deleteBtn.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); let storedItems = shortcutManager.get(); storedItems = storedItems.filter(stored => stored.url !== item.url); shortcutManager.set(storedItems); initialRender(); }); const moveBtn = document.createElement('button'); moveBtn.className = 'fhl-control-btn'; moveBtn.innerHTML = '>'; moveBtn.title = '오른쪽으로 이동'; moveBtn.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); let storedItems = shortcutManager.get(); const currentIndex = storedItems.findIndex(stored => stored.url === item.url); if (currentIndex > -1) { const newIndex = (currentIndex + 1) % storedItems.length; const [movedItem] = storedItems.splice(currentIndex, 1); storedItems.splice(newIndex, 0, movedItem); shortcutManager.set(storedItems); initialRender(); } }); controls.appendChild(deleteBtn); controls.appendChild(moveBtn); link.appendChild(controls); } return link; }; 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; }; const renderInitialResults = (items) => { searchResultsContainer.innerHTML = ''; currentPage = 1; currentDisplayItems = Array.isArray(items) ? items : []; appendNextPage(); }; const performSearch = async (query = '') => { try { const response = await fetch(`${API_BASE_URL}/appapi2.php?query=${encodeURIComponent(query)}`); if (!response.ok) throw new Error('API 응답 오류'); const results = await response.json(); renderInitialResults(results); } catch (error) { console.error('앱 목록 로드 실패:', error); searchResultsContainer.innerHTML = '<p>목록 로드 실패</p>'; } }; const openSearch = () => { widgetContainer.classList.add('search-mode'); widgetWrapper.classList.add('search-mode'); searchInput.focus(); performSearch(); }; const initialRender = () => { const t = translations[currentLang] || translations['en']; const b = [{ name: t.search, url: 'https://isai.kr', icon: 'ph-sparkle' }, { name: t.question, url: 'https://isai.kr/#chat', icon: 'ph-question-mark' }, { name: t.blog, url: 'https://blog.099.kr', icon: 'ph-article-medium' }, { name: t.characterChat, url: 'https://lai.oduc.kr/', img: 'https://cdn.jsdelivr.net/npm/cdnhost@2.7.6/lai48.png' },{ name: t.translate, url: 'https://translato.isai.kr/', icon: 'ph-translate' }, { name: t.tarot, url: 'https://tarot.isai.kr/', icon: 'ph-cards' }, { name: t.novel, url: 'https://ranovel.kr/', img: 'https://cdn.jsdelivr.net/npm/cdnhost@2.2.2/__ra.png' }, { name: t.psychology, url: 'https://simpong.oduc.kr/', img: '//cdn.jsdelivr.net/npm/cdnhost@2.2.0/_simpong.png' }, { name: t.forum, url: 'https://logig.im', img: 'https://cdn.jsdelivr.net/npm/cdnhost@2.3.9/_______________logig.png' }, { name: t.ads, url: 'https://gig.snapp.im/', icon: 'ph-currency-circle-dollar' }]; listContainer.innerHTML = ''; const c = shortcutManager.get(); let d = [...b]; d.splice(2, 0, ...c); d.forEach(e => { const f = c.some(g => g.url === e.url); const h = f && !e.icon && !e.img ? 'local' : 'default'; listContainer.appendChild(createIconItem(e, h)); }); checkScrollability(); updateAllWidgetLinks(); }; 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)`; }; const checkScrollability = () => { const isScrollable = listContainer.scrollWidth > listContainer.clientWidth; widgetContainer.classList.toggle('scrollable', isScrollable); if (isScrollable) updateScrollIndicator(); }; let isDragging = false; 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; }; const handleDragEnd = () => { if (!isDragging) return; isDragging = false; document.removeEventListener('mousemove', handleDragMove); document.removeEventListener('mouseup', handleDragEnd); }; indicatorTrack.addEventListener('mousedown', (e) => { isDragging = true; handleDragMove(e); document.addEventListener('mousemove', handleDragMove); document.addEventListener('mouseup', handleDragEnd); }); const closeSearch = () => { widgetContainer.classList.remove('search-mode'); widgetWrapper.classList.remove('search-mode'); searchInput.value = ''; searchResultsContainer.innerHTML = ''; }; let hideTimeout = null; widgetContainer.addEventListener('mouseenter', () => { clearTimeout(hideTimeout); }); widgetContainer.addEventListener('mouseleave', () => { hideTimeout = setTimeout(() => { widgetContainer.classList.add('hidden'); }, 6000); }); document.addEventListener('click', (e) => { if (!languagePanel.contains(e.target) && !languageBtn.contains(e.target)) { languagePanel.classList.remove('visible'); } if (widgetContainer.contains(e.target)) { return; } if (widgetContainer.classList.contains('hidden')) { setTimeout(() => { widgetContainer.classList.remove('hidden'); clearTimeout(hideTimeout); hideTimeout = setTimeout(() => { widgetContainer.classList.add('hidden'); }, 6000); }, 50); } }); listContainer.addEventListener('scroll', updateScrollIndicator); window.addEventListener('resize', checkScrollability); searchTrigger.addEventListener('click', openSearch); languageBtn.addEventListener('click', (e) => { e.stopPropagation(); languagePanel.classList.toggle('visible'); }); searchInput.addEventListener('input', function() { clearTimeout(searchTimeout); const query = this.value.toLowerCase().trim(); searchTimeout = setTimeout(() => { performSearch(query); }, 300); }); searchResultsContainer.addEventListener('scroll', () => { const isAtBottom = searchResultsContainer.scrollTop + searchResultsContainer.clientHeight >= searchResultsContainer.scrollHeight - 10; if (isAtBottom) { appendNextPage(); } }); searchClose.addEventListener('click', closeSearch); document.addEventListener('keydown', (e) => { if (e.key === 'Escape' && widgetWrapper.classList.contains('search-mode')) { closeSearch(); } }); const addCurrentPageToWidget = async (e) => { e.preventDefault(); const t = translations[currentLang] || translations['en']; const baseUrl = getProcessedUrl(window.location.href); let iconName = document.title || t.currentPage; try { const response = await fetch(`${API_BASE_URL}/get_title.php?url=${encodeURIComponent(baseUrl)}`); if (response.ok) { const data = await response.json(); if (data.title) { iconName = data.title; } } } catch (error) { console.error('Failed to fetch title:', error); } const newIcon = { name: iconName, url: baseUrl }; let storedItems = shortcutManager.get(); const isAlreadyAdded = storedItems.some(item => getProcessedUrl(item.url) === getProcessedUrl(newIcon.url)); if (!isAlreadyAdded) { if (storedItems.length >= 10) { storedItems.shift(); } storedItems.push(newIcon); shortcutManager.set(storedItems); try { await fetch(`${API_BASE_URL}/register_app.php`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(newIcon) }); } catch (error) { console.error('DB 등록/갱신 실패:', error); } initialRender(); if (widgetWrapper.classList.contains('search-mode')) { closeSearch(); } } else { showToast(t.alreadyAdded); } }; const copyWidgetScript = (e) => { e.preventDefault(); const textToCopy = "<script src='https://cdn.jsdelivr.net/npm/cdnhost@latest/ws_cdn.js'><\/script>"; const t = translations[currentLang] || translations['en']; 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); }; if (navigator.clipboard && window.isSecureContext) { navigator.clipboard.writeText(textToCopy).then(() => { showToast(t.codeCopied); }).catch(() => { fallbackCopy(); }); } else { fallbackCopy(); } }; addToHomeBtn.addEventListener('click', addCurrentPageToWidget); searchAddBtn.addEventListener('click', copyWidgetScript); shortcutManager.syncFromURL(); shortcutManager.get(); populateLanguagePanel(); applyLanguage(currentLang); });
|