@kangsicj/dcinsideenhancer 1.0.2 → 1.1.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/README.en.md +6 -2
- package/README.md +6 -2
- package/dist/DCInsideEnhancer.meta.js +1 -1
- package/dist/DCInsideEnhancer.user.js +1372 -71
- package/package.json +1 -1
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// @name DCInside Enhancer
|
|
3
3
|
// @name:ko 디시 인핸서
|
|
4
4
|
// @namespace https://github.com/kangsicj/DCInsideEnhancer
|
|
5
|
-
// @version 1.0
|
|
5
|
+
// @version 1.1.0
|
|
6
6
|
// @description DCInside filter & enhancer — mobile & PC. Block users/words/heads/notices/images & GIFs across posts, infinite list, zoom viewer, theme sync, encrypted multi-device sync.
|
|
7
7
|
// @description:ko 디시인사이드 차단·편의 기능 (모바일·PC) - 유저·단어(제목/본문)·말머리·공지 차단, 같은/비슷한 이미지·움짤 모든 글 차단, 디시콘 제거, 차단 미디어 데이터 절약, 목록 자동 보충·무한 스크롤·뒤로 가기 위치 복원, 스크랩, 이미지 확대뷰 개선, 야간모드 자동 연동, 암호화 기기 간 동기화
|
|
8
8
|
// @author kangsicj
|
|
@@ -75,7 +75,7 @@
|
|
|
75
75
|
// 0. 설정·상수
|
|
76
76
|
// =========================================================
|
|
77
77
|
|
|
78
|
-
const SCRIPT_VERSION = '1.0
|
|
78
|
+
const SCRIPT_VERSION = '1.1.0';
|
|
79
79
|
const PROJECT_URL = 'https://github.com/kangsicj/DCInsideEnhancer';
|
|
80
80
|
const DONATE_URL = 'https://fairy.hada.io/@jinny#support';
|
|
81
81
|
|
|
@@ -104,6 +104,10 @@
|
|
|
104
104
|
syncUndo: 'dc_sync_undo',
|
|
105
105
|
syncLog: 'dc_sync_log',
|
|
106
106
|
syncLock: 'dc_sync_lock',
|
|
107
|
+
syncRt: 'dc_sync_rt',
|
|
108
|
+
recent: 'dc_recent_galls',
|
|
109
|
+
recentNative: 'dc_recent_native',
|
|
110
|
+
alarmSeen: 'dc_alarm_marks',
|
|
107
111
|
seenVersion: 'dc_seen_version'
|
|
108
112
|
};
|
|
109
113
|
|
|
@@ -212,6 +216,12 @@
|
|
|
212
216
|
hideHomeNewGall: true,
|
|
213
217
|
hideHomeMedia: true,
|
|
214
218
|
hideFooterInfo: true,
|
|
219
|
+
hideAdSlots: true,
|
|
220
|
+
hideNewsRows: true,
|
|
221
|
+
hideCommentAds: true,
|
|
222
|
+
hideSilbeAlarm: true,
|
|
223
|
+
alarmBubble: true,
|
|
224
|
+
alarmKeepPolling: true,
|
|
215
225
|
pcFitWidth: true,
|
|
216
226
|
autoHideScrollbar: true,
|
|
217
227
|
scrapIntercept: true,
|
|
@@ -395,57 +405,77 @@
|
|
|
395
405
|
|
|
396
406
|
html.dc-pc-fit { --dc-fit-gap: clamp(12px, 2vw, 32px); }
|
|
397
407
|
html.dc-pc-fit body,
|
|
398
|
-
html.dc-pc-fit .dcwrap
|
|
399
|
-
html.dc-pc-fit .dcwrap
|
|
400
|
-
html.dc-pc-fit .dcwrap
|
|
401
|
-
html.dc-pc-fit .dcwrap
|
|
402
|
-
html.dc-pc-fit .dcwrap
|
|
403
|
-
html.dc-pc-fit .dcwrap
|
|
404
|
-
html.dc-pc-fit .dcwrap
|
|
408
|
+
html.dc-pc-fit .dcwrap { min-width: 0 !important; }
|
|
409
|
+
html.dc-pc-fit .dcwrap .dchead,
|
|
410
|
+
html.dc-pc-fit .dcwrap .gnb,
|
|
411
|
+
html.dc-pc-fit .dcwrap .gnb_depth2_list,
|
|
412
|
+
html.dc-pc-fit .dcwrap .info_policy,
|
|
413
|
+
html.dc-pc-fit .dcwrap .copyright,
|
|
414
|
+
html.dc-pc-fit .dcwrap .visit_bookmark {
|
|
405
415
|
width: auto !important;
|
|
406
416
|
max-width: none !important;
|
|
407
417
|
margin-left: var(--dc-fit-gap) !important;
|
|
408
418
|
margin-right: var(--dc-fit-gap) !important;
|
|
409
419
|
}
|
|
410
|
-
|
|
411
|
-
html.dc-pc-fit .dcwrap.
|
|
412
|
-
html.dc-pc-fit .dcwrap
|
|
420
|
+
|
|
421
|
+
html.dc-pc-fit .dcwrap .gnb_bar { min-width: 0 !important; width: auto !important; margin: 0 !important; }
|
|
422
|
+
html.dc-pc-fit .dcwrap .gnb_list,
|
|
423
|
+
html.dc-pc-fit .dcwrap .dc_all { width: auto !important; }
|
|
413
424
|
html.dc-pc-fit .dcwrap .wrap_inner { width: auto !important; }
|
|
414
|
-
html.dc-pc-fit .dcwrap
|
|
425
|
+
html.dc-pc-fit .dcwrap #container {
|
|
426
|
+
width: auto !important;
|
|
427
|
+
margin-left: var(--dc-fit-gap) !important;
|
|
428
|
+
margin-right: var(--dc-fit-gap) !important;
|
|
429
|
+
}
|
|
430
|
+
html.dc-pc-fit .dcwrap #container:has(> .left_content),
|
|
431
|
+
html.dc-pc-fit .dcwrap #container > .main_content:has(> .left_content) {
|
|
415
432
|
display: flex;
|
|
416
433
|
flex-wrap: wrap;
|
|
417
434
|
align-items: flex-start;
|
|
418
435
|
gap: 20px;
|
|
419
436
|
width: auto !important;
|
|
420
|
-
margin: 20px var(--dc-fit-gap) 0 !important;
|
|
421
437
|
}
|
|
422
|
-
html.dc-pc-fit .dcwrap
|
|
423
|
-
html.dc-pc-fit .dcwrap
|
|
424
|
-
html.dc-pc-fit .dcwrap.
|
|
438
|
+
html.dc-pc-fit .dcwrap #container:has(> .left_content)::before,
|
|
439
|
+
html.dc-pc-fit .dcwrap #container:has(> .left_content)::after,
|
|
440
|
+
html.dc-pc-fit .dcwrap .main_content:has(> .left_content)::before,
|
|
441
|
+
html.dc-pc-fit .dcwrap .main_content:has(> .left_content)::after { display: none !important; }
|
|
442
|
+
html.dc-pc-fit .dcwrap #container:has(> .left_content) > :not(.left_content):not(.right_content),
|
|
443
|
+
html.dc-pc-fit .dcwrap .main_content:has(> .left_content) > :not(.left_content):not(.right_content) {
|
|
425
444
|
flex: 0 0 100%;
|
|
426
445
|
float: none !important;
|
|
427
446
|
}
|
|
428
|
-
html.dc-pc-fit .dcwrap
|
|
447
|
+
html.dc-pc-fit .dcwrap :is(#container, .main_content) > .left_content {
|
|
429
448
|
float: none !important;
|
|
430
449
|
flex: 1 1 0;
|
|
431
450
|
width: auto !important;
|
|
432
451
|
min-width: 0;
|
|
433
452
|
}
|
|
434
|
-
html.dc-pc-fit .dcwrap
|
|
453
|
+
html.dc-pc-fit .dcwrap :is(#container, .main_content) > .right_content {
|
|
435
454
|
float: none !important;
|
|
436
455
|
flex: 0 0 300px;
|
|
456
|
+
margin-left: 0 !important;
|
|
437
457
|
}
|
|
438
|
-
html.dc-pc-fit .dcwrap
|
|
439
|
-
html.dc-pc-fit .dcwrap
|
|
440
|
-
html.dc-pc-fit .dcwrap
|
|
441
|
-
html.dc-pc-fit .dcwrap
|
|
458
|
+
html.dc-pc-fit .dcwrap .issuebox,
|
|
459
|
+
html.dc-pc-fit .dcwrap .newvisit_history,
|
|
460
|
+
html.dc-pc-fit .dcwrap .trendalign { width: auto !important; }
|
|
461
|
+
html.dc-pc-fit .dcwrap .list_array_option { width: 100% !important; }
|
|
442
462
|
html.dc-pc-fit .dcwrap .stickyunit { display: none !important; }
|
|
443
463
|
html.dc-pc-fit .dcwrap .darkmodebox .pop_tipbox { left: auto !important; right: 0 !important; }
|
|
444
464
|
|
|
445
465
|
@media (max-width: 1240px) {
|
|
446
|
-
html.dc-pc-fit .dcwrap
|
|
466
|
+
html.dc-pc-fit .dcwrap :is(#container, .main_content) > .right_content { display: none !important; }
|
|
447
467
|
}
|
|
448
468
|
|
|
469
|
+
html.dc-footer-info-off .dcwrap footer.dcfoot,
|
|
470
|
+
html.dc-footer-info-off .dcwrap #container > .ad_bottom_list { display: none !important; }
|
|
471
|
+
|
|
472
|
+
html.dc-ad-slots-off .dcwrap :is(.stickyunit, .rightbanner, .rightbanner1, .banner_box, .ad_bottom_list, #ad_floating, .kakao_ad_area, .ad_top, .view_ad_wrap, .power_link, .con_banner.writing_banbox, #ad_nv_slot, ins.adsbygoogle),
|
|
473
|
+
html.dc-ad-slots-off :is(.adv-group, li.adv-inner, section.grid.clkhigh, #home-ad, .kakao_ad_area) { display: none !important; }
|
|
474
|
+
html.dc-news-rows-off :is(table.gall_list tr.ub-content[data-type="icon_fnews"], .gall-detail-lst > li:has(.sp-lst-fnews)) { display: none !important; }
|
|
475
|
+
html.dc-comment-ads-off :is(.comment_box li.ub-content.dory, .comment_box li.cmt_pw_ad, .all-comment-lst > li.dc-comment-boy) { display: none !important; }
|
|
476
|
+
html.dc-silbe-alarm-off :is(.alarm-popup.silbe-alarm, .rtbAlarmPopup) { display: none !important; }
|
|
477
|
+
html.dc-alarm-bubble :is(.alarm-popup.alarm-default, .alarmPopup) { display: none !important; }
|
|
478
|
+
|
|
449
479
|
html.dc-top-concept-off:not(.dc-peek) .detail-top-concept,
|
|
450
480
|
html.dc-top-concept-off:not(.dc-peek) .con-child-box#recom_box2,
|
|
451
481
|
html:not(.dc-peek) .detail-top-lst.dc-top-empty {
|
|
@@ -939,7 +969,7 @@
|
|
|
939
969
|
|
|
940
970
|
#dc-custom-modal .dc-modal-tabs {
|
|
941
971
|
display: flex;
|
|
942
|
-
gap:
|
|
972
|
+
gap: 2px;
|
|
943
973
|
margin: 0;
|
|
944
974
|
padding: 3px;
|
|
945
975
|
border-radius: 12px;
|
|
@@ -954,8 +984,9 @@
|
|
|
954
984
|
border-radius: 9px;
|
|
955
985
|
background: transparent;
|
|
956
986
|
color: var(--dc-text-dim);
|
|
957
|
-
font-size:
|
|
987
|
+
font-size: 12px;
|
|
958
988
|
font-weight: 700;
|
|
989
|
+
letter-spacing: -.3px;
|
|
959
990
|
white-space: nowrap;
|
|
960
991
|
cursor: pointer;
|
|
961
992
|
}
|
|
@@ -1678,6 +1709,127 @@
|
|
|
1678
1709
|
|
|
1679
1710
|
@keyframes dc-pop { from { transform: scale(0); } to { transform: scale(1); } }
|
|
1680
1711
|
|
|
1712
|
+
#dc-custom-fab[data-dc-alarm]::before {
|
|
1713
|
+
content: attr(data-dc-alarm);
|
|
1714
|
+
position: absolute;
|
|
1715
|
+
top: -6px;
|
|
1716
|
+
left: -6px;
|
|
1717
|
+
min-width: 18px;
|
|
1718
|
+
height: 18px;
|
|
1719
|
+
padding: 0 5px;
|
|
1720
|
+
box-sizing: border-box;
|
|
1721
|
+
border: 2px solid var(--dc-surface);
|
|
1722
|
+
border-radius: 9px;
|
|
1723
|
+
background: var(--dc-danger);
|
|
1724
|
+
color: #fff;
|
|
1725
|
+
font-size: 10.5px;
|
|
1726
|
+
font-weight: 800;
|
|
1727
|
+
line-height: 14px;
|
|
1728
|
+
text-align: center;
|
|
1729
|
+
pointer-events: none;
|
|
1730
|
+
animation: dc-pop .35s ease-out;
|
|
1731
|
+
}
|
|
1732
|
+
|
|
1733
|
+
#dc-custom-fab[data-dc-alarm] { opacity: 1; }
|
|
1734
|
+
|
|
1735
|
+
#dc-bubbles {
|
|
1736
|
+
position: fixed;
|
|
1737
|
+
right: 15px;
|
|
1738
|
+
bottom: calc(144px + env(safe-area-inset-bottom));
|
|
1739
|
+
z-index: 99998;
|
|
1740
|
+
display: flex;
|
|
1741
|
+
flex-direction: column;
|
|
1742
|
+
align-items: flex-end;
|
|
1743
|
+
gap: 8px;
|
|
1744
|
+
width: min(320px, calc(100vw - 24px));
|
|
1745
|
+
pointer-events: none;
|
|
1746
|
+
}
|
|
1747
|
+
|
|
1748
|
+
#dc-bubbles.dc-left { align-items: flex-start; }
|
|
1749
|
+
|
|
1750
|
+
#dc-bubbles .dc-bubble {
|
|
1751
|
+
position: relative;
|
|
1752
|
+
box-sizing: border-box;
|
|
1753
|
+
width: 100%;
|
|
1754
|
+
padding: 10px 12px 11px;
|
|
1755
|
+
border: 1px solid var(--dc-border);
|
|
1756
|
+
border-radius: 16px 16px 6px 16px;
|
|
1757
|
+
background: var(--dc-surface);
|
|
1758
|
+
color: var(--dc-text);
|
|
1759
|
+
box-shadow: var(--dc-shadow);
|
|
1760
|
+
font-size: 13px;
|
|
1761
|
+
line-height: 1.4;
|
|
1762
|
+
cursor: pointer;
|
|
1763
|
+
pointer-events: auto;
|
|
1764
|
+
touch-action: pan-y;
|
|
1765
|
+
user-select: none;
|
|
1766
|
+
-webkit-user-select: none;
|
|
1767
|
+
opacity: 0;
|
|
1768
|
+
transform: translateY(10px) scale(.96);
|
|
1769
|
+
transition: opacity .22s ease, transform .22s ease;
|
|
1770
|
+
}
|
|
1771
|
+
|
|
1772
|
+
#dc-bubbles.dc-left .dc-bubble { border-radius: 16px 16px 16px 6px; }
|
|
1773
|
+
#dc-bubbles .dc-bubble.dc-in { opacity: 1; transform: none; }
|
|
1774
|
+
#dc-bubbles .dc-bubble.dc-out { opacity: 0; transform: translateY(6px) scale(.96); pointer-events: none; }
|
|
1775
|
+
|
|
1776
|
+
#dc-bubbles .dc-bubble-top {
|
|
1777
|
+
display: flex;
|
|
1778
|
+
align-items: center;
|
|
1779
|
+
gap: 6px;
|
|
1780
|
+
margin-bottom: 3px;
|
|
1781
|
+
}
|
|
1782
|
+
|
|
1783
|
+
#dc-bubbles .dc-bubble-tag {
|
|
1784
|
+
flex: 0 0 auto;
|
|
1785
|
+
padding: 1px 7px;
|
|
1786
|
+
border-radius: 8px;
|
|
1787
|
+
background: var(--dc-accent-soft);
|
|
1788
|
+
color: var(--dc-accent-ink);
|
|
1789
|
+
font-size: 11px;
|
|
1790
|
+
font-weight: 800;
|
|
1791
|
+
}
|
|
1792
|
+
|
|
1793
|
+
#dc-bubbles .dc-bubble-time {
|
|
1794
|
+
flex: 1 1 auto;
|
|
1795
|
+
min-width: 0;
|
|
1796
|
+
color: var(--dc-text-dim);
|
|
1797
|
+
font-size: 11px;
|
|
1798
|
+
}
|
|
1799
|
+
|
|
1800
|
+
#dc-bubbles .dc-bubble-x {
|
|
1801
|
+
flex: 0 0 auto;
|
|
1802
|
+
width: 24px;
|
|
1803
|
+
height: 24px;
|
|
1804
|
+
margin: -4px -6px -4px 0;
|
|
1805
|
+
padding: 0;
|
|
1806
|
+
border: none;
|
|
1807
|
+
border-radius: 50%;
|
|
1808
|
+
background: transparent;
|
|
1809
|
+
color: var(--dc-text-dim);
|
|
1810
|
+
font-size: 17px;
|
|
1811
|
+
line-height: 24px;
|
|
1812
|
+
cursor: pointer;
|
|
1813
|
+
}
|
|
1814
|
+
|
|
1815
|
+
#dc-bubbles .dc-bubble-text {
|
|
1816
|
+
display: -webkit-box;
|
|
1817
|
+
overflow: hidden;
|
|
1818
|
+
-webkit-line-clamp: 2;
|
|
1819
|
+
-webkit-box-orient: vertical;
|
|
1820
|
+
word-break: break-all;
|
|
1821
|
+
font-weight: 600;
|
|
1822
|
+
}
|
|
1823
|
+
|
|
1824
|
+
#dc-bubbles .dc-bubble-more {
|
|
1825
|
+
margin-top: 4px;
|
|
1826
|
+
color: var(--dc-accent-ink);
|
|
1827
|
+
font-size: 11.5px;
|
|
1828
|
+
font-weight: 700;
|
|
1829
|
+
}
|
|
1830
|
+
|
|
1831
|
+
#dc-custom-modal .dc-row.dc-row-new .dc-row-title { font-weight: 800; }
|
|
1832
|
+
|
|
1681
1833
|
#dc-custom-modal .dc-about {
|
|
1682
1834
|
margin-top: 14px;
|
|
1683
1835
|
padding: 12px 13px;
|
|
@@ -2799,6 +2951,11 @@
|
|
|
2799
2951
|
root.classList.toggle('dc-home-media-off', settings.hideHomeMedia !== false);
|
|
2800
2952
|
root.classList.toggle('dc-footer-info-off', settings.hideFooterInfo !== false);
|
|
2801
2953
|
root.classList.toggle('dc-pc-fit', IS_PC_SITE && settings.pcFitWidth !== false);
|
|
2954
|
+
root.classList.toggle('dc-ad-slots-off', settings.hideAdSlots !== false);
|
|
2955
|
+
root.classList.toggle('dc-news-rows-off', settings.hideNewsRows !== false);
|
|
2956
|
+
root.classList.toggle('dc-comment-ads-off', settings.hideCommentAds !== false);
|
|
2957
|
+
root.classList.toggle('dc-silbe-alarm-off', settings.hideSilbeAlarm !== false);
|
|
2958
|
+
root.classList.toggle('dc-alarm-bubble', settings.alarmBubble !== false);
|
|
2802
2959
|
root.classList.toggle('dc-hide-scrollbar', settings.autoHideScrollbar !== false && root.classList.contains('dc-classic-scrollbar'));
|
|
2803
2960
|
};
|
|
2804
2961
|
|
|
@@ -2953,6 +3110,7 @@
|
|
|
2953
3110
|
until: Number(stored.until) || 0,
|
|
2954
3111
|
last: Number(stored.last) || 0,
|
|
2955
3112
|
strikes: Number(stored.strikes) || 0,
|
|
3113
|
+
src: stored.src === 'page' ? 'page' : 'fetch',
|
|
2956
3114
|
lastStrike: Number(stored.lastStrike) || 0,
|
|
2957
3115
|
hits: Array.isArray(stored.hits) ? stored.hits.filter(t => Date.now() - t < NET_WINDOW) : [],
|
|
2958
3116
|
autoHits: Array.isArray(stored.autoHits) ? stored.autoHits.filter(t => Date.now() - t < NET_WINDOW) : [],
|
|
@@ -2979,10 +3137,12 @@
|
|
|
2979
3137
|
|
|
2980
3138
|
const netListeners = [];
|
|
2981
3139
|
|
|
2982
|
-
const noteNetBlocked = () => {
|
|
3140
|
+
const noteNetBlocked = (source = 'fetch') => {
|
|
2983
3141
|
const state = readNetState();
|
|
2984
3142
|
const now = Date.now();
|
|
2985
3143
|
|
|
3144
|
+
state.src = source;
|
|
3145
|
+
|
|
2986
3146
|
state.strikes = now - state.lastStrike < 60 * 60 * 1000 ? state.strikes + 1 : 1;
|
|
2987
3147
|
state.lastStrike = now;
|
|
2988
3148
|
state.until = now + Math.min(NET_COOLDOWN_MAX, NET_COOLDOWN * 2 ** (state.strikes - 1));
|
|
@@ -3024,10 +3184,21 @@
|
|
|
3024
3184
|
}
|
|
3025
3185
|
};
|
|
3026
3186
|
|
|
3187
|
+
const NET_PROBATION = 60 * 1000;
|
|
3188
|
+
|
|
3027
3189
|
const noteSitePageView = () => {
|
|
3028
3190
|
const state = readNetState();
|
|
3191
|
+
const now = Date.now();
|
|
3192
|
+
|
|
3193
|
+
state.recent.push(now);
|
|
3194
|
+
|
|
3195
|
+
if (state.until > now + NET_PROBATION && state.src !== 'page') {
|
|
3196
|
+
state.until = now + NET_PROBATION;
|
|
3197
|
+
writeNetState(state);
|
|
3198
|
+
scheduleNetResume();
|
|
3199
|
+
return;
|
|
3200
|
+
}
|
|
3029
3201
|
|
|
3030
|
-
state.recent.push(Date.now());
|
|
3031
3202
|
writeNetState(state);
|
|
3032
3203
|
};
|
|
3033
3204
|
|
|
@@ -3041,6 +3212,8 @@
|
|
|
3041
3212
|
let netHiddenAt = 0;
|
|
3042
3213
|
let netResumedAt = 0;
|
|
3043
3214
|
|
|
3215
|
+
let netLeavingAt = 0;
|
|
3216
|
+
|
|
3044
3217
|
const pageIsHidden = () => document.visibilityState === 'hidden';
|
|
3045
3218
|
|
|
3046
3219
|
document.addEventListener('visibilitychange', () => {
|
|
@@ -3052,7 +3225,48 @@
|
|
|
3052
3225
|
});
|
|
3053
3226
|
window.addEventListener('pagehide', () => {
|
|
3054
3227
|
netHiddenAt = Date.now();
|
|
3228
|
+
netLeavingAt = Date.now();
|
|
3055
3229
|
});
|
|
3230
|
+
|
|
3231
|
+
const noteNetLeaving = () => {
|
|
3232
|
+
netLeavingAt = Date.now();
|
|
3233
|
+
};
|
|
3234
|
+
|
|
3235
|
+
window.addEventListener('beforeunload', noteNetLeaving);
|
|
3236
|
+
|
|
3237
|
+
document.addEventListener('click', event => {
|
|
3238
|
+
const link = event.target instanceof Element ? event.target.closest('a[href]') : null;
|
|
3239
|
+
|
|
3240
|
+
if (!link || event.defaultPrevented || event.button > 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) {
|
|
3241
|
+
return;
|
|
3242
|
+
}
|
|
3243
|
+
|
|
3244
|
+
if (link.target && link.target !== '_self') {
|
|
3245
|
+
return;
|
|
3246
|
+
}
|
|
3247
|
+
|
|
3248
|
+
try {
|
|
3249
|
+
const to = new URL(link.href, location.href);
|
|
3250
|
+
|
|
3251
|
+
if (/^https?:$/.test(to.protocol) && (to.pathname !== location.pathname || to.search !== location.search)) {
|
|
3252
|
+
noteNetLeaving();
|
|
3253
|
+
}
|
|
3254
|
+
} catch {
|
|
3255
|
+
}
|
|
3256
|
+
}, true);
|
|
3257
|
+
|
|
3258
|
+
try {
|
|
3259
|
+
if (window.navigation && typeof window.navigation.addEventListener === 'function') {
|
|
3260
|
+
window.navigation.addEventListener('navigate', event => {
|
|
3261
|
+
if (!event.hashChange && !(event.destination && event.destination.sameDocument)) {
|
|
3262
|
+
noteNetLeaving();
|
|
3263
|
+
}
|
|
3264
|
+
});
|
|
3265
|
+
}
|
|
3266
|
+
} catch {
|
|
3267
|
+
}
|
|
3268
|
+
|
|
3269
|
+
const netLeavingRecently = () => netLeavingAt && Date.now() - netLeavingAt < 6000;
|
|
3056
3270
|
window.addEventListener('pageshow', event => {
|
|
3057
3271
|
if (event.persisted) {
|
|
3058
3272
|
netResumedAt = Date.now();
|
|
@@ -3066,7 +3280,18 @@
|
|
|
3066
3280
|
});
|
|
3067
3281
|
|
|
3068
3282
|
const unreliableWindow = startedAt =>
|
|
3069
|
-
pageIsHidden() || netHiddenAt >= startedAt ||
|
|
3283
|
+
pageIsHidden() || netHiddenAt >= startedAt || netLeavingAt >= startedAt || netLeavingRecently() ||
|
|
3284
|
+
Date.now() - netResumedAt < NET_RESUME_SETTLE + 3000;
|
|
3285
|
+
|
|
3286
|
+
const truncatedResponse = (response, text) => {
|
|
3287
|
+
try {
|
|
3288
|
+
const length = Number(response.headers && response.headers.get && response.headers.get('content-length'));
|
|
3289
|
+
|
|
3290
|
+
return Number.isFinite(length) && length > 0 && !text.trim();
|
|
3291
|
+
} catch {
|
|
3292
|
+
return false;
|
|
3293
|
+
}
|
|
3294
|
+
};
|
|
3070
3295
|
|
|
3071
3296
|
class NetTransientError extends Error {}
|
|
3072
3297
|
|
|
@@ -3084,7 +3309,7 @@
|
|
|
3084
3309
|
throw new NetPausedError('paused');
|
|
3085
3310
|
}
|
|
3086
3311
|
|
|
3087
|
-
if (pageIsHidden()) {
|
|
3312
|
+
if (pageIsHidden() || netLeavingRecently()) {
|
|
3088
3313
|
throw new NetTransientError('hidden');
|
|
3089
3314
|
}
|
|
3090
3315
|
|
|
@@ -3158,7 +3383,7 @@
|
|
|
3158
3383
|
let { response, text } = await fetchOnce('');
|
|
3159
3384
|
|
|
3160
3385
|
if (looksBlockedResponse(response, text)) {
|
|
3161
|
-
if (unreliableWindow(startedAt)) {
|
|
3386
|
+
if (unreliableWindow(startedAt) || truncatedResponse(response, text)) {
|
|
3162
3387
|
throw new NetTransientError('unreliable');
|
|
3163
3388
|
}
|
|
3164
3389
|
|
|
@@ -3170,11 +3395,17 @@
|
|
|
3170
3395
|
({ response, text } = await fetchOnce(options.cache === 'no-store' ? 'no-store' : 'reload'));
|
|
3171
3396
|
|
|
3172
3397
|
if (looksBlockedResponse(response, text)) {
|
|
3398
|
+
if (unreliableWindow(startedAt) || truncatedResponse(response, text)) {
|
|
3399
|
+
throw new NetTransientError('unreliable');
|
|
3400
|
+
}
|
|
3401
|
+
|
|
3402
|
+
await sleepMs(1500 * NET_GAP_SCALE);
|
|
3403
|
+
|
|
3173
3404
|
if (unreliableWindow(startedAt)) {
|
|
3174
3405
|
throw new NetTransientError('unreliable');
|
|
3175
3406
|
}
|
|
3176
3407
|
|
|
3177
|
-
noteNetBlocked();
|
|
3408
|
+
noteNetBlocked('fetch');
|
|
3178
3409
|
throw new NetPausedError('blocked');
|
|
3179
3410
|
}
|
|
3180
3411
|
}
|
|
@@ -6814,6 +7045,8 @@
|
|
|
6814
7045
|
document.querySelectorAll(COMMENT_ITEM_SELECTOR).forEach(li => {
|
|
6815
7046
|
processUserNode(li, li.querySelector(COMMENT_NICK_SELECTOR), 'comment');
|
|
6816
7047
|
li.classList.toggle('dc-comment-hidden', settings.hideDcconComments !== false && isDcconOnlyComment(li));
|
|
7048
|
+
|
|
7049
|
+
li.classList.toggle('dc-comment-boy', /^\s*댓글돌이\s*$/.test((li.querySelector('.nick') || {}).textContent || ''));
|
|
6817
7050
|
});
|
|
6818
7051
|
|
|
6819
7052
|
processMediaElements();
|
|
@@ -9700,6 +9933,7 @@
|
|
|
9700
9933
|
const SETTING_GROUPS = [
|
|
9701
9934
|
{ key: 'block', title: '🚫 차단 방식', intro: '차단한 글·댓글을 화면에서 어떻게 처리할지 정합니다.' },
|
|
9702
9935
|
{ key: 'clean', title: '🧹 화면 정리', intro: '사이트가 기본으로 보여 주는 영역 중 필요 없는 것을 숨깁니다.' },
|
|
9936
|
+
{ key: 'alarm', title: '🔔 알림', intro: '디시 알림(댓글·답글·힛갤 등)을 보기 쉽게 바꿉니다. 알림 내용은 사이트가 받은 것을 그대로 씁니다.' },
|
|
9703
9937
|
{ key: 'dccon', title: '😶 디시콘', intro: '디시 자체 이모티콘을 다룹니다.' },
|
|
9704
9938
|
{ key: 'list', title: '📜 목록 보기', intro: '게시판 목록을 넘겨 보는 방식을 정합니다.' },
|
|
9705
9939
|
{ key: 'media', title: '🖼 이미지·영상', intro: '본문 이미지 차단과 확대 보기를 다룹니다.' },
|
|
@@ -9823,22 +10057,71 @@
|
|
|
9823
10057
|
off: '디시미디어·디시이슈 묶음을 보여 줍니다.',
|
|
9824
10058
|
refresh: 'root'
|
|
9825
10059
|
},
|
|
10060
|
+
{
|
|
10061
|
+
group: 'clean',
|
|
10062
|
+
key: 'hideNewsRows',
|
|
10063
|
+
name: '목록의 제휴 뉴스 숨기기',
|
|
10064
|
+
on: "말머리가 '뉴스'인 제휴 기사(광고성 글)를 목록에서 숨깁니다.",
|
|
10065
|
+
off: '제휴 뉴스를 목록에 그대로 보여 줍니다.',
|
|
10066
|
+
refresh: 'root'
|
|
10067
|
+
},
|
|
10068
|
+
{
|
|
10069
|
+
group: 'clean',
|
|
10070
|
+
key: 'hideCommentAds',
|
|
10071
|
+
name: '댓글돌이·파워링크 댓글 숨기기',
|
|
10072
|
+
on: '댓글 사이에 끼어드는 댓글돌이(기사·광고 넘겨 보기)와 파워링크 댓글을 숨깁니다.',
|
|
10073
|
+
off: '댓글돌이를 그대로 보여 줍니다.',
|
|
10074
|
+
refresh: 'root'
|
|
10075
|
+
},
|
|
10076
|
+
{
|
|
10077
|
+
group: 'clean',
|
|
10078
|
+
key: 'hideAdSlots',
|
|
10079
|
+
name: '광고 자리 숨기기',
|
|
10080
|
+
on: '배너·파워링크·구글 광고처럼 광고가 들어가는 칸을 숨겨 빈 공간 없이 보여 줍니다.',
|
|
10081
|
+
off: '광고 칸을 사이트 기본처럼 둡니다.',
|
|
10082
|
+
refresh: 'root'
|
|
10083
|
+
},
|
|
9826
10084
|
{
|
|
9827
10085
|
group: 'clean',
|
|
9828
10086
|
key: 'pcFitWidth',
|
|
9829
10087
|
name: 'PC 화면을 창 크기에 꽉 차게 (PC)',
|
|
9830
|
-
on: '가운데 고정 폭
|
|
10088
|
+
on: '가운데 고정 폭 대신 창 너비에 맞춰 갤러리 목록·글·디시 홈을 넓힙니다. 창이 좁으면 오른쪽 칸(로그인·실시간 베스트)을 숨겨 목록이 잘리지 않게 합니다.',
|
|
9831
10089
|
off: '사이트 기본처럼 가운데 고정 폭으로 보여 줍니다.',
|
|
9832
10090
|
refresh: 'root'
|
|
9833
10091
|
},
|
|
9834
10092
|
{
|
|
9835
10093
|
group: 'clean',
|
|
9836
10094
|
key: 'hideFooterInfo',
|
|
9837
|
-
name: '맨 아래
|
|
9838
|
-
on: '
|
|
10095
|
+
name: '맨 아래 정보 영역 숨기기',
|
|
10096
|
+
on: '모바일은 맨 아래 앱 설치·회사 소개·약관·저작권 줄을, PC는 검색창 아래의 갤러리 분류 목록·야간모드/맨위로·회사 정보·저작권을 숨깁니다. 창이 작아도 목록이 가려지지 않습니다.',
|
|
9839
10097
|
off: '맨 아래 정보 줄을 모두 보여 줍니다.',
|
|
9840
10098
|
refresh: 'root'
|
|
9841
10099
|
},
|
|
10100
|
+
{
|
|
10101
|
+
group: 'clean',
|
|
10102
|
+
key: 'hideSilbeAlarm',
|
|
10103
|
+
name: '실베(실시간 베스트) 알림 팝업 숨기기',
|
|
10104
|
+
on: '내 알림과 상관없이 화면 구석에 뜨는 실시간 베스트 홍보 한 줄 알림을 숨깁니다.',
|
|
10105
|
+
off: '실시간 베스트 알림을 사이트 기본처럼 띄웁니다.',
|
|
10106
|
+
refresh: 'root'
|
|
10107
|
+
},
|
|
10108
|
+
{
|
|
10109
|
+
group: 'alarm',
|
|
10110
|
+
key: 'alarmBubble',
|
|
10111
|
+
name: '알림을 플로팅 말풍선으로',
|
|
10112
|
+
on: '사이트의 한 줄 알림 대신 차단 관리 버튼 곁에 말풍선으로 띄우고, 읽지 않은 알림 수를 버튼에 표시합니다. 누르면 이동, 좌우로 밀면 닫힙니다. 지난 알림은 [🕘 최근] 탭에 모입니다.',
|
|
10113
|
+
off: '사이트 기본 한 줄 알림을 그대로 씁니다.',
|
|
10114
|
+
refresh: 'root'
|
|
10115
|
+
},
|
|
10116
|
+
{
|
|
10117
|
+
group: 'alarm',
|
|
10118
|
+
key: 'alarmKeepPolling',
|
|
10119
|
+
parent: 'alarmBubble',
|
|
10120
|
+
name: '오래 보는 페이지에서도 알림 받기 (모바일)',
|
|
10121
|
+
on: '모바일 사이트는 한 페이지에서 알림을 세 번(약 4분 반)만 확인합니다. 그 뒤에도 화면을 보는 동안 3분마다 이어서 확인합니다.',
|
|
10122
|
+
off: '사이트 기본처럼 페이지를 옮길 때만 다시 확인합니다.',
|
|
10123
|
+
refresh: 'none'
|
|
10124
|
+
},
|
|
9842
10125
|
{
|
|
9843
10126
|
group: 'dccon',
|
|
9844
10127
|
key: 'blockDccon',
|
|
@@ -10048,6 +10331,10 @@
|
|
|
10048
10331
|
repinReadColors();
|
|
10049
10332
|
}
|
|
10050
10333
|
|
|
10334
|
+
if (item.key === 'alarmBubble') {
|
|
10335
|
+
paintAlarmBadge();
|
|
10336
|
+
}
|
|
10337
|
+
|
|
10051
10338
|
break;
|
|
10052
10339
|
|
|
10053
10340
|
case 'log':
|
|
@@ -11080,6 +11367,9 @@
|
|
|
11080
11367
|
const SYNC_MIN_GAP = 20000;
|
|
11081
11368
|
const SYNC_POLL = 60000;
|
|
11082
11369
|
const SYNC_LOCK_MS = 20000;
|
|
11370
|
+
const SYNC_RETRY = [3000, 10000, 30000];
|
|
11371
|
+
const SYNC_FAIL_SHOW = 3;
|
|
11372
|
+
const SYNC_STUCK_MS = 30000;
|
|
11083
11373
|
const SYNC_DEVICE_REFRESH = 10 * 60 * 1000;
|
|
11084
11374
|
const SYNC_DEVICE_STALE = 60 * 24 * 3600 * 1000;
|
|
11085
11375
|
const SYNC_LOG_MAX = 20;
|
|
@@ -11094,6 +11384,7 @@
|
|
|
11094
11384
|
{ key: 'words', label: '단어', fields: ['keywords', 'titles'] },
|
|
11095
11385
|
{ key: 'heads', label: '말머리', fields: ['heads'] },
|
|
11096
11386
|
{ key: 'scraps', label: '스크랩', fields: ['scraps'] },
|
|
11387
|
+
{ key: 'recent', label: '최근 갤러리', fields: ['recent'] },
|
|
11097
11388
|
{ key: 'settings', label: '설정', fields: ['settings'] }
|
|
11098
11389
|
];
|
|
11099
11390
|
|
|
@@ -11260,7 +11551,8 @@
|
|
|
11260
11551
|
titles: [...blockedTitles],
|
|
11261
11552
|
heads: JSON.parse(JSON.stringify(hiddenHeadMap || {})),
|
|
11262
11553
|
scraps: scraps.map(item => ({ ...item })),
|
|
11263
|
-
settings: syncedSettings()
|
|
11554
|
+
settings: syncedSettings(),
|
|
11555
|
+
recent: collectRecentForSync()
|
|
11264
11556
|
});
|
|
11265
11557
|
|
|
11266
11558
|
const skippedFields = skip => SYNC_PARTS.filter(part => skip.includes(part.key)).flatMap(part => part.fields);
|
|
@@ -11366,7 +11658,8 @@
|
|
|
11366
11658
|
titles,
|
|
11367
11659
|
heads: mergeHeads(local.heads, base && base.heads, remote && remote.heads),
|
|
11368
11660
|
scraps: mergeScraps(local.scraps, base && base.scraps, remote && remote.scraps),
|
|
11369
|
-
settings: mergeSettings(local.settings, base && base.settings, remote && remote.settings)
|
|
11661
|
+
settings: mergeSettings(local.settings, base && base.settings, remote && remote.settings),
|
|
11662
|
+
recent: mergeRecent(local.recent, base && base.recent, remote && remote.recent)
|
|
11370
11663
|
};
|
|
11371
11664
|
|
|
11372
11665
|
skippedFields(skip).forEach(field => {
|
|
@@ -11416,9 +11709,18 @@
|
|
|
11416
11709
|
|
|
11417
11710
|
let syncApplying = false;
|
|
11418
11711
|
|
|
11712
|
+
const blockSyncData = () => {
|
|
11713
|
+
const data = collectSyncData();
|
|
11714
|
+
|
|
11715
|
+
delete data.recent;
|
|
11716
|
+
|
|
11717
|
+
return JSON.stringify(data);
|
|
11718
|
+
};
|
|
11719
|
+
|
|
11419
11720
|
const applySyncData = data => {
|
|
11721
|
+
const recentChanged = data && 'recent' in data ? applyRecentFromSync(data.recent) : false;
|
|
11420
11722
|
const clean = sanitizeBackup({ app: BACKUP_APP, ...data });
|
|
11421
|
-
const before =
|
|
11723
|
+
const before = blockSyncData();
|
|
11422
11724
|
|
|
11423
11725
|
syncApplying = true;
|
|
11424
11726
|
|
|
@@ -11465,13 +11767,13 @@
|
|
|
11465
11767
|
syncApplying = false;
|
|
11466
11768
|
}
|
|
11467
11769
|
|
|
11468
|
-
const changed = before !==
|
|
11770
|
+
const changed = before !== blockSyncData();
|
|
11469
11771
|
|
|
11470
11772
|
if (changed && initialized) {
|
|
11471
11773
|
refreshAfterBlockChange();
|
|
11472
11774
|
}
|
|
11473
11775
|
|
|
11474
|
-
return changed;
|
|
11776
|
+
return changed || recentChanged;
|
|
11475
11777
|
};
|
|
11476
11778
|
|
|
11477
11779
|
const saveSyncUndo = (before, incoming, summary, from) => {
|
|
@@ -11501,6 +11803,20 @@
|
|
|
11501
11803
|
return /^https:\/\/[^/\s]+/.test(base) ? `${base}${path}` : '';
|
|
11502
11804
|
};
|
|
11503
11805
|
|
|
11806
|
+
const syncError = (message, { transient = false, quiet = false } = {}) => Object.assign(new Error(message), { transient, quiet });
|
|
11807
|
+
const statusError = status => syncError(status === 413 ? '내용이 너무 큽니다' : `서버 응답 ${status}`, { transient: status >= 500 || status === 429 || status === 408 });
|
|
11808
|
+
|
|
11809
|
+
const syncControllers = new Set();
|
|
11810
|
+
|
|
11811
|
+
const abortSyncRequests = () => {
|
|
11812
|
+
syncControllers.forEach(controller => {
|
|
11813
|
+
if (!controller.dcKeep) {
|
|
11814
|
+
controller.dcQuiet = true;
|
|
11815
|
+
controller.abort();
|
|
11816
|
+
}
|
|
11817
|
+
});
|
|
11818
|
+
};
|
|
11819
|
+
|
|
11504
11820
|
const syncRequest = async (path, { method = 'GET', headers = {}, body, keepalive = false } = {}) => {
|
|
11505
11821
|
const url = syncEndpoint(path);
|
|
11506
11822
|
|
|
@@ -11512,22 +11828,42 @@
|
|
|
11512
11828
|
const timer = controller ? setTimeout(() => controller.abort(), 15000) : null;
|
|
11513
11829
|
const payload = body === undefined ? undefined : JSON.stringify(body);
|
|
11514
11830
|
|
|
11831
|
+
if (controller) {
|
|
11832
|
+
controller.dcKeep = keepalive;
|
|
11833
|
+
syncControllers.add(controller);
|
|
11834
|
+
}
|
|
11835
|
+
|
|
11515
11836
|
try {
|
|
11516
|
-
|
|
11837
|
+
let response;
|
|
11838
|
+
|
|
11839
|
+
try {
|
|
11840
|
+
response = await fetch(url, {
|
|
11517
11841
|
method,
|
|
11518
11842
|
headers: { 'Content-Type': 'application/json', ...headers },
|
|
11519
11843
|
body: payload,
|
|
11520
11844
|
credentials: 'omit',
|
|
11521
11845
|
cache: 'no-store',
|
|
11522
11846
|
referrerPolicy: 'no-referrer',
|
|
11523
|
-
|
|
11524
|
-
|
|
11525
|
-
|
|
11847
|
+
keepalive: keepalive && (!payload || payload.length < 60000),
|
|
11848
|
+
signal: controller ? controller.signal : undefined
|
|
11849
|
+
});
|
|
11850
|
+
} catch (error) {
|
|
11851
|
+
if (controller && controller.dcQuiet) {
|
|
11852
|
+
throw syncError('페이지를 떠나며 멈춤', { quiet: true });
|
|
11853
|
+
}
|
|
11854
|
+
|
|
11855
|
+
throw syncError(error && error.name === 'AbortError' ? '서버 응답이 없습니다' : '서버에 연결하지 못했습니다', { transient: true });
|
|
11856
|
+
}
|
|
11857
|
+
|
|
11526
11858
|
const data = await response.json().catch(() => ({}));
|
|
11527
11859
|
|
|
11528
11860
|
return { status: response.status, data };
|
|
11529
11861
|
} finally {
|
|
11530
11862
|
clearTimeout(timer);
|
|
11863
|
+
|
|
11864
|
+
if (controller) {
|
|
11865
|
+
syncControllers.delete(controller);
|
|
11866
|
+
}
|
|
11531
11867
|
}
|
|
11532
11868
|
};
|
|
11533
11869
|
|
|
@@ -11610,12 +11946,35 @@
|
|
|
11610
11946
|
};
|
|
11611
11947
|
|
|
11612
11948
|
let syncRunning = false;
|
|
11949
|
+
let syncRunId = 0;
|
|
11950
|
+
let syncRunSince = 0;
|
|
11951
|
+
let syncEpoch = 0;
|
|
11952
|
+
let pageLeaving = false;
|
|
11613
11953
|
let syncAgain = false;
|
|
11614
11954
|
let syncTimer = null;
|
|
11615
11955
|
let syncPending = false;
|
|
11616
|
-
let lastSyncAt = 0;
|
|
11617
11956
|
let lastErrorMsg = '';
|
|
11618
11957
|
|
|
11958
|
+
const readSyncRt = () => readObject(STORAGE.syncRt);
|
|
11959
|
+
const writeSyncRt = patch => writeJson(STORAGE.syncRt, { ...readSyncRt(), ...patch });
|
|
11960
|
+
const lastSyncTry = () => Number(readSyncRt().lastTry) || 0;
|
|
11961
|
+
|
|
11962
|
+
const resetStuckSync = (force = false) => {
|
|
11963
|
+
if (syncRunning && (force || Date.now() - syncRunSince > SYNC_STUCK_MS)) {
|
|
11964
|
+
syncRunId++;
|
|
11965
|
+
syncRunning = false;
|
|
11966
|
+
releaseSyncLock();
|
|
11967
|
+
setSyncPhase('');
|
|
11968
|
+
}
|
|
11969
|
+
};
|
|
11970
|
+
|
|
11971
|
+
const syncDueOnReturn = () => {
|
|
11972
|
+
const rt = readSyncRt();
|
|
11973
|
+
const state = readObject(STORAGE.syncState);
|
|
11974
|
+
|
|
11975
|
+
return syncPending || rt.dirty === true || (state.at && state.ok === false) || Date.now() - lastSyncTry() > SYNC_MIN_GAP;
|
|
11976
|
+
};
|
|
11977
|
+
|
|
11619
11978
|
const syncReady = () => {
|
|
11620
11979
|
const conf = readSyncConf();
|
|
11621
11980
|
|
|
@@ -11649,6 +12008,8 @@
|
|
|
11649
12008
|
return false;
|
|
11650
12009
|
}
|
|
11651
12010
|
|
|
12011
|
+
resetStuckSync();
|
|
12012
|
+
|
|
11652
12013
|
if (syncRunning) {
|
|
11653
12014
|
syncAgain = true;
|
|
11654
12015
|
return false;
|
|
@@ -11665,7 +12026,7 @@
|
|
|
11665
12026
|
return false;
|
|
11666
12027
|
}
|
|
11667
12028
|
|
|
11668
|
-
if (!takeSyncLock(manual)) {
|
|
12029
|
+
if (!takeSyncLock(manual || leaving)) {
|
|
11669
12030
|
scheduleSync(SYNC_LOCK_MS / 2);
|
|
11670
12031
|
return false;
|
|
11671
12032
|
}
|
|
@@ -11673,7 +12034,11 @@
|
|
|
11673
12034
|
syncRunning = true;
|
|
11674
12035
|
syncPending = false;
|
|
11675
12036
|
clearTimeout(syncTimer);
|
|
11676
|
-
|
|
12037
|
+
syncRunSince = Date.now();
|
|
12038
|
+
writeSyncRt({ lastTry: syncRunSince });
|
|
12039
|
+
|
|
12040
|
+
const runId = ++syncRunId;
|
|
12041
|
+
const epoch = syncEpoch;
|
|
11677
12042
|
|
|
11678
12043
|
const key = readSyncKey();
|
|
11679
12044
|
const conf = readSyncConf();
|
|
@@ -11696,7 +12061,7 @@
|
|
|
11696
12061
|
}
|
|
11697
12062
|
|
|
11698
12063
|
if (got.status !== 200) {
|
|
11699
|
-
throw
|
|
12064
|
+
throw statusError(got.status);
|
|
11700
12065
|
}
|
|
11701
12066
|
|
|
11702
12067
|
setSyncPhase('merge');
|
|
@@ -11736,7 +12101,7 @@
|
|
|
11736
12101
|
}
|
|
11737
12102
|
|
|
11738
12103
|
if (put.status !== 200) {
|
|
11739
|
-
throw
|
|
12104
|
+
throw statusError(put.status);
|
|
11740
12105
|
}
|
|
11741
12106
|
|
|
11742
12107
|
newRev = Number(put.data.rev) || rev + 1;
|
|
@@ -11772,6 +12137,7 @@
|
|
|
11772
12137
|
const msg = changed ? `${from}에서 받음 · ${summary}` : sentSummary ? `올림 · ${sentSummary}` : '최신 상태입니다';
|
|
11773
12138
|
|
|
11774
12139
|
writeSyncState({ ok: true, msg });
|
|
12140
|
+
writeSyncRt({ fails: 0, dirty: syncPending });
|
|
11775
12141
|
lastErrorMsg = '';
|
|
11776
12142
|
|
|
11777
12143
|
if (changed) {
|
|
@@ -11796,9 +12162,37 @@
|
|
|
11796
12162
|
return true;
|
|
11797
12163
|
}
|
|
11798
12164
|
|
|
11799
|
-
throw
|
|
12165
|
+
throw syncError('다른 기기와 동시에 바뀌어 잠시 뒤 다시 맞춥니다', { transient: true });
|
|
11800
12166
|
} catch (error) {
|
|
11801
|
-
|
|
12167
|
+
syncPending = true;
|
|
12168
|
+
|
|
12169
|
+
if (runId !== syncRunId || (error && error.quiet) || pageLeaving || leaving || epoch !== syncEpoch || document.visibilityState === 'hidden') {
|
|
12170
|
+
writeSyncRt({ dirty: true });
|
|
12171
|
+
|
|
12172
|
+
if (runId === syncRunId && !pageLeaving && document.visibilityState !== 'hidden') {
|
|
12173
|
+
scheduleSync(SYNC_RETRY[0]);
|
|
12174
|
+
}
|
|
12175
|
+
|
|
12176
|
+
return false;
|
|
12177
|
+
}
|
|
12178
|
+
|
|
12179
|
+
if (navigator.onLine === false) {
|
|
12180
|
+
writeSyncRt({ dirty: true });
|
|
12181
|
+
writeSyncState({ ok: false, offline: true, msg: '오프라인 · 연결되면 맞춥니다' });
|
|
12182
|
+
return false;
|
|
12183
|
+
}
|
|
12184
|
+
|
|
12185
|
+
const transient = !!(error && (error.transient || error.name === 'TypeError'));
|
|
12186
|
+
const fails = (Number(readSyncRt().fails) || 0) + 1;
|
|
12187
|
+
|
|
12188
|
+
writeSyncRt({ fails, dirty: true });
|
|
12189
|
+
|
|
12190
|
+
if (transient && !manual && fails < SYNC_FAIL_SHOW) {
|
|
12191
|
+
scheduleSync(SYNC_RETRY[fails - 1]);
|
|
12192
|
+
return false;
|
|
12193
|
+
}
|
|
12194
|
+
|
|
12195
|
+
const msg = String((error && error.message) || error);
|
|
11802
12196
|
|
|
11803
12197
|
writeSyncState({ ok: false, msg });
|
|
11804
12198
|
|
|
@@ -11811,16 +12205,17 @@
|
|
|
11811
12205
|
}
|
|
11812
12206
|
|
|
11813
12207
|
lastErrorMsg = msg;
|
|
11814
|
-
|
|
11815
|
-
scheduleSync(SYNC_POLL);
|
|
12208
|
+
scheduleSync(transient ? SYNC_RETRY[Math.min(fails - 1, SYNC_RETRY.length - 1)] : SYNC_POLL);
|
|
11816
12209
|
|
|
11817
12210
|
return false;
|
|
11818
12211
|
} finally {
|
|
11819
|
-
|
|
11820
|
-
|
|
11821
|
-
|
|
12212
|
+
if (runId === syncRunId) {
|
|
12213
|
+
syncRunning = false;
|
|
12214
|
+
releaseSyncLock();
|
|
12215
|
+
setSyncPhase('');
|
|
12216
|
+
}
|
|
11822
12217
|
|
|
11823
|
-
if (syncAgain) {
|
|
12218
|
+
if (syncAgain && runId === syncRunId) {
|
|
11824
12219
|
syncAgain = false;
|
|
11825
12220
|
scheduleSync(SYNC_DEBOUNCE);
|
|
11826
12221
|
}
|
|
@@ -11870,6 +12265,7 @@
|
|
|
11870
12265
|
storageWriteListeners.push(key => {
|
|
11871
12266
|
if (!syncApplying && SYNCED_STORAGE.includes(key) && syncReady()) {
|
|
11872
12267
|
syncPending = true;
|
|
12268
|
+
writeSyncRt({ dirty: true });
|
|
11873
12269
|
scheduleSync(SYNC_DEBOUNCE);
|
|
11874
12270
|
}
|
|
11875
12271
|
});
|
|
@@ -11904,7 +12300,7 @@
|
|
|
11904
12300
|
return;
|
|
11905
12301
|
}
|
|
11906
12302
|
|
|
11907
|
-
if (document.visibilityState === 'visible' && Date.now() -
|
|
12303
|
+
if (document.visibilityState === 'visible' && Date.now() - lastSyncTry() > SYNC_POLL - 1000) {
|
|
11908
12304
|
syncNow();
|
|
11909
12305
|
}
|
|
11910
12306
|
}, SYNC_POLL);
|
|
@@ -11961,29 +12357,48 @@
|
|
|
11961
12357
|
syncStarted = true;
|
|
11962
12358
|
noteScriptVersion();
|
|
11963
12359
|
|
|
11964
|
-
|
|
12360
|
+
const onReturn = restored => {
|
|
12361
|
+
pageLeaving = false;
|
|
12362
|
+
|
|
11965
12363
|
if (!syncReady()) {
|
|
11966
12364
|
return;
|
|
11967
12365
|
}
|
|
11968
12366
|
|
|
11969
|
-
|
|
11970
|
-
|
|
11971
|
-
|
|
11972
|
-
|
|
11973
|
-
} else if (syncPending) {
|
|
11974
|
-
syncNow({ leaving: true });
|
|
12367
|
+
resetStuckSync(restored);
|
|
12368
|
+
|
|
12369
|
+
if (syncDueOnReturn() && !syncRunning) {
|
|
12370
|
+
scheduleSync(800);
|
|
11975
12371
|
}
|
|
11976
|
-
}
|
|
12372
|
+
};
|
|
12373
|
+
|
|
12374
|
+
const onLeave = unloading => {
|
|
12375
|
+
syncEpoch++;
|
|
12376
|
+
pageLeaving = unloading;
|
|
12377
|
+
|
|
12378
|
+
if (unloading) {
|
|
12379
|
+
abortSyncRequests();
|
|
12380
|
+
}
|
|
12381
|
+
|
|
12382
|
+
clearTimeout(syncTimer);
|
|
11977
12383
|
|
|
11978
|
-
window.addEventListener('pagehide', () => {
|
|
11979
12384
|
if (syncReady() && syncPending && !syncRunning) {
|
|
11980
12385
|
syncNow({ leaving: true });
|
|
11981
12386
|
}
|
|
12387
|
+
};
|
|
12388
|
+
|
|
12389
|
+
document.addEventListener('visibilitychange', () => {
|
|
12390
|
+
if (document.visibilityState === 'visible') {
|
|
12391
|
+
onReturn(false);
|
|
12392
|
+
} else {
|
|
12393
|
+
onLeave(false);
|
|
12394
|
+
}
|
|
11982
12395
|
});
|
|
11983
12396
|
|
|
12397
|
+
window.addEventListener('pagehide', () => onLeave(true));
|
|
12398
|
+
|
|
11984
12399
|
window.addEventListener('pageshow', event => {
|
|
11985
|
-
if (event.persisted
|
|
11986
|
-
|
|
12400
|
+
if (event.persisted) {
|
|
12401
|
+
onReturn(true);
|
|
11987
12402
|
}
|
|
11988
12403
|
});
|
|
11989
12404
|
|
|
@@ -12010,7 +12425,10 @@
|
|
|
12010
12425
|
|
|
12011
12426
|
if (syncReady()) {
|
|
12012
12427
|
startSyncPoll();
|
|
12013
|
-
|
|
12428
|
+
|
|
12429
|
+
if (syncDueOnReturn()) {
|
|
12430
|
+
scheduleSync(1500);
|
|
12431
|
+
}
|
|
12014
12432
|
}
|
|
12015
12433
|
};
|
|
12016
12434
|
|
|
@@ -12575,6 +12993,886 @@
|
|
|
12575
12993
|
return box;
|
|
12576
12994
|
};
|
|
12577
12995
|
|
|
12996
|
+
// =========================================================
|
|
12997
|
+
// 13-3. 최근 방문 갤러리 · 사이트 알림
|
|
12998
|
+
// =========================================================
|
|
12999
|
+
|
|
13000
|
+
const RECENT_LIMIT = 50;
|
|
13001
|
+
const RECENT_NATIVE_CAP = IS_MOBILE_SITE ? 24 : 50;
|
|
13002
|
+
const RECENT_KINDS = ['G', 'M', 'N', 'P'];
|
|
13003
|
+
const RECENT_ID_RE = /^[A-Za-z0-9_.-]{1,64}$/;
|
|
13004
|
+
|
|
13005
|
+
const recentKeyOf = item => `${item.k}:${item.id}`;
|
|
13006
|
+
|
|
13007
|
+
const normalizeRecent = item => {
|
|
13008
|
+
if (!item || typeof item !== 'object' || typeof item.id !== 'string' || !RECENT_ID_RE.test(item.id)) {
|
|
13009
|
+
return null;
|
|
13010
|
+
}
|
|
13011
|
+
|
|
13012
|
+
return {
|
|
13013
|
+
k: RECENT_KINDS.includes(item.k) ? item.k : 'G',
|
|
13014
|
+
id: item.id,
|
|
13015
|
+
n: cleanText(item.n).slice(0, 40),
|
|
13016
|
+
at: Number(item.at) || 0
|
|
13017
|
+
};
|
|
13018
|
+
};
|
|
13019
|
+
|
|
13020
|
+
const tidyRecent = list => {
|
|
13021
|
+
const best = new Map();
|
|
13022
|
+
|
|
13023
|
+
list.map(normalizeRecent).filter(Boolean).forEach(item => {
|
|
13024
|
+
const key = recentKeyOf(item);
|
|
13025
|
+
const prev = best.get(key);
|
|
13026
|
+
|
|
13027
|
+
if (!prev || item.at > prev.at || (item.at === prev.at && !prev.n && item.n)) {
|
|
13028
|
+
best.set(key, prev && !item.n ? { ...item, n: prev.n } : item);
|
|
13029
|
+
}
|
|
13030
|
+
});
|
|
13031
|
+
|
|
13032
|
+
return [...best.values()].sort((a, b) => b.at - a.at).slice(0, RECENT_LIMIT);
|
|
13033
|
+
};
|
|
13034
|
+
|
|
13035
|
+
let recentGalls = tidyRecent(readArray(STORAGE.recent));
|
|
13036
|
+
|
|
13037
|
+
const saveRecent = () => {
|
|
13038
|
+
recentGalls = tidyRecent(recentGalls);
|
|
13039
|
+
writeJson(STORAGE.recent, recentGalls);
|
|
13040
|
+
};
|
|
13041
|
+
|
|
13042
|
+
const recentUrl = item => {
|
|
13043
|
+
if (IS_MOBILE_SITE) {
|
|
13044
|
+
const base = item.k === 'N' ? 'mini' : item.k === 'P' ? 'person' : 'board';
|
|
13045
|
+
|
|
13046
|
+
return `https://m.dcinside.com/${base}/${encodeURIComponent(item.id)}`;
|
|
13047
|
+
}
|
|
13048
|
+
|
|
13049
|
+
const prefix = { G: '', M: '/mgallery', N: '/mini', P: '/person' }[item.k] || '';
|
|
13050
|
+
|
|
13051
|
+
return `https://gall.dcinside.com${prefix}/board/lists/?id=${encodeURIComponent(item.id)}`;
|
|
13052
|
+
};
|
|
13053
|
+
|
|
13054
|
+
const RECENT_KIND_LABEL = { G: '', M: '마이너', N: '미니', P: '인물' };
|
|
13055
|
+
|
|
13056
|
+
const MOBILE_KIND_MARK = { M: '$minor$', N: '$mini$', P: '$person$' };
|
|
13057
|
+
const PC_KIND = { G: 'G', M: 'M', N: 'MI', P: 'PR' };
|
|
13058
|
+
const PC_KIND_BACK = { G: 'G', M: 'M', MI: 'N', PR: 'P' };
|
|
13059
|
+
|
|
13060
|
+
const readNativeRecent = () => {
|
|
13061
|
+
try {
|
|
13062
|
+
if (IS_MOBILE_SITE) {
|
|
13063
|
+
const raw = unescape(localStorage.getItem('m_dcinside_lately') || '');
|
|
13064
|
+
|
|
13065
|
+
return raw.split(',').map(part => {
|
|
13066
|
+
const cut = part.indexOf('|');
|
|
13067
|
+
|
|
13068
|
+
if (cut < 1) {
|
|
13069
|
+
return null;
|
|
13070
|
+
}
|
|
13071
|
+
|
|
13072
|
+
let name = part.slice(cut + 1);
|
|
13073
|
+
let k = 'G';
|
|
13074
|
+
|
|
13075
|
+
Object.entries(MOBILE_KIND_MARK).forEach(([kind, mark]) => {
|
|
13076
|
+
if (name.includes(mark)) {
|
|
13077
|
+
k = kind;
|
|
13078
|
+
name = name.replace(mark, '');
|
|
13079
|
+
}
|
|
13080
|
+
});
|
|
13081
|
+
|
|
13082
|
+
return normalizeRecent({ k, id: part.slice(0, cut).trim(), n: name, at: 0 });
|
|
13083
|
+
}).filter(Boolean);
|
|
13084
|
+
}
|
|
13085
|
+
|
|
13086
|
+
if (!/^gall\.dcinside\.com$/i.test(location.hostname)) {
|
|
13087
|
+
return null;
|
|
13088
|
+
}
|
|
13089
|
+
|
|
13090
|
+
const list = JSON.parse(localStorage.getItem('lately_gallery') || 'null');
|
|
13091
|
+
|
|
13092
|
+
return Array.isArray(list)
|
|
13093
|
+
? list.map(item => item && normalizeRecent({ k: PC_KIND_BACK[item.type] || 'G', id: String(item.id || ''), n: item.name, at: 0 })).filter(Boolean)
|
|
13094
|
+
: [];
|
|
13095
|
+
} catch {
|
|
13096
|
+
return null;
|
|
13097
|
+
}
|
|
13098
|
+
};
|
|
13099
|
+
|
|
13100
|
+
const writeNativeRecent = list => {
|
|
13101
|
+
const top = list.slice(0, RECENT_NATIVE_CAP);
|
|
13102
|
+
|
|
13103
|
+
try {
|
|
13104
|
+
if (IS_MOBILE_SITE) {
|
|
13105
|
+
const text = top.map(item => `${item.id}|${(item.n || item.id).replace(/[,|]/g, ' ')}${MOBILE_KIND_MARK[item.k] ? ` ${MOBILE_KIND_MARK[item.k]}` : ''},`).join('');
|
|
13106
|
+
|
|
13107
|
+
localStorage.setItem('m_dcinside_lately', escape(text));
|
|
13108
|
+
} else if (/^gall\.dcinside\.com$/i.test(location.hostname)) {
|
|
13109
|
+
const dir = { G: '', M: 'mgallery/', N: 'mini/', P: 'person/' };
|
|
13110
|
+
|
|
13111
|
+
localStorage.setItem('lately_gallery', JSON.stringify(top.map(item => ({
|
|
13112
|
+
id: item.id,
|
|
13113
|
+
name: item.n || item.id,
|
|
13114
|
+
type: PC_KIND[item.k] || 'G',
|
|
13115
|
+
link: `gall.dcinside.com/${dir[item.k] || ''}board/lists?id=${item.id}`
|
|
13116
|
+
}))));
|
|
13117
|
+
} else {
|
|
13118
|
+
return;
|
|
13119
|
+
}
|
|
13120
|
+
|
|
13121
|
+
writeJson(STORAGE.recentNative, top.map(recentKeyOf));
|
|
13122
|
+
} catch {
|
|
13123
|
+
}
|
|
13124
|
+
};
|
|
13125
|
+
|
|
13126
|
+
const importNativeRecent = () => {
|
|
13127
|
+
const native = readNativeRecent();
|
|
13128
|
+
|
|
13129
|
+
if (!native) {
|
|
13130
|
+
return false;
|
|
13131
|
+
}
|
|
13132
|
+
|
|
13133
|
+
const before = JSON.stringify(recentGalls);
|
|
13134
|
+
const nativeKeys = new Set(native.map(recentKeyOf));
|
|
13135
|
+
const snapshot = readArray(STORAGE.recentNative).filter(key => typeof key === 'string');
|
|
13136
|
+
|
|
13137
|
+
if (snapshot.length && native.length < RECENT_NATIVE_CAP - 1) {
|
|
13138
|
+
const removed = new Set(snapshot.filter(key => !nativeKeys.has(key)));
|
|
13139
|
+
|
|
13140
|
+
if (removed.size) {
|
|
13141
|
+
recentGalls = recentGalls.filter(item => !removed.has(recentKeyOf(item)));
|
|
13142
|
+
}
|
|
13143
|
+
}
|
|
13144
|
+
|
|
13145
|
+
const mine = new Map(recentGalls.map(item => [recentKeyOf(item), item]));
|
|
13146
|
+
let prevAt = Date.now();
|
|
13147
|
+
|
|
13148
|
+
native.forEach(item => {
|
|
13149
|
+
const known = mine.get(recentKeyOf(item));
|
|
13150
|
+
|
|
13151
|
+
if (known) {
|
|
13152
|
+
prevAt = Math.min(prevAt, known.at || prevAt);
|
|
13153
|
+
|
|
13154
|
+
if (!known.n && item.n) {
|
|
13155
|
+
known.n = item.n;
|
|
13156
|
+
}
|
|
13157
|
+
} else {
|
|
13158
|
+
prevAt -= 1000;
|
|
13159
|
+
recentGalls.push({ ...item, at: prevAt });
|
|
13160
|
+
}
|
|
13161
|
+
});
|
|
13162
|
+
|
|
13163
|
+
recentGalls = tidyRecent(recentGalls);
|
|
13164
|
+
|
|
13165
|
+
const changed = JSON.stringify(recentGalls) !== before;
|
|
13166
|
+
|
|
13167
|
+
if (changed) {
|
|
13168
|
+
writeJson(STORAGE.recent, recentGalls);
|
|
13169
|
+
}
|
|
13170
|
+
|
|
13171
|
+
return changed;
|
|
13172
|
+
};
|
|
13173
|
+
|
|
13174
|
+
const exportNativeRecent = () => {
|
|
13175
|
+
const native = readNativeRecent();
|
|
13176
|
+
|
|
13177
|
+
if (!native) {
|
|
13178
|
+
return;
|
|
13179
|
+
}
|
|
13180
|
+
|
|
13181
|
+
const want = recentGalls.slice(0, RECENT_NATIVE_CAP);
|
|
13182
|
+
|
|
13183
|
+
if (JSON.stringify(native.map(recentKeyOf)) !== JSON.stringify(want.map(recentKeyOf))) {
|
|
13184
|
+
writeNativeRecent(want);
|
|
13185
|
+
} else {
|
|
13186
|
+
writeJson(STORAGE.recentNative, want.map(recentKeyOf));
|
|
13187
|
+
}
|
|
13188
|
+
};
|
|
13189
|
+
|
|
13190
|
+
const currentGalleryVisit = () => {
|
|
13191
|
+
const id = getGalleryId();
|
|
13192
|
+
|
|
13193
|
+
if (!id || id === HOME_GALLERY_KEY || !RECENT_ID_RE.test(id)) {
|
|
13194
|
+
return null;
|
|
13195
|
+
}
|
|
13196
|
+
|
|
13197
|
+
let k = 'G';
|
|
13198
|
+
let name = '';
|
|
13199
|
+
|
|
13200
|
+
if (IS_MOBILE_SITE) {
|
|
13201
|
+
if (!/^\/(board|mini|person)\//.test(location.pathname)) {
|
|
13202
|
+
return null;
|
|
13203
|
+
}
|
|
13204
|
+
|
|
13205
|
+
k = /^\/mini\//.test(location.pathname)
|
|
13206
|
+
? 'N'
|
|
13207
|
+
: /^\/person\//.test(location.pathname)
|
|
13208
|
+
? 'P'
|
|
13209
|
+
: (document.getElementById('is_minor')?.value || '') !== '' ? 'M' : 'G';
|
|
13210
|
+
name = document.querySelector('.gall-tit-box .gall-tit')?.textContent || '';
|
|
13211
|
+
} else {
|
|
13212
|
+
if (!/^\/(mgallery\/|mini\/|person\/)?board\/(lists|view)/.test(location.pathname)) {
|
|
13213
|
+
return null;
|
|
13214
|
+
}
|
|
13215
|
+
|
|
13216
|
+
k = /^\/mgallery\//.test(location.pathname) ? 'M' : /^\/mini\//.test(location.pathname) ? 'N' : /^\/person\//.test(location.pathname) ? 'P' : 'G';
|
|
13217
|
+
|
|
13218
|
+
const head = document.querySelector('.page_head h2 a') || document.querySelector('.page_head h2');
|
|
13219
|
+
|
|
13220
|
+
name = head ? [...head.childNodes].filter(node => node.nodeType === 3).map(node => node.textContent).join(' ') : '';
|
|
13221
|
+
|
|
13222
|
+
if (!cleanText(name)) {
|
|
13223
|
+
const og = document.querySelector('meta[property="og:title"]')?.content || '';
|
|
13224
|
+
|
|
13225
|
+
name = og.split(' - ').slice(-2, -1)[0] || og.split(' - ')[0] || '';
|
|
13226
|
+
}
|
|
13227
|
+
}
|
|
13228
|
+
|
|
13229
|
+
name = cleanText(name).replace(/\s*(마이너|미니|인물)?\s*갤러리$/, '');
|
|
13230
|
+
|
|
13231
|
+
return normalizeRecent({ k, id, n: name || id, at: Date.now() });
|
|
13232
|
+
};
|
|
13233
|
+
|
|
13234
|
+
const recordGalleryVisit = () => {
|
|
13235
|
+
const visit = currentGalleryVisit();
|
|
13236
|
+
|
|
13237
|
+
if (!visit) {
|
|
13238
|
+
return;
|
|
13239
|
+
}
|
|
13240
|
+
|
|
13241
|
+
const prev = recentGalls.find(item => recentKeyOf(item) === recentKeyOf(visit));
|
|
13242
|
+
|
|
13243
|
+
if (prev && recentGalls[0] === prev && Date.now() - prev.at < 60000 && prev.n === visit.n) {
|
|
13244
|
+
return;
|
|
13245
|
+
}
|
|
13246
|
+
|
|
13247
|
+
recentGalls = [visit, ...recentGalls.filter(item => item !== prev)];
|
|
13248
|
+
saveRecent();
|
|
13249
|
+
};
|
|
13250
|
+
|
|
13251
|
+
const initRecentEarly = () => {
|
|
13252
|
+
importNativeRecent();
|
|
13253
|
+
exportNativeRecent();
|
|
13254
|
+
};
|
|
13255
|
+
|
|
13256
|
+
const initRecent = () => {
|
|
13257
|
+
importNativeRecent();
|
|
13258
|
+
recordGalleryVisit();
|
|
13259
|
+
exportNativeRecent();
|
|
13260
|
+
|
|
13261
|
+
const follow = () => {
|
|
13262
|
+
if (document.visibilityState === 'visible' && importNativeRecent()) {
|
|
13263
|
+
exportNativeRecent();
|
|
13264
|
+
}
|
|
13265
|
+
};
|
|
13266
|
+
|
|
13267
|
+
setTimeout(follow, 3000);
|
|
13268
|
+
document.addEventListener('visibilitychange', follow);
|
|
13269
|
+
};
|
|
13270
|
+
|
|
13271
|
+
const collectRecentForSync = () => recentGalls.map(item => ({ ...item }));
|
|
13272
|
+
|
|
13273
|
+
const mergeRecent = (local = [], base, remote) => {
|
|
13274
|
+
if (!Array.isArray(remote)) {
|
|
13275
|
+
return tidyRecent(local || []);
|
|
13276
|
+
}
|
|
13277
|
+
|
|
13278
|
+
const pick = new Map();
|
|
13279
|
+
|
|
13280
|
+
[...(remote || []), ...(local || [])].map(normalizeRecent).filter(Boolean).forEach(item => {
|
|
13281
|
+
const key = recentKeyOf(item);
|
|
13282
|
+
const prev = pick.get(key);
|
|
13283
|
+
|
|
13284
|
+
if (!prev || item.at > prev.at) {
|
|
13285
|
+
pick.set(key, prev && !item.n ? { ...item, n: prev.n } : item);
|
|
13286
|
+
}
|
|
13287
|
+
});
|
|
13288
|
+
|
|
13289
|
+
const keys = mergeList(
|
|
13290
|
+
(local || []).map(item => recentKeyOf(item)),
|
|
13291
|
+
Array.isArray(base) ? base.map(item => recentKeyOf(item)) : null,
|
|
13292
|
+
remote.map(item => recentKeyOf(item))
|
|
13293
|
+
);
|
|
13294
|
+
|
|
13295
|
+
return tidyRecent(keys.map(key => pick.get(key)).filter(Boolean));
|
|
13296
|
+
};
|
|
13297
|
+
|
|
13298
|
+
const applyRecentFromSync = list => {
|
|
13299
|
+
if (!Array.isArray(list)) {
|
|
13300
|
+
return false;
|
|
13301
|
+
}
|
|
13302
|
+
|
|
13303
|
+
const next = tidyRecent(list);
|
|
13304
|
+
|
|
13305
|
+
if (JSON.stringify(next) === JSON.stringify(recentGalls)) {
|
|
13306
|
+
return false;
|
|
13307
|
+
}
|
|
13308
|
+
|
|
13309
|
+
recentGalls = next;
|
|
13310
|
+
writeJson(STORAGE.recent, recentGalls);
|
|
13311
|
+
exportNativeRecent();
|
|
13312
|
+
|
|
13313
|
+
return true;
|
|
13314
|
+
};
|
|
13315
|
+
|
|
13316
|
+
// =========================================================
|
|
13317
|
+
|
|
13318
|
+
// =========================================================
|
|
13319
|
+
|
|
13320
|
+
const ALARM_TAGS = { R: '댓글', RR: '답글', P: '펌', hit: '힛갤', B: '실베', EC: '동영상', G: '방명록' };
|
|
13321
|
+
const ALARM_POLL_GAP = 3 * 60 * 1000;
|
|
13322
|
+
const ALARM_BUBBLE_MAX = 3;
|
|
13323
|
+
const ALARM_BUBBLE_FRESH = 10 * 60 * 1000;
|
|
13324
|
+
|
|
13325
|
+
const siteUserId = () => cleanText(document.getElementById('user_id')?.value || '');
|
|
13326
|
+
|
|
13327
|
+
const alarmStorageKeys = () => {
|
|
13328
|
+
try {
|
|
13329
|
+
if (IS_MOBILE_SITE) {
|
|
13330
|
+
const id = siteUserId();
|
|
13331
|
+
|
|
13332
|
+
return id ? [`alarm_notification_${id}`] : [];
|
|
13333
|
+
}
|
|
13334
|
+
|
|
13335
|
+
if (!/^gall\.dcinside\.com$/i.test(location.hostname)) {
|
|
13336
|
+
return [];
|
|
13337
|
+
}
|
|
13338
|
+
|
|
13339
|
+
const keys = [];
|
|
13340
|
+
|
|
13341
|
+
for (let i = 0; i < localStorage.length; i++) {
|
|
13342
|
+
const key = localStorage.key(i);
|
|
13343
|
+
|
|
13344
|
+
if (/^alarm_list/.test(key || '')) {
|
|
13345
|
+
keys.push(key);
|
|
13346
|
+
}
|
|
13347
|
+
}
|
|
13348
|
+
|
|
13349
|
+
return keys;
|
|
13350
|
+
} catch {
|
|
13351
|
+
return [];
|
|
13352
|
+
}
|
|
13353
|
+
};
|
|
13354
|
+
|
|
13355
|
+
const readAlarmRaw = () => alarmStorageKeys().map(key => {
|
|
13356
|
+
try {
|
|
13357
|
+
return localStorage.getItem(key) || '';
|
|
13358
|
+
} catch {
|
|
13359
|
+
return '';
|
|
13360
|
+
}
|
|
13361
|
+
}).join('\u0001');
|
|
13362
|
+
|
|
13363
|
+
const mobileAlarmHref = item => {
|
|
13364
|
+
const raw = String(item.id || '');
|
|
13365
|
+
const no = String(item.no || '').replace(/\D/g, '');
|
|
13366
|
+
|
|
13367
|
+
if (item.type === 'B' || raw === 'dcbest') {
|
|
13368
|
+
return no ? `https://m.dcinside.com/board/dcbest/${no}` : '';
|
|
13369
|
+
}
|
|
13370
|
+
|
|
13371
|
+
if (item.type === 'G') {
|
|
13372
|
+
const me = siteUserId();
|
|
13373
|
+
|
|
13374
|
+
return me ? `https://m.dcinside.com/gallog/${encodeURIComponent(me)}/guestbook` : '';
|
|
13375
|
+
}
|
|
13376
|
+
|
|
13377
|
+
const base = raw.startsWith('mi$') ? 'mini' : raw.startsWith('pr$') ? 'person' : 'board';
|
|
13378
|
+
const id = raw.replace(/^(mi|pr)\$/, '');
|
|
13379
|
+
|
|
13380
|
+
if (!id || !no || id === 'hit') {
|
|
13381
|
+
return id === 'hit' && no ? `https://m.dcinside.com/board/hit/${no}` : '';
|
|
13382
|
+
}
|
|
13383
|
+
|
|
13384
|
+
const comment = String(item.comment_no || '').replace(/\D/g, '');
|
|
13385
|
+
|
|
13386
|
+
return `https://m.dcinside.com/${base}/${encodeURIComponent(id)}/${no}${comment ? `?comment=${comment}` : ''}`;
|
|
13387
|
+
};
|
|
13388
|
+
|
|
13389
|
+
const readSiteAlarms = () => {
|
|
13390
|
+
const out = [];
|
|
13391
|
+
|
|
13392
|
+
alarmStorageKeys().forEach(key => {
|
|
13393
|
+
let list = [];
|
|
13394
|
+
|
|
13395
|
+
try {
|
|
13396
|
+
list = JSON.parse(localStorage.getItem(key) || '[]');
|
|
13397
|
+
} catch {
|
|
13398
|
+
list = [];
|
|
13399
|
+
}
|
|
13400
|
+
|
|
13401
|
+
(Array.isArray(list) ? list : []).forEach(item => {
|
|
13402
|
+
if (!item || typeof item !== 'object') {
|
|
13403
|
+
return;
|
|
13404
|
+
}
|
|
13405
|
+
|
|
13406
|
+
if (IS_MOBILE_SITE) {
|
|
13407
|
+
const at = Number(item.reg_date) || 0;
|
|
13408
|
+
const tag = item.id === 'hit' ? ALARM_TAGS.hit : ALARM_TAGS[item.type] || '알림';
|
|
13409
|
+
|
|
13410
|
+
out.push({
|
|
13411
|
+
key: `${item.type}:${item.id}:${item.no}:${item.comment_no || ''}:${at}`,
|
|
13412
|
+
tag,
|
|
13413
|
+
text: cleanText(item.subject).slice(0, 140) || '(내용 없음)',
|
|
13414
|
+
href: mobileAlarmHref(item),
|
|
13415
|
+
at
|
|
13416
|
+
});
|
|
13417
|
+
} else {
|
|
13418
|
+
const at = (Number(item.time) || 0) * 1000;
|
|
13419
|
+
const text = cleanText(`${item.prefix || ''} ${item.content || ''}`).replace(/<[^>]*>/g, '');
|
|
13420
|
+
const tagMatch = /^\[([^\]]{1,10})\]/.exec(cleanText(item.prefix || ''));
|
|
13421
|
+
let href = String(item.link || '');
|
|
13422
|
+
|
|
13423
|
+
if (href && !/^https?:\/\//.test(href)) {
|
|
13424
|
+
href = href.startsWith('/') ? `https://gall.dcinside.com${href}` : '';
|
|
13425
|
+
}
|
|
13426
|
+
|
|
13427
|
+
out.push({
|
|
13428
|
+
key: `${item.idx || ''}:${item.link || ''}:${at}`,
|
|
13429
|
+
tag: item.type === 'manager' ? '관리' : tagMatch ? tagMatch[1] : '알림',
|
|
13430
|
+
text: text.slice(0, 140) || '(내용 없음)',
|
|
13431
|
+
href: /^https:\/\/[a-z0-9.-]*dcinside\.com\//i.test(href) ? href : '',
|
|
13432
|
+
at,
|
|
13433
|
+
idx: item.idx
|
|
13434
|
+
});
|
|
13435
|
+
}
|
|
13436
|
+
});
|
|
13437
|
+
});
|
|
13438
|
+
|
|
13439
|
+
const seen = new Set();
|
|
13440
|
+
|
|
13441
|
+
return out
|
|
13442
|
+
.filter(item => !seen.has(item.key) && seen.add(item.key))
|
|
13443
|
+
.sort((a, b) => b.at - a.at)
|
|
13444
|
+
.slice(0, 30);
|
|
13445
|
+
};
|
|
13446
|
+
|
|
13447
|
+
const readAlarmMarks = () => {
|
|
13448
|
+
const marks = readObject(STORAGE.alarmSeen);
|
|
13449
|
+
|
|
13450
|
+
return { seen: Number(marks.seen) || 0, shown: Number(marks.shown) || 0 };
|
|
13451
|
+
};
|
|
13452
|
+
|
|
13453
|
+
const writeAlarmMarks = patch => writeJson(STORAGE.alarmSeen, { ...readAlarmMarks(), ...patch });
|
|
13454
|
+
|
|
13455
|
+
const unreadAlarmCount = () => {
|
|
13456
|
+
const { seen } = readAlarmMarks();
|
|
13457
|
+
|
|
13458
|
+
return readSiteAlarms().filter(item => item.at > seen).length;
|
|
13459
|
+
};
|
|
13460
|
+
|
|
13461
|
+
const paintAlarmBadge = () => {
|
|
13462
|
+
const fab = document.getElementById('dc-custom-fab');
|
|
13463
|
+
|
|
13464
|
+
if (!fab) {
|
|
13465
|
+
return;
|
|
13466
|
+
}
|
|
13467
|
+
|
|
13468
|
+
const count = settings.alarmBubble === false ? 0 : unreadAlarmCount();
|
|
13469
|
+
|
|
13470
|
+
if (count) {
|
|
13471
|
+
fab.dataset.dcAlarm = count > 9 ? '9+' : String(count);
|
|
13472
|
+
} else {
|
|
13473
|
+
delete fab.dataset.dcAlarm;
|
|
13474
|
+
}
|
|
13475
|
+
};
|
|
13476
|
+
|
|
13477
|
+
const markAlarmsSeen = () => {
|
|
13478
|
+
const latest = readSiteAlarms()[0];
|
|
13479
|
+
|
|
13480
|
+
writeAlarmMarks({ seen: Math.max(Date.now(), latest ? latest.at : 0) });
|
|
13481
|
+
paintAlarmBadge();
|
|
13482
|
+
};
|
|
13483
|
+
|
|
13484
|
+
let bubbleBox = null;
|
|
13485
|
+
|
|
13486
|
+
const placeBubbleBox = () => {
|
|
13487
|
+
if (!bubbleBox) {
|
|
13488
|
+
return;
|
|
13489
|
+
}
|
|
13490
|
+
|
|
13491
|
+
const fab = document.getElementById('dc-custom-fab');
|
|
13492
|
+
const rect = fab && fab.offsetParent !== null ? fab.getBoundingClientRect() : null;
|
|
13493
|
+
const vw = window.innerWidth;
|
|
13494
|
+
const vh = window.innerHeight;
|
|
13495
|
+
|
|
13496
|
+
if (rect && rect.width) {
|
|
13497
|
+
const onRight = rect.left + rect.width / 2 > vw / 2;
|
|
13498
|
+
|
|
13499
|
+
bubbleBox.style.left = onRight ? '' : `${Math.max(8, rect.left)}px`;
|
|
13500
|
+
bubbleBox.style.right = onRight ? `${Math.max(8, vw - rect.right)}px` : '';
|
|
13501
|
+
bubbleBox.style.bottom = `${Math.max(8, vh - rect.top + 10)}px`;
|
|
13502
|
+
bubbleBox.classList.toggle('dc-left', !onRight);
|
|
13503
|
+
} else {
|
|
13504
|
+
bubbleBox.style.left = '';
|
|
13505
|
+
bubbleBox.style.right = '15px';
|
|
13506
|
+
bubbleBox.style.bottom = 'calc(144px + env(safe-area-inset-bottom))';
|
|
13507
|
+
}
|
|
13508
|
+
};
|
|
13509
|
+
|
|
13510
|
+
const ensureBubbleBox = () => {
|
|
13511
|
+
if (bubbleBox && bubbleBox.isConnected) {
|
|
13512
|
+
return bubbleBox;
|
|
13513
|
+
}
|
|
13514
|
+
|
|
13515
|
+
bubbleBox = h('div', 'dc-ui');
|
|
13516
|
+
bubbleBox.id = 'dc-bubbles';
|
|
13517
|
+
bubbleBox.setAttribute('role', 'log');
|
|
13518
|
+
bubbleBox.setAttribute('aria-live', 'polite');
|
|
13519
|
+
document.body.appendChild(bubbleBox);
|
|
13520
|
+
|
|
13521
|
+
return bubbleBox;
|
|
13522
|
+
};
|
|
13523
|
+
|
|
13524
|
+
const dismissBubble = (bubble, direction = 0) => {
|
|
13525
|
+
if (!bubble || bubble.classList.contains('dc-out')) {
|
|
13526
|
+
return;
|
|
13527
|
+
}
|
|
13528
|
+
|
|
13529
|
+
clearTimeout(bubble._dcTimer);
|
|
13530
|
+
bubble.classList.add('dc-out');
|
|
13531
|
+
|
|
13532
|
+
if (direction) {
|
|
13533
|
+
bubble.style.transform = `translateX(${direction * 120}%)`;
|
|
13534
|
+
}
|
|
13535
|
+
|
|
13536
|
+
setTimeout(() => bubble.remove(), 260);
|
|
13537
|
+
};
|
|
13538
|
+
|
|
13539
|
+
const openAlarm = item => {
|
|
13540
|
+
if (!IS_MOBILE_SITE && item.idx && typeof window.alarmReadOk === 'function') {
|
|
13541
|
+
try {
|
|
13542
|
+
window.alarmReadOk(item.idx);
|
|
13543
|
+
} catch {
|
|
13544
|
+
}
|
|
13545
|
+
}
|
|
13546
|
+
|
|
13547
|
+
markAlarmsSeen();
|
|
13548
|
+
|
|
13549
|
+
if (item.href) {
|
|
13550
|
+
location.href = item.href;
|
|
13551
|
+
}
|
|
13552
|
+
};
|
|
13553
|
+
|
|
13554
|
+
const bindBubbleSwipe = bubble => {
|
|
13555
|
+
let startX = 0;
|
|
13556
|
+
let dx = 0;
|
|
13557
|
+
let dragging = false;
|
|
13558
|
+
|
|
13559
|
+
bubble.addEventListener('pointerdown', event => {
|
|
13560
|
+
startX = event.clientX;
|
|
13561
|
+
dx = 0;
|
|
13562
|
+
dragging = true;
|
|
13563
|
+
clearTimeout(bubble._dcTimer);
|
|
13564
|
+
});
|
|
13565
|
+
bubble.addEventListener('pointermove', event => {
|
|
13566
|
+
if (!dragging) {
|
|
13567
|
+
return;
|
|
13568
|
+
}
|
|
13569
|
+
|
|
13570
|
+
dx = event.clientX - startX;
|
|
13571
|
+
|
|
13572
|
+
if (Math.abs(dx) > 6) {
|
|
13573
|
+
bubble.style.transition = 'none';
|
|
13574
|
+
bubble.style.transform = `translateX(${dx}px)`;
|
|
13575
|
+
bubble.style.opacity = String(Math.max(0.2, 1 - Math.abs(dx) / 220));
|
|
13576
|
+
}
|
|
13577
|
+
});
|
|
13578
|
+
|
|
13579
|
+
const end = () => {
|
|
13580
|
+
if (!dragging) {
|
|
13581
|
+
return;
|
|
13582
|
+
}
|
|
13583
|
+
|
|
13584
|
+
dragging = false;
|
|
13585
|
+
bubble.style.transition = '';
|
|
13586
|
+
|
|
13587
|
+
if (Math.abs(dx) > 70) {
|
|
13588
|
+
bubble._dcSwiped = true;
|
|
13589
|
+
dismissBubble(bubble, Math.sign(dx));
|
|
13590
|
+
} else {
|
|
13591
|
+
bubble.style.transform = '';
|
|
13592
|
+
bubble.style.opacity = '';
|
|
13593
|
+
bubble._dcTimer = setTimeout(() => dismissBubble(bubble), 6000);
|
|
13594
|
+
}
|
|
13595
|
+
};
|
|
13596
|
+
|
|
13597
|
+
bubble.addEventListener('pointerup', end);
|
|
13598
|
+
bubble.addEventListener('pointercancel', end);
|
|
13599
|
+
};
|
|
13600
|
+
|
|
13601
|
+
const showAlarmBubble = (item, extra = 0) => {
|
|
13602
|
+
const box = ensureBubbleBox();
|
|
13603
|
+
|
|
13604
|
+
placeBubbleBox();
|
|
13605
|
+
|
|
13606
|
+
while (box.children.length >= ALARM_BUBBLE_MAX) {
|
|
13607
|
+
box.firstElementChild.remove();
|
|
13608
|
+
}
|
|
13609
|
+
|
|
13610
|
+
const bubble = h('div', 'dc-bubble');
|
|
13611
|
+
const top = h('div', 'dc-bubble-top');
|
|
13612
|
+
|
|
13613
|
+
top.appendChild(h('span', 'dc-bubble-tag', item.tag));
|
|
13614
|
+
top.appendChild(h('span', 'dc-bubble-time', extra ? `새 알림 ${extra + 1}개` : timeAgo(item.at)));
|
|
13615
|
+
|
|
13616
|
+
const close = h('button', 'dc-bubble-x', '×');
|
|
13617
|
+
|
|
13618
|
+
close.type = 'button';
|
|
13619
|
+
close.setAttribute('aria-label', '알림 닫기');
|
|
13620
|
+
close.addEventListener('click', event => {
|
|
13621
|
+
event.stopPropagation();
|
|
13622
|
+
dismissBubble(bubble);
|
|
13623
|
+
});
|
|
13624
|
+
top.appendChild(close);
|
|
13625
|
+
|
|
13626
|
+
bubble.appendChild(top);
|
|
13627
|
+
bubble.appendChild(h('div', 'dc-bubble-text', item.text));
|
|
13628
|
+
|
|
13629
|
+
if (extra) {
|
|
13630
|
+
bubble.appendChild(h('div', 'dc-bubble-more', `외 ${extra}개 · 눌러서 모두 보기`));
|
|
13631
|
+
}
|
|
13632
|
+
|
|
13633
|
+
bubble.addEventListener('click', () => {
|
|
13634
|
+
if (bubble._dcSwiped) {
|
|
13635
|
+
return;
|
|
13636
|
+
}
|
|
13637
|
+
|
|
13638
|
+
dismissBubble(bubble);
|
|
13639
|
+
|
|
13640
|
+
if (extra) {
|
|
13641
|
+
openBlockModal('recent');
|
|
13642
|
+
} else {
|
|
13643
|
+
openAlarm(item);
|
|
13644
|
+
}
|
|
13645
|
+
});
|
|
13646
|
+
|
|
13647
|
+
bindBubbleSwipe(bubble);
|
|
13648
|
+
box.appendChild(bubble);
|
|
13649
|
+
requestAnimationFrame(() => bubble.classList.add('dc-in'));
|
|
13650
|
+
bubble._dcTimer = setTimeout(() => dismissBubble(bubble), 8000);
|
|
13651
|
+
};
|
|
13652
|
+
|
|
13653
|
+
let alarmRawMemo = null;
|
|
13654
|
+
let alarmWatchTimer = null;
|
|
13655
|
+
|
|
13656
|
+
const checkSiteAlarms = () => {
|
|
13657
|
+
if (settings.alarmBubble === false || document.visibilityState === 'hidden') {
|
|
13658
|
+
return;
|
|
13659
|
+
}
|
|
13660
|
+
|
|
13661
|
+
const raw = readAlarmRaw();
|
|
13662
|
+
|
|
13663
|
+
if (raw === alarmRawMemo) {
|
|
13664
|
+
return;
|
|
13665
|
+
}
|
|
13666
|
+
|
|
13667
|
+
alarmRawMemo = raw;
|
|
13668
|
+
|
|
13669
|
+
const list = readSiteAlarms();
|
|
13670
|
+
const marks = readAlarmMarks();
|
|
13671
|
+
const latest = list.length ? list[0].at : 0;
|
|
13672
|
+
|
|
13673
|
+
if (!marks.shown) {
|
|
13674
|
+
writeAlarmMarks({ shown: Math.max(latest, Date.now() - ALARM_BUBBLE_FRESH), seen: marks.seen || latest });
|
|
13675
|
+
paintAlarmBadge();
|
|
13676
|
+
return;
|
|
13677
|
+
}
|
|
13678
|
+
|
|
13679
|
+
const fresh = list.filter(item => item.at > marks.shown && Date.now() - item.at < ALARM_BUBBLE_FRESH);
|
|
13680
|
+
|
|
13681
|
+
if (latest > marks.shown) {
|
|
13682
|
+
writeAlarmMarks({ shown: latest });
|
|
13683
|
+
}
|
|
13684
|
+
|
|
13685
|
+
if (fresh.length) {
|
|
13686
|
+
showAlarmBubble(fresh[0], fresh.length > ALARM_BUBBLE_MAX ? fresh.length - 1 : 0);
|
|
13687
|
+
|
|
13688
|
+
if (fresh.length > 1 && fresh.length <= ALARM_BUBBLE_MAX) {
|
|
13689
|
+
fresh.slice(1).reverse().forEach(item => showAlarmBubble(item));
|
|
13690
|
+
}
|
|
13691
|
+
}
|
|
13692
|
+
|
|
13693
|
+
paintAlarmBadge();
|
|
13694
|
+
};
|
|
13695
|
+
|
|
13696
|
+
let alarmPollAt = Date.now();
|
|
13697
|
+
|
|
13698
|
+
const siteAlarmsOff = () => {
|
|
13699
|
+
try {
|
|
13700
|
+
const list = JSON.parse(localStorage.getItem('alarm_setting') || '[]');
|
|
13701
|
+
|
|
13702
|
+
return Array.isArray(list) && list.some(item => item && item.setting === 'onoff' && item.value !== 'on');
|
|
13703
|
+
} catch {
|
|
13704
|
+
return false;
|
|
13705
|
+
}
|
|
13706
|
+
};
|
|
13707
|
+
|
|
13708
|
+
const keepSiteAlarmPolling = () => {
|
|
13709
|
+
if (!IS_MOBILE_SITE || settings.alarmKeepPolling === false || settings.alarmBubble === false) {
|
|
13710
|
+
return;
|
|
13711
|
+
}
|
|
13712
|
+
|
|
13713
|
+
if (document.visibilityState === 'hidden' || !siteUserId() || siteAlarmsOff() || netPausedUntil()) {
|
|
13714
|
+
return;
|
|
13715
|
+
}
|
|
13716
|
+
|
|
13717
|
+
if (Date.now() - alarmPollAt < ALARM_POLL_GAP || typeof window.alarm_notification_pop !== 'function') {
|
|
13718
|
+
return;
|
|
13719
|
+
}
|
|
13720
|
+
|
|
13721
|
+
alarmPollAt = Date.now();
|
|
13722
|
+
|
|
13723
|
+
try {
|
|
13724
|
+
if (typeof window.set_interval_noti === 'number' && window.set_interval_noti >= 1) {
|
|
13725
|
+
window.set_interval_noti = 1;
|
|
13726
|
+
}
|
|
13727
|
+
|
|
13728
|
+
window.alarm_notification_pop();
|
|
13729
|
+
} catch {
|
|
13730
|
+
}
|
|
13731
|
+
};
|
|
13732
|
+
|
|
13733
|
+
const initSiteAlarms = () => {
|
|
13734
|
+
if (alarmWatchTimer || !alarmStorageKeys().length && !siteUserId()) {
|
|
13735
|
+
paintAlarmBadge();
|
|
13736
|
+
return;
|
|
13737
|
+
}
|
|
13738
|
+
|
|
13739
|
+
checkSiteAlarms();
|
|
13740
|
+
alarmWatchTimer = setInterval(() => {
|
|
13741
|
+
checkSiteAlarms();
|
|
13742
|
+
keepSiteAlarmPolling();
|
|
13743
|
+
}, 2000);
|
|
13744
|
+
|
|
13745
|
+
document.addEventListener('visibilitychange', () => {
|
|
13746
|
+
if (document.visibilityState === 'visible') {
|
|
13747
|
+
checkSiteAlarms();
|
|
13748
|
+
paintAlarmBadge();
|
|
13749
|
+
}
|
|
13750
|
+
});
|
|
13751
|
+
|
|
13752
|
+
window.addEventListener('storage', event => {
|
|
13753
|
+
if (event.key === STORAGE.alarmSeen || /^alarm_(list|notification_)/.test(event.key || '')) {
|
|
13754
|
+
alarmRawMemo = null;
|
|
13755
|
+
checkSiteAlarms();
|
|
13756
|
+
paintAlarmBadge();
|
|
13757
|
+
}
|
|
13758
|
+
});
|
|
13759
|
+
|
|
13760
|
+
window.addEventListener('resize', placeBubbleBox, { passive: true });
|
|
13761
|
+
};
|
|
13762
|
+
|
|
13763
|
+
// =========================================================
|
|
13764
|
+
|
|
13765
|
+
// =========================================================
|
|
13766
|
+
|
|
13767
|
+
const renderRecentView = area => {
|
|
13768
|
+
const alarms = siteUserId() || alarmStorageKeys().length ? readSiteAlarms() : null;
|
|
13769
|
+
|
|
13770
|
+
if (alarms) {
|
|
13771
|
+
const { seen } = readAlarmMarks();
|
|
13772
|
+
const unread = alarms.filter(item => item.at > seen).length;
|
|
13773
|
+
|
|
13774
|
+
area.appendChild(makeSectionTitle(`🔔 알림${unread ? ` · 새 알림 ${unread}개` : ''}`));
|
|
13775
|
+
|
|
13776
|
+
if (!alarms.length) {
|
|
13777
|
+
area.appendChild(makeEmpty('받은 알림이 없습니다. 댓글·답글 등 디시 알림이 오면 여기와 플로팅 버튼 곁 말풍선으로 보입니다.'));
|
|
13778
|
+
} else {
|
|
13779
|
+
alarms.slice(0, 15).forEach(item => {
|
|
13780
|
+
const row = makeRow({
|
|
13781
|
+
title: item.text,
|
|
13782
|
+
chip: item.tag,
|
|
13783
|
+
chipClass: item.at > seen ? 'on' : '',
|
|
13784
|
+
meta: timeAgo(item.at),
|
|
13785
|
+
actions: item.href ? [{ label: '열기', href: item.href }] : []
|
|
13786
|
+
});
|
|
13787
|
+
|
|
13788
|
+
if (item.at > seen) {
|
|
13789
|
+
row.classList.add('dc-row-new');
|
|
13790
|
+
}
|
|
13791
|
+
|
|
13792
|
+
if (item.href) {
|
|
13793
|
+
row.querySelector('a.dc-btn')?.addEventListener('click', () => openAlarm({ ...item, href: '' }));
|
|
13794
|
+
}
|
|
13795
|
+
|
|
13796
|
+
area.appendChild(row);
|
|
13797
|
+
});
|
|
13798
|
+
|
|
13799
|
+
if (unread) {
|
|
13800
|
+
area.appendChild(makeButton('✓ 모두 읽음', 'wide', () => {
|
|
13801
|
+
markAlarmsSeen();
|
|
13802
|
+
rerenderModal();
|
|
13803
|
+
}));
|
|
13804
|
+
}
|
|
13805
|
+
}
|
|
13806
|
+
} else if (IS_MOBILE_SITE || /^gall\.dcinside\.com$/i.test(location.hostname)) {
|
|
13807
|
+
area.appendChild(makeSectionTitle('🔔 알림'));
|
|
13808
|
+
area.appendChild(makeNote('디시에 로그인하면 댓글·답글 알림을 여기와 플로팅 말풍선으로 볼 수 있습니다.'));
|
|
13809
|
+
}
|
|
13810
|
+
|
|
13811
|
+
area.appendChild(makeSectionTitle(`🕘 최근 방문 갤러리 · ${recentGalls.length}개`));
|
|
13812
|
+
|
|
13813
|
+
const syncOn = typeof syncReady === 'function' && syncReady() && !readSyncConf().skip.includes('recent');
|
|
13814
|
+
|
|
13815
|
+
area.appendChild(makeNote(syncOn
|
|
13816
|
+
? '동기화로 모든 기기의 방문 기록이 합쳐집니다. 디시 화면의 최근 방문 목록에도 함께 보입니다.'
|
|
13817
|
+
: '디시의 최근 방문 목록과 같은 내용입니다. 동기화를 켜면 다른 기기의 방문도 합쳐집니다.'));
|
|
13818
|
+
|
|
13819
|
+
if (!recentGalls.length) {
|
|
13820
|
+
area.appendChild(makeEmpty('아직 방문한 갤러리가 없습니다.'));
|
|
13821
|
+
return;
|
|
13822
|
+
}
|
|
13823
|
+
|
|
13824
|
+
const filterRow = makeFilterRow(recentGalls.length);
|
|
13825
|
+
|
|
13826
|
+
if (filterRow) {
|
|
13827
|
+
area.appendChild(filterRow);
|
|
13828
|
+
}
|
|
13829
|
+
|
|
13830
|
+
recentGalls.forEach(item => {
|
|
13831
|
+
area.appendChild(makeRow({
|
|
13832
|
+
title: item.n || item.id,
|
|
13833
|
+
chip: RECENT_KIND_LABEL[item.k] || '',
|
|
13834
|
+
meta: [item.id, item.at ? timeAgo(item.at) : ''].filter(Boolean).join(' · '),
|
|
13835
|
+
actions: [
|
|
13836
|
+
{ label: '열기', href: recentUrl(item) },
|
|
13837
|
+
{
|
|
13838
|
+
label: '지우기',
|
|
13839
|
+
onClick: () => {
|
|
13840
|
+
recentGalls = recentGalls.filter(other => other !== item);
|
|
13841
|
+
saveRecent();
|
|
13842
|
+
exportNativeRecent();
|
|
13843
|
+
rerenderModal();
|
|
13844
|
+
}
|
|
13845
|
+
}
|
|
13846
|
+
]
|
|
13847
|
+
}));
|
|
13848
|
+
});
|
|
13849
|
+
|
|
13850
|
+
area.appendChild(makeConfirmButton('최근 방문 모두 지우기', '최근 방문 갤러리 목록을 모두 지울까요? (동기화 중이면 다른 기기에서도 지워집니다)', () => {
|
|
13851
|
+
recentGalls = [];
|
|
13852
|
+
saveRecent();
|
|
13853
|
+
exportNativeRecent();
|
|
13854
|
+
rerenderModal();
|
|
13855
|
+
}));
|
|
13856
|
+
};
|
|
13857
|
+
|
|
13858
|
+
(() => {
|
|
13859
|
+
const settingsView = MODAL_VIEWS.settings;
|
|
13860
|
+
|
|
13861
|
+
delete MODAL_VIEWS.settings;
|
|
13862
|
+
MODAL_VIEWS.recent = {
|
|
13863
|
+
title: '알림 · 최근 갤러리',
|
|
13864
|
+
tab: '🕘 최근',
|
|
13865
|
+
render: area => {
|
|
13866
|
+
document.querySelectorAll('#dc-bubbles .dc-bubble').forEach(bubble => dismissBubble(bubble));
|
|
13867
|
+
renderRecentView(area);
|
|
13868
|
+
|
|
13869
|
+
setTimeout(markAlarmsSeen, 600);
|
|
13870
|
+
}
|
|
13871
|
+
};
|
|
13872
|
+
MODAL_VIEWS.settings = settingsView;
|
|
13873
|
+
})();
|
|
13874
|
+
|
|
13875
|
+
initRecentEarly();
|
|
12578
13876
|
// =========================================================
|
|
12579
13877
|
// 14. 플로팅 버튼
|
|
12580
13878
|
// =========================================================
|
|
@@ -12892,7 +14190,7 @@
|
|
|
12892
14190
|
return;
|
|
12893
14191
|
}
|
|
12894
14192
|
|
|
12895
|
-
openBlockModal();
|
|
14193
|
+
openBlockModal(fab.dataset.dcAlarm ? 'recent' : 'overview');
|
|
12896
14194
|
});
|
|
12897
14195
|
|
|
12898
14196
|
wake();
|
|
@@ -13533,6 +14831,7 @@
|
|
|
13533
14831
|
|
|
13534
14832
|
state.strikes = Date.now() - state.lastStrike < 60 * 60 * 1000 ? state.strikes + 1 : 1;
|
|
13535
14833
|
state.lastStrike = Date.now();
|
|
14834
|
+
state.src = 'page';
|
|
13536
14835
|
state.until = Date.now() + Math.min(NET_COOLDOWN_MAX, NET_COOLDOWN * 2 ** (state.strikes - 1));
|
|
13537
14836
|
writeNetState(state);
|
|
13538
14837
|
}
|
|
@@ -13559,6 +14858,8 @@
|
|
|
13559
14858
|
initScrap();
|
|
13560
14859
|
initScrollIndicator();
|
|
13561
14860
|
initSync();
|
|
14861
|
+
initRecent();
|
|
14862
|
+
initSiteAlarms();
|
|
13562
14863
|
restoreListSnapshot();
|
|
13563
14864
|
processElements();
|
|
13564
14865
|
|