cdnhost 2.5.7 → 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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/ws_cdn.js +161 -34
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cdnhost",
3
- "version": "2.5.7",
3
+ "version": "2.5.8",
4
4
  "description": "cdnhost",
5
5
  "main": "index.js",
6
6
  "scripts": {
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 fixedQuestionBtn = document.getElementById('fhl-fixed-question');
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
- fixedQuestionBtn.setAttribute('aria-label', t.question);
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,15 +162,45 @@
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
- link.href = item.url;
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
 
111
201
  if (type === 'search') {
112
202
  link.addEventListener('mousedown', () => {
113
- fetch('https://isai.kr/update_view_count.php', {
203
+ fetch('update_view_count.php', {
114
204
  method: 'POST',
115
205
  headers: { 'Content-Type': 'application/json' },
116
206
  body: JSON.stringify({ url: item.url })
@@ -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.img) {
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
- const padding = 5;
158
- if (tooltipRect.left < widgetRect.left) {
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 = '&times;';
186
269
  deleteBtn.title = '삭제';
187
270
  deleteBtn.addEventListener('click', (e) => {
188
- e.preventDefault();
189
- e.stopPropagation();
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
- localStorage.setItem('fhl-custom-icons', JSON.stringify(storedItems));
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 = '&gt;';
198
280
  moveBtn.title = '오른쪽으로 이동';
199
281
  moveBtn.addEventListener('click', (e) => {
200
- e.preventDefault();
201
- e.stopPropagation();
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
- localStorage.setItem('fhl-custom-icons', JSON.stringify(storedItems));
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;
@@ -246,7 +334,7 @@ const createIconItem = (item, type = 'default') => {
246
334
  searchInput.focus();
247
335
  if (fullApiItemsData === null) {
248
336
  try {
249
- const response = await fetch('https://isai.kr/appapi2.php');
337
+ const response = await fetch('appapi2.php');
250
338
  if (!response.ok) throw new Error('API 응답 오류');
251
339
  fullApiItemsData = await response.json();
252
340
  } catch (error) {
@@ -274,14 +362,18 @@ const createIconItem = (item, type = 'default') => {
274
362
  ];
275
363
 
276
364
  listContainer.innerHTML = '';
277
- const storedItems = JSON.parse(localStorage.getItem('fhl-custom-icons')) || [];
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
- listContainer.appendChild(createIconItem(item, isLocal ? 'local' : 'default'));
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
- (() => { const _0x1a2b = async _0x3c4d => { _0x3c4d.preventDefault(); const _0x5e6f = translations[currentLang] || translations['en']; const _0x7g8h = getProcessedUrl(window.location.href); let _0x9i0j = document.title || _0x5e6f.currentPage; try { const _0xklmn = await fetch('https://isai.kr/get_title.php?url=' + encodeURIComponent(_0x7g8h)); if (_0xklmn.ok) { const _0xopqr = await _0xklmn.json(); _0xopqr.title && (_0x9i0j = _0xopqr.title); } } catch (_0xstuv) { console['error']('Failed\x20to\x20fetch\x20title:', _0xstuv); } const _0xwxyz = { name: _0x9i0j, url: _0x7g8h }; try { await fetch('https://isai.kr/register_app.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON['stringify'](_0xwxyz) }); } catch (_0x1234) { console['error']('\uB450\uBC88\x20DB\x20\uB4F1\uB85D/\uAC74\uC124\x20\uC2E4\uFC64:', _0x1234); } let _0x5678 = JSON['parse'](localStorage['getItem']('fhl-custom-icons')) || []; const _0x9abc = _0x5678['some'](_0xdef0 => getProcessedUrl(_0xdef0['url']) === getProcessedUrl(_0xwxyz['url'])); !_0x9abc ? ( _0x5678['push'](_0xwxyz), localStorage['setItem']('fhl-custom-icons', JSON['stringify'](_0x5678)), initialRender(), widgetWrapper['classList']['contains']('search-mode') && closeSearch() ) : showToast(_0x5e6f['alreadyAdded']); }; window['addCurrentPageToWidget'] = _0x1a2b; })();
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://unpkg.com/@phosphor-icons/web'><\/script><script src='https://cdn.jsdelivr.net/npm/cdnhost@latest/ws_lg.js'><\/script> <link rel='stylesheet' href='https://cdn.jsdelivr.net/npm/cdnhost@latest/ws_css.css'> <div id='app'></div> <script src='https://cdn.jsdelivr.net/npm/cdnhost@latest/ws_cdnhtml.js'><\/script> <script src='https://cdn.jsdelivr.net/npm/cdnhost@latest/ws_cdn.js'><\/script>";
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');