@kangsicj/dcinsideenhancer 1.1.1 → 1.2.1
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 +5 -2
- package/README.md +5 -2
- package/dist/DCInsideEnhancer.meta.js +1 -1
- package/dist/DCInsideEnhancer.user.js +1198 -70
- 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.
|
|
5
|
+
// @version 1.2.1
|
|
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.
|
|
78
|
+
const SCRIPT_VERSION = '1.2.1';
|
|
79
79
|
const PROJECT_URL = 'https://github.com/kangsicj/DCInsideEnhancer';
|
|
80
80
|
const DONATE_URL = 'https://fairy.hada.io/@jinny#support';
|
|
81
81
|
|
|
@@ -108,6 +108,7 @@
|
|
|
108
108
|
recent: 'dc_recent_galls',
|
|
109
109
|
recentNative: 'dc_recent_native',
|
|
110
110
|
searchHistory: 'dc_search_history',
|
|
111
|
+
blockHits: 'dc_block_hits',
|
|
111
112
|
alarmSeen: 'dc_alarm_marks',
|
|
112
113
|
seenVersion: 'dc_seen_version'
|
|
113
114
|
};
|
|
@@ -224,6 +225,8 @@
|
|
|
224
225
|
alarmBubble: true,
|
|
225
226
|
alarmKeepPolling: true,
|
|
226
227
|
keyboardShortcuts: true,
|
|
228
|
+
hoverPreview: true,
|
|
229
|
+
alarmSystemNotify: false,
|
|
227
230
|
pcFitWidth: true,
|
|
228
231
|
autoHideScrollbar: true,
|
|
229
232
|
scrapIntercept: true,
|
|
@@ -409,67 +412,76 @@
|
|
|
409
412
|
html.dc-home-media-off .main-wrapping section.grid.media-group { display: none !important; }
|
|
410
413
|
html.dc-footer-info-off footer.footer > .ft-btm { display: none !important; }
|
|
411
414
|
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
width: auto !important;
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
html.dc-pc-fit .dcwrap
|
|
415
|
+
@media (max-width: 1519.98px) {
|
|
416
|
+
html.dc-pc-fit { --dc-fit-gap: clamp(10px, 1.6vw, 24px); }
|
|
417
|
+
html.dc-pc-fit body,
|
|
418
|
+
html.dc-pc-fit .dcwrap { min-width: 0 !important; }
|
|
419
|
+
html.dc-pc-fit body { overflow-x: hidden; }
|
|
420
|
+
html.dc-pc-fit .dcwrap .dchead,
|
|
421
|
+
html.dc-pc-fit .dcwrap .gnb,
|
|
422
|
+
html.dc-pc-fit .dcwrap .gnb_depth2_list,
|
|
423
|
+
html.dc-pc-fit .dcwrap .info_policy,
|
|
424
|
+
html.dc-pc-fit .dcwrap .copyright,
|
|
425
|
+
html.dc-pc-fit .dcwrap .visit_bookmark {
|
|
426
|
+
width: auto !important;
|
|
427
|
+
max-width: none !important;
|
|
428
|
+
margin-left: var(--dc-fit-gap) !important;
|
|
429
|
+
margin-right: var(--dc-fit-gap) !important;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
html.dc-pc-fit .dcwrap .gnb_bar { min-width: 0 !important; width: auto !important; margin: 0 !important; }
|
|
433
|
+
html.dc-pc-fit .dcwrap .gnb_list,
|
|
434
|
+
html.dc-pc-fit .dcwrap .dc_all,
|
|
435
|
+
html.dc-pc-fit .dcwrap .wrap_inner { width: auto !important; }
|
|
436
|
+
html.dc-pc-fit .dcwrap #container {
|
|
437
|
+
width: auto !important;
|
|
438
|
+
margin-left: var(--dc-fit-gap) !important;
|
|
439
|
+
margin-right: var(--dc-fit-gap) !important;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
html.dc-pc-fit .dcwrap :is(#container, .main_content, #bottom_listwrap) > .right_content { display: none !important; }
|
|
443
|
+
html.dc-pc-fit .dcwrap :is(#container, .main_content, #bottom_listwrap) > .left_content {
|
|
444
|
+
float: none !important;
|
|
445
|
+
width: auto !important;
|
|
446
|
+
max-width: none !important;
|
|
447
|
+
min-width: 0;
|
|
448
|
+
}
|
|
449
|
+
html.dc-pc-fit .dcwrap .main_content { width: auto !important; }
|
|
450
|
+
|
|
451
|
+
html.dc-pc-fit .dcwrap :is(.view_content_wrap, .gallview_head, .writing_view_box, .view_comment, .comment_wrap,
|
|
452
|
+
.comment_box, .cmt_write_box, #bottom_listwrap, .listwrap, .gall_listwrap, .page_head, .issuebox,
|
|
453
|
+
.newvisit_history, .trendalign, .bottom_paging_wrap, .bottom_paging_box, .bottom_search, .btn_recommend_box) {
|
|
454
|
+
width: auto !important;
|
|
455
|
+
max-width: 100% !important;
|
|
456
|
+
box-sizing: border-box;
|
|
457
|
+
}
|
|
458
|
+
html.dc-pc-fit .dcwrap .write_div { max-width: 100% !important; }
|
|
459
|
+
html.dc-pc-fit .dcwrap .write_div img,
|
|
460
|
+
html.dc-pc-fit .dcwrap .write_div video,
|
|
461
|
+
html.dc-pc-fit .dcwrap .write_div iframe { max-width: 100% !important; }
|
|
462
|
+
html.dc-pc-fit .dcwrap .list_array_option { width: 100% !important; }
|
|
463
|
+
html.dc-pc-fit .dcwrap .stickyunit { display: none !important; }
|
|
464
|
+
html.dc-pc-fit .dcwrap .darkmodebox .pop_tipbox { left: auto !important; right: 0 !important; }
|
|
465
|
+
|
|
466
|
+
html.dc-pc-fit .dcwrap .cmt_info { display: flex !important; align-items: flex-start; }
|
|
467
|
+
html.dc-pc-fit .dcwrap .cmt_info::before,
|
|
468
|
+
html.dc-pc-fit .dcwrap .cmt_info::after { display: none !important; }
|
|
469
|
+
html.dc-pc-fit .dcwrap .cmt_info > .addbox { display: flex; flex: 1 1 auto; min-width: 0; width: auto !important; }
|
|
470
|
+
html.dc-pc-fit .dcwrap .cmt_info > .addbox > .cmt_nickbox { flex: 0 0 auto; float: none !important; }
|
|
471
|
+
html.dc-pc-fit .dcwrap .cmt_info > .addbox > .cmt_txtbox { flex: 1 1 auto; min-width: 0; float: none !important; width: auto !important; }
|
|
472
|
+
html.dc-pc-fit .dcwrap .cmt_info .cmt_txtbox .usertxt { float: none !important; width: auto !important; max-width: 100% !important; }
|
|
473
|
+
html.dc-pc-fit .dcwrap .cmt_info > .fr { flex: 0 0 auto; float: none !important; margin-left: 10px; }
|
|
474
|
+
|
|
475
|
+
html.dc-pc-fit .dcwrap .cmt_write_box > .fl { width: 142px; }
|
|
476
|
+
html.dc-pc-fit .dcwrap .cmt_write_box > .cmt_txt_cont {
|
|
477
|
+
float: right !important;
|
|
478
|
+
width: calc(100% - 158px) !important;
|
|
479
|
+
}
|
|
480
|
+
html.dc-pc-fit .dcwrap .cmt_write_box > .cmt_txt_cont .cmt_write,
|
|
481
|
+
html.dc-pc-fit .dcwrap .cmt_write_box > .cmt_txt_cont textarea,
|
|
482
|
+
html.dc-pc-fit .dcwrap .cmt_write_box > .cmt_txt_cont .cmt_cont_bottm { width: 100% !important; box-sizing: border-box; }
|
|
483
|
+
|
|
484
|
+
html.dc-pc-fit .dcwrap .cmt_write_box:not(:has(> .fl)) > .cmt_txt_cont { width: 100% !important; }
|
|
473
485
|
}
|
|
474
486
|
|
|
475
487
|
html.dc-footer-info-off .dcwrap footer.dcfoot,
|
|
@@ -1851,6 +1863,158 @@
|
|
|
1851
1863
|
|
|
1852
1864
|
#dc-custom-modal .dc-row.dc-row-new .dc-row-title { font-weight: 800; }
|
|
1853
1865
|
|
|
1866
|
+
#dc-preview {
|
|
1867
|
+
position: fixed;
|
|
1868
|
+
top: 50%;
|
|
1869
|
+
left: 50%;
|
|
1870
|
+
z-index: 99997;
|
|
1871
|
+
display: flex;
|
|
1872
|
+
flex-direction: column;
|
|
1873
|
+
box-sizing: border-box;
|
|
1874
|
+
width: clamp(520px, 52vw, 980px);
|
|
1875
|
+
height: clamp(420px, 84vh, 1100px);
|
|
1876
|
+
max-width: calc(100vw - 32px);
|
|
1877
|
+
max-height: calc(100vh - 32px);
|
|
1878
|
+
border: 1px solid var(--dc-border);
|
|
1879
|
+
border-radius: 16px;
|
|
1880
|
+
overflow: hidden;
|
|
1881
|
+
background: var(--dc-surface);
|
|
1882
|
+
color: var(--dc-text);
|
|
1883
|
+
box-shadow: 0 18px 60px rgba(0,0,0,.28), var(--dc-shadow);
|
|
1884
|
+
font-size: 14px;
|
|
1885
|
+
line-height: 1.6;
|
|
1886
|
+
opacity: 0;
|
|
1887
|
+
visibility: hidden;
|
|
1888
|
+
pointer-events: none;
|
|
1889
|
+
transform: translate(-50%, -50%) scale(.97);
|
|
1890
|
+
transition: opacity .16s ease, transform .16s ease, visibility 0s linear .16s;
|
|
1891
|
+
}
|
|
1892
|
+
|
|
1893
|
+
#dc-preview.show {
|
|
1894
|
+
opacity: 1;
|
|
1895
|
+
visibility: visible;
|
|
1896
|
+
pointer-events: auto;
|
|
1897
|
+
transform: translate(-50%, -50%);
|
|
1898
|
+
transition: opacity .16s ease, transform .16s ease, visibility 0s;
|
|
1899
|
+
}
|
|
1900
|
+
|
|
1901
|
+
#dc-preview .dc-pv-head {
|
|
1902
|
+
flex: 0 0 auto;
|
|
1903
|
+
padding: 14px 18px 10px;
|
|
1904
|
+
border-bottom: 1px solid var(--dc-border);
|
|
1905
|
+
background: var(--dc-surface);
|
|
1906
|
+
}
|
|
1907
|
+
|
|
1908
|
+
#dc-preview .dc-pv-title {
|
|
1909
|
+
display: -webkit-box;
|
|
1910
|
+
overflow: hidden;
|
|
1911
|
+
-webkit-line-clamp: 2;
|
|
1912
|
+
-webkit-box-orient: vertical;
|
|
1913
|
+
font-size: 16px;
|
|
1914
|
+
font-weight: 800;
|
|
1915
|
+
line-height: 1.4;
|
|
1916
|
+
word-break: break-all;
|
|
1917
|
+
}
|
|
1918
|
+
|
|
1919
|
+
#dc-preview .dc-pv-meta {
|
|
1920
|
+
margin-top: 3px;
|
|
1921
|
+
color: var(--dc-text-dim);
|
|
1922
|
+
font-size: 12px;
|
|
1923
|
+
}
|
|
1924
|
+
|
|
1925
|
+
#dc-preview .dc-pv-actions {
|
|
1926
|
+
display: flex;
|
|
1927
|
+
flex-wrap: wrap;
|
|
1928
|
+
align-items: center;
|
|
1929
|
+
gap: 6px;
|
|
1930
|
+
margin-top: 9px;
|
|
1931
|
+
}
|
|
1932
|
+
|
|
1933
|
+
#dc-preview .dc-pv-hint { margin-left: auto; color: var(--dc-text-dim); font-size: 11.5px; }
|
|
1934
|
+
#dc-preview .dc-pv-notes:empty { display: none; }
|
|
1935
|
+
#dc-preview .dc-pv-notes { margin-top: 6px; }
|
|
1936
|
+
|
|
1937
|
+
#dc-preview .dc-btn {
|
|
1938
|
+
display: inline-flex;
|
|
1939
|
+
align-items: center;
|
|
1940
|
+
justify-content: center;
|
|
1941
|
+
box-sizing: border-box;
|
|
1942
|
+
height: 30px;
|
|
1943
|
+
padding: 0 12px;
|
|
1944
|
+
border: 1px solid var(--dc-border);
|
|
1945
|
+
border-radius: 999px;
|
|
1946
|
+
background: var(--dc-surface-2);
|
|
1947
|
+
color: var(--dc-text);
|
|
1948
|
+
font-size: 12.5px;
|
|
1949
|
+
font-weight: 700;
|
|
1950
|
+
line-height: 1;
|
|
1951
|
+
text-decoration: none;
|
|
1952
|
+
cursor: pointer;
|
|
1953
|
+
}
|
|
1954
|
+
|
|
1955
|
+
#dc-preview .dc-btn:hover { border-color: var(--dc-accent-ink); color: var(--dc-accent-ink); }
|
|
1956
|
+
#dc-preview .dc-btn.primary { border-color: var(--dc-accent); background: var(--dc-accent); color: var(--dc-on-accent); }
|
|
1957
|
+
#dc-preview .dc-btn.primary:hover { color: var(--dc-on-accent); filter: brightness(1.08); }
|
|
1958
|
+
#dc-preview .dc-btn.danger-ghost { color: var(--dc-danger-ink); }
|
|
1959
|
+
#dc-preview .dc-btn.dc-pv-close { width: 30px; padding: 0; font-size: 14px; }
|
|
1960
|
+
|
|
1961
|
+
#dc-preview .dc-pv-body {
|
|
1962
|
+
position: relative;
|
|
1963
|
+
display: flex;
|
|
1964
|
+
flex-direction: column;
|
|
1965
|
+
flex: 1 1 auto;
|
|
1966
|
+
min-height: 0;
|
|
1967
|
+
background: var(--dc-surface);
|
|
1968
|
+
}
|
|
1969
|
+
|
|
1970
|
+
#dc-preview .dc-pv-frame {
|
|
1971
|
+
flex: 1 1 auto;
|
|
1972
|
+
width: 100%;
|
|
1973
|
+
min-height: 0;
|
|
1974
|
+
border: 0;
|
|
1975
|
+
background: var(--dc-surface);
|
|
1976
|
+
}
|
|
1977
|
+
|
|
1978
|
+
#dc-preview .dc-pv-loading,
|
|
1979
|
+
#dc-preview .dc-pv-note {
|
|
1980
|
+
padding: 12px 18px;
|
|
1981
|
+
color: var(--dc-text-dim);
|
|
1982
|
+
font-size: 13px;
|
|
1983
|
+
}
|
|
1984
|
+
|
|
1985
|
+
#dc-preview .dc-pv-notes .dc-pv-note { padding: 0; font-size: 12px; }
|
|
1986
|
+
|
|
1987
|
+
#dc-preview .dc-pv-loading::before {
|
|
1988
|
+
content: '';
|
|
1989
|
+
display: inline-block;
|
|
1990
|
+
width: 12px;
|
|
1991
|
+
height: 12px;
|
|
1992
|
+
margin-right: 8px;
|
|
1993
|
+
border: 2px solid var(--dc-border);
|
|
1994
|
+
border-top-color: var(--dc-accent-ink);
|
|
1995
|
+
border-radius: 50%;
|
|
1996
|
+
vertical-align: -1px;
|
|
1997
|
+
animation: dc-spin .7s linear infinite;
|
|
1998
|
+
}
|
|
1999
|
+
|
|
2000
|
+
@keyframes dc-spin { to { transform: rotate(360deg); } }
|
|
2001
|
+
|
|
2002
|
+
#dc-preview .dc-pv-warn {
|
|
2003
|
+
display: flex;
|
|
2004
|
+
flex-wrap: wrap;
|
|
2005
|
+
align-items: center;
|
|
2006
|
+
gap: 8px;
|
|
2007
|
+
margin: 14px 18px;
|
|
2008
|
+
padding: 10px 12px;
|
|
2009
|
+
border-radius: 10px;
|
|
2010
|
+
background: var(--dc-danger-soft);
|
|
2011
|
+
color: var(--dc-danger-ink);
|
|
2012
|
+
font-size: 13px;
|
|
2013
|
+
font-weight: 700;
|
|
2014
|
+
}
|
|
2015
|
+
|
|
2016
|
+
.dc-pv-anchor { text-decoration: underline !important; text-decoration-color: var(--dc-accent-ink) !important; }
|
|
2017
|
+
|
|
1854
2018
|
.dc-key-cursor { outline: 2px solid var(--dc-accent-ink) !important; outline-offset: -2px; background: var(--dc-accent-soft) !important; }
|
|
1855
2019
|
|
|
1856
2020
|
#dc-custom-modal .dc-about {
|
|
@@ -6462,7 +6626,97 @@
|
|
|
6462
6626
|
|
|
6463
6627
|
const currentPostKey = () => extractPostPath(location.href) || location.pathname;
|
|
6464
6628
|
|
|
6465
|
-
const
|
|
6629
|
+
const HIT_DAY_MS = 86400000;
|
|
6630
|
+
const hitToday = () => Math.floor(Date.now() / HIT_DAY_MS);
|
|
6631
|
+
|
|
6632
|
+
const readBlockHits = () => {
|
|
6633
|
+
const stored = readObject(STORAGE.blockHits);
|
|
6634
|
+
|
|
6635
|
+
return {
|
|
6636
|
+
since: Number(stored.since) || hitToday(),
|
|
6637
|
+
k: stored.k && typeof stored.k === 'object' && !Array.isArray(stored.k) ? { ...stored.k } : {}
|
|
6638
|
+
};
|
|
6639
|
+
};
|
|
6640
|
+
|
|
6641
|
+
let blockHits = readBlockHits();
|
|
6642
|
+
let blockHitsDirty = !readObject(STORAGE.blockHits).since;
|
|
6643
|
+
let blockHitsTimer = null;
|
|
6644
|
+
|
|
6645
|
+
const blockHitKeys = () => [
|
|
6646
|
+
...blockedUsers,
|
|
6647
|
+
...blockedTitles.map(word => `t:${word}`),
|
|
6648
|
+
...blockedKeywords.map(word => `w:${word}`)
|
|
6649
|
+
];
|
|
6650
|
+
|
|
6651
|
+
const flushBlockHits = () => {
|
|
6652
|
+
clearTimeout(blockHitsTimer);
|
|
6653
|
+
blockHitsTimer = null;
|
|
6654
|
+
|
|
6655
|
+
if (!blockHitsDirty) {
|
|
6656
|
+
return;
|
|
6657
|
+
}
|
|
6658
|
+
|
|
6659
|
+
blockHitsDirty = false;
|
|
6660
|
+
|
|
6661
|
+
const today = hitToday();
|
|
6662
|
+
const next = {};
|
|
6663
|
+
|
|
6664
|
+
blockHitKeys().forEach(key => {
|
|
6665
|
+
next[key] = Number(blockHits.k[key]) || today;
|
|
6666
|
+
});
|
|
6667
|
+
|
|
6668
|
+
blockHits = { since: blockHits.since, k: next };
|
|
6669
|
+
writeJson(STORAGE.blockHits, blockHits);
|
|
6670
|
+
};
|
|
6671
|
+
|
|
6672
|
+
const scheduleBlockHitsFlush = () => {
|
|
6673
|
+
blockHitsDirty = true;
|
|
6674
|
+
|
|
6675
|
+
if (!blockHitsTimer) {
|
|
6676
|
+
blockHitsTimer = setTimeout(flushBlockHits, 20000);
|
|
6677
|
+
}
|
|
6678
|
+
};
|
|
6679
|
+
|
|
6680
|
+
const noteBlockHit = reason => {
|
|
6681
|
+
if (!reason) {
|
|
6682
|
+
return reason;
|
|
6683
|
+
}
|
|
6684
|
+
|
|
6685
|
+
const keys = reason.type === 'user'
|
|
6686
|
+
? reason.userKeys || []
|
|
6687
|
+
: reason.type === 'title'
|
|
6688
|
+
? [`t:${reason.word}`]
|
|
6689
|
+
: reason.type === 'keyword'
|
|
6690
|
+
? [blockedTitles.includes(reason.word) && !blockedKeywords.includes(reason.word) ? `t:${reason.word}` : `w:${reason.word}`]
|
|
6691
|
+
: [];
|
|
6692
|
+
const today = hitToday();
|
|
6693
|
+
|
|
6694
|
+
keys.forEach(key => {
|
|
6695
|
+
if (blockHits.k[key] !== today) {
|
|
6696
|
+
blockHits.k[key] = today;
|
|
6697
|
+
scheduleBlockHitsFlush();
|
|
6698
|
+
}
|
|
6699
|
+
});
|
|
6700
|
+
|
|
6701
|
+
return reason;
|
|
6702
|
+
};
|
|
6703
|
+
|
|
6704
|
+
window.addEventListener('pagehide', flushBlockHits);
|
|
6705
|
+
document.addEventListener('visibilitychange', () => {
|
|
6706
|
+
if (document.visibilityState === 'hidden') {
|
|
6707
|
+
flushBlockHits();
|
|
6708
|
+
}
|
|
6709
|
+
});
|
|
6710
|
+
|
|
6711
|
+
storageWriteListeners.push(key => {
|
|
6712
|
+
if (key === STORAGE.users || key === STORAGE.keywords || key === STORAGE.titles) {
|
|
6713
|
+
scheduleBlockHitsFlush();
|
|
6714
|
+
}
|
|
6715
|
+
});
|
|
6716
|
+
|
|
6717
|
+
const computeBlockReason = el => noteBlockHit(computeBlockReasonRaw(el));
|
|
6718
|
+
|
|
6719
|
+
const computeBlockReasonRaw = el => {
|
|
6466
6720
|
const hits = buildBlockKeys({
|
|
6467
6721
|
nickname: el.dataset.dcNick || '',
|
|
6468
6722
|
ip: el.dataset.dcIp || '',
|
|
@@ -9987,6 +10241,7 @@
|
|
|
9987
10241
|
});
|
|
9988
10242
|
|
|
9989
10243
|
area.appendChild(seg);
|
|
10244
|
+
renderCleanupHelper(area);
|
|
9990
10245
|
(renderBlockSections[blockSection] || renderBlockSections.keywords)(area);
|
|
9991
10246
|
};
|
|
9992
10247
|
|
|
@@ -10173,6 +10428,15 @@
|
|
|
10173
10428
|
off: '사이트 기본 한 줄 알림을 그대로 씁니다.',
|
|
10174
10429
|
refresh: 'root'
|
|
10175
10430
|
},
|
|
10431
|
+
{
|
|
10432
|
+
group: 'alarm',
|
|
10433
|
+
key: 'alarmSystemNotify',
|
|
10434
|
+
parent: 'alarmBubble',
|
|
10435
|
+
name: '다른 탭을 볼 때 기기 알림으로도 (PC)',
|
|
10436
|
+
on: '이 탭을 보고 있지 않을 때 디시 알림이 오면 윈도우·맥 알림으로 띄웁니다. 누르면 이 탭으로 돌아와 해당 글로 갑니다. 처음 켤 때 브라우저가 권한을 묻습니다.',
|
|
10437
|
+
off: '이 탭을 볼 때 말풍선으로만 알립니다.',
|
|
10438
|
+
refresh: 'root'
|
|
10439
|
+
},
|
|
10176
10440
|
{
|
|
10177
10441
|
group: 'alarm',
|
|
10178
10442
|
key: 'alarmKeepPolling',
|
|
@@ -10206,6 +10470,14 @@
|
|
|
10206
10470
|
off: '단축키를 쓰지 않습니다.',
|
|
10207
10471
|
refresh: 'none'
|
|
10208
10472
|
},
|
|
10473
|
+
{
|
|
10474
|
+
group: 'list',
|
|
10475
|
+
key: 'hoverPreview',
|
|
10476
|
+
name: '제목에 마우스를 올리면 글 미리보기 (PC)',
|
|
10477
|
+
on: '목록 제목 위에 잠깐 머무르면 글 전체를 옆 창에 보여 줍니다. 창 안에서 스크롤해 읽고, 스크랩·작성자 차단도 바로 할 수 있습니다. 차단한 이미지·단어는 미리보기에서도 가립니다. 키보드로 고른 글은 P 로 엽니다.',
|
|
10478
|
+
off: '제목을 눌러야 글을 봅니다.',
|
|
10479
|
+
refresh: 'root'
|
|
10480
|
+
},
|
|
10209
10481
|
{
|
|
10210
10482
|
group: 'list',
|
|
10211
10483
|
key: 'autoFillList',
|
|
@@ -10403,6 +10675,20 @@
|
|
|
10403
10675
|
paintAlarmBadge();
|
|
10404
10676
|
}
|
|
10405
10677
|
|
|
10678
|
+
if (item.key === 'hoverPreview' && settings.hoverPreview === false) {
|
|
10679
|
+
closePreview();
|
|
10680
|
+
}
|
|
10681
|
+
|
|
10682
|
+
if (item.key === 'alarmSystemNotify' && settings.alarmSystemNotify === true) {
|
|
10683
|
+
requestSystemNotify().then(granted => {
|
|
10684
|
+
if (!granted) {
|
|
10685
|
+
settings.alarmSystemNotify = false;
|
|
10686
|
+
saveSettings();
|
|
10687
|
+
rerenderModal();
|
|
10688
|
+
}
|
|
10689
|
+
});
|
|
10690
|
+
}
|
|
10691
|
+
|
|
10406
10692
|
break;
|
|
10407
10693
|
|
|
10408
10694
|
case 'log':
|
|
@@ -11626,7 +11912,8 @@
|
|
|
11626
11912
|
scraps: scraps.map(item => ({ ...item })),
|
|
11627
11913
|
settings: syncedSettings(),
|
|
11628
11914
|
recent: collectRecentForSync(),
|
|
11629
|
-
search: collectSearchForSync()
|
|
11915
|
+
search: collectSearchForSync(),
|
|
11916
|
+
hits: collectHitsForSync()
|
|
11630
11917
|
});
|
|
11631
11918
|
|
|
11632
11919
|
const skippedFields = skip => SYNC_PARTS.filter(part => skip.includes(part.key)).flatMap(part => part.fields);
|
|
@@ -11734,7 +12021,8 @@
|
|
|
11734
12021
|
scraps: mergeScraps(local.scraps, base && base.scraps, remote && remote.scraps),
|
|
11735
12022
|
settings: mergeSettings(local.settings, base && base.settings, remote && remote.settings),
|
|
11736
12023
|
recent: mergeRecent(local.recent, base && base.recent, remote && remote.recent),
|
|
11737
|
-
search: mergeSearchHistory(local.search, base && base.search, remote && remote.search)
|
|
12024
|
+
search: mergeSearchHistory(local.search, base && base.search, remote && remote.search),
|
|
12025
|
+
hits: mergeBlockHits(local.hits, remote && remote.hits)
|
|
11738
12026
|
};
|
|
11739
12027
|
|
|
11740
12028
|
skippedFields(skip).forEach(field => {
|
|
@@ -11789,6 +12077,7 @@
|
|
|
11789
12077
|
|
|
11790
12078
|
delete data.recent;
|
|
11791
12079
|
delete data.search;
|
|
12080
|
+
delete data.hits;
|
|
11792
12081
|
|
|
11793
12082
|
return JSON.stringify(data);
|
|
11794
12083
|
};
|
|
@@ -11796,6 +12085,7 @@
|
|
|
11796
12085
|
const applySyncData = data => {
|
|
11797
12086
|
const recentChanged = data && 'recent' in data ? applyRecentFromSync(data.recent) : false;
|
|
11798
12087
|
const searchChanged = data && 'search' in data ? applySearchFromSync(data.search) : false;
|
|
12088
|
+
const hitsChanged = data && 'hits' in data ? applyHitsFromSync(data.hits) : false;
|
|
11799
12089
|
const clean = sanitizeBackup({ app: BACKUP_APP, ...data });
|
|
11800
12090
|
const before = blockSyncData();
|
|
11801
12091
|
|
|
@@ -11850,7 +12140,7 @@
|
|
|
11850
12140
|
refreshAfterBlockChange();
|
|
11851
12141
|
}
|
|
11852
12142
|
|
|
11853
|
-
return changed || recentChanged || searchChanged;
|
|
12143
|
+
return changed || recentChanged || searchChanged || hitsChanged;
|
|
11854
12144
|
};
|
|
11855
12145
|
|
|
11856
12146
|
const saveSyncUndo = (before, incoming, summary, from) => {
|
|
@@ -13751,7 +14041,9 @@
|
|
|
13751
14041
|
let alarmWatchTimer = null;
|
|
13752
14042
|
|
|
13753
14043
|
const checkSiteAlarms = () => {
|
|
13754
|
-
|
|
14044
|
+
const away = document.visibilityState === 'hidden' || (typeof document.hasFocus === 'function' && !document.hasFocus());
|
|
14045
|
+
|
|
14046
|
+
if (settings.alarmBubble === false || (document.visibilityState === 'hidden' && !canSystemNotify())) {
|
|
13755
14047
|
return;
|
|
13756
14048
|
}
|
|
13757
14049
|
|
|
@@ -13779,7 +14071,11 @@
|
|
|
13779
14071
|
writeAlarmMarks({ shown: latest });
|
|
13780
14072
|
}
|
|
13781
14073
|
|
|
13782
|
-
if (fresh.length) {
|
|
14074
|
+
if (fresh.length && away && canSystemNotify()) {
|
|
14075
|
+
showSystemAlarm(fresh[0], fresh.length - 1);
|
|
14076
|
+
}
|
|
14077
|
+
|
|
14078
|
+
if (fresh.length && document.visibilityState !== 'hidden') {
|
|
13783
14079
|
showAlarmBubble(fresh[0], fresh.length > ALARM_BUBBLE_MAX ? fresh.length - 1 : 0);
|
|
13784
14080
|
|
|
13785
14081
|
if (fresh.length > 1 && fresh.length <= ALARM_BUBBLE_MAX) {
|
|
@@ -13990,7 +14286,7 @@
|
|
|
13990
14286
|
|
|
13991
14287
|
// =========================================================
|
|
13992
14288
|
|
|
13993
|
-
const KEY_HELP = 'J/K 다음·이전 글 · Enter 열기 · S 스크랩 · / 검색 · B 차단 관리';
|
|
14289
|
+
const KEY_HELP = 'J/K 다음·이전 글 · Enter 열기 · P 미리보기 · S 스크랩 · / 검색 · B 차단 관리';
|
|
13994
14290
|
|
|
13995
14291
|
const typingTarget = el =>
|
|
13996
14292
|
!!el && (el.isContentEditable || /^(INPUT|TEXTAREA|SELECT)$/.test(el.tagName) || !!el.closest?.('[contenteditable="true"]'));
|
|
@@ -14008,6 +14304,10 @@
|
|
|
14008
14304
|
let keyCursor = null;
|
|
14009
14305
|
|
|
14010
14306
|
const moveKeyCursor = step => {
|
|
14307
|
+
if (previewOpen()) {
|
|
14308
|
+
closePreview();
|
|
14309
|
+
}
|
|
14310
|
+
|
|
14011
14311
|
const rows = visibleListRows();
|
|
14012
14312
|
|
|
14013
14313
|
if (!rows.length) {
|
|
@@ -14091,6 +14391,8 @@
|
|
|
14091
14391
|
} else if (key === 'b' || key === 'B') {
|
|
14092
14392
|
openBlockModal(document.getElementById('dc-custom-fab')?.dataset.dcAlarm ? 'recent' : 'overview');
|
|
14093
14393
|
handled = true;
|
|
14394
|
+
} else if ((key === 'p' || key === 'P') && togglePreviewForCursor()) {
|
|
14395
|
+
handled = true;
|
|
14094
14396
|
} else if (key === '?') {
|
|
14095
14397
|
showToast(`⌨️ ${KEY_HELP}`, { duration: 6000 });
|
|
14096
14398
|
handled = true;
|
|
@@ -14159,6 +14461,831 @@
|
|
|
14159
14461
|
|
|
14160
14462
|
return true;
|
|
14161
14463
|
};
|
|
14464
|
+
|
|
14465
|
+
// =========================================================
|
|
14466
|
+
// 13-4. 차단 목록 정리 도우미 · 기기 알림 · PC 글 미리보기
|
|
14467
|
+
// =========================================================
|
|
14468
|
+
|
|
14469
|
+
const collectHitsForSync = () => {
|
|
14470
|
+
flushBlockHits();
|
|
14471
|
+
|
|
14472
|
+
return { since: blockHits.since, k: { ...blockHits.k } };
|
|
14473
|
+
};
|
|
14474
|
+
|
|
14475
|
+
const mergeBlockHits = (local, remote) => {
|
|
14476
|
+
if (!remote || typeof remote !== 'object' || !remote.k) {
|
|
14477
|
+
return local;
|
|
14478
|
+
}
|
|
14479
|
+
|
|
14480
|
+
const k = { ...(remote.k || {}) };
|
|
14481
|
+
|
|
14482
|
+
Object.entries((local && local.k) || {}).forEach(([key, day]) => {
|
|
14483
|
+
k[key] = Math.max(Number(k[key]) || 0, Number(day) || 0);
|
|
14484
|
+
});
|
|
14485
|
+
|
|
14486
|
+
return { since: Math.min(Number(local && local.since) || hitToday(), Number(remote.since) || hitToday()), k };
|
|
14487
|
+
};
|
|
14488
|
+
|
|
14489
|
+
const applyHitsFromSync = data => {
|
|
14490
|
+
if (!data || typeof data !== 'object' || !data.k) {
|
|
14491
|
+
return false;
|
|
14492
|
+
}
|
|
14493
|
+
|
|
14494
|
+
const merged = mergeBlockHits(blockHits, data);
|
|
14495
|
+
|
|
14496
|
+
if (JSON.stringify(merged) === JSON.stringify(blockHits)) {
|
|
14497
|
+
return false;
|
|
14498
|
+
}
|
|
14499
|
+
|
|
14500
|
+
blockHits = merged;
|
|
14501
|
+
blockHitsDirty = true;
|
|
14502
|
+
flushBlockHits();
|
|
14503
|
+
|
|
14504
|
+
return true;
|
|
14505
|
+
};
|
|
14506
|
+
|
|
14507
|
+
// =========================================================
|
|
14508
|
+
|
|
14509
|
+
// =========================================================
|
|
14510
|
+
|
|
14511
|
+
const CLEANUP_IDLE_DAYS = 60;
|
|
14512
|
+
|
|
14513
|
+
const cleanupSuggestions = () => {
|
|
14514
|
+
flushBlockHits();
|
|
14515
|
+
|
|
14516
|
+
const today = hitToday();
|
|
14517
|
+
const tracked = today - blockHits.since;
|
|
14518
|
+
const idle = [];
|
|
14519
|
+
|
|
14520
|
+
if (tracked >= CLEANUP_IDLE_DAYS) {
|
|
14521
|
+
blockHitKeys().forEach(key => {
|
|
14522
|
+
const day = Number(blockHits.k[key]) || today;
|
|
14523
|
+
|
|
14524
|
+
if (today - day >= CLEANUP_IDLE_DAYS) {
|
|
14525
|
+
idle.push({ key, days: today - day });
|
|
14526
|
+
}
|
|
14527
|
+
});
|
|
14528
|
+
}
|
|
14529
|
+
|
|
14530
|
+
const redundant = [];
|
|
14531
|
+
|
|
14532
|
+
blockedKeywords.forEach(word => {
|
|
14533
|
+
const cover = blockedKeywords.find(other => other !== word && other.length < word.length && word.includes(other));
|
|
14534
|
+
|
|
14535
|
+
if (cover) {
|
|
14536
|
+
redundant.push({ key: `w:${word}`, cover });
|
|
14537
|
+
}
|
|
14538
|
+
});
|
|
14539
|
+
|
|
14540
|
+
blockedTitles.forEach(word => {
|
|
14541
|
+
const cover = [...blockedKeywords, ...blockedTitles].find(other => other !== word && other.length < word.length && word.includes(other));
|
|
14542
|
+
|
|
14543
|
+
if (cover) {
|
|
14544
|
+
redundant.push({ key: `t:${word}`, cover });
|
|
14545
|
+
}
|
|
14546
|
+
});
|
|
14547
|
+
|
|
14548
|
+
return { idle: idle.sort((a, b) => b.days - a.days), redundant, tracked };
|
|
14549
|
+
};
|
|
14550
|
+
|
|
14551
|
+
const describeHitKey = key => {
|
|
14552
|
+
if (key.startsWith('w:')) {
|
|
14553
|
+
return { label: key.slice(2), kind: '단어' };
|
|
14554
|
+
}
|
|
14555
|
+
|
|
14556
|
+
if (key.startsWith('t:')) {
|
|
14557
|
+
return { label: key.slice(2), kind: '제목 단어' };
|
|
14558
|
+
}
|
|
14559
|
+
|
|
14560
|
+
const parsed = parseBlockKey(key);
|
|
14561
|
+
|
|
14562
|
+
return { label: parsed.value, kind: BLOCK_TYPE_LABELS[parsed.type] || '유저' };
|
|
14563
|
+
};
|
|
14564
|
+
|
|
14565
|
+
const removeBlockKeys = keys => {
|
|
14566
|
+
const before = { users: [...blockedUsers], keywords: [...blockedKeywords], titles: [...blockedTitles] };
|
|
14567
|
+
const drop = new Set(keys);
|
|
14568
|
+
|
|
14569
|
+
blockedUsers = blockedUsers.filter(key => !drop.has(key));
|
|
14570
|
+
blockedKeywords = blockedKeywords.filter(word => !drop.has(`w:${word}`));
|
|
14571
|
+
blockedTitles = blockedTitles.filter(word => !drop.has(`t:${word}`));
|
|
14572
|
+
saveUsers();
|
|
14573
|
+
saveKeywords();
|
|
14574
|
+
saveTitles();
|
|
14575
|
+
refreshAfterBlockChange();
|
|
14576
|
+
|
|
14577
|
+
showToast(`🧹 ${keys.length}개 정리함`, {
|
|
14578
|
+
actionLabel: '되돌리기',
|
|
14579
|
+
duration: 6000,
|
|
14580
|
+
onAction: () => {
|
|
14581
|
+
blockedUsers = before.users;
|
|
14582
|
+
blockedKeywords = before.keywords;
|
|
14583
|
+
blockedTitles = before.titles;
|
|
14584
|
+
saveUsers();
|
|
14585
|
+
saveKeywords();
|
|
14586
|
+
saveTitles();
|
|
14587
|
+
refreshAfterBlockChange();
|
|
14588
|
+
rerenderModal();
|
|
14589
|
+
}
|
|
14590
|
+
});
|
|
14591
|
+
};
|
|
14592
|
+
|
|
14593
|
+
const renderCleanupHelper = area => {
|
|
14594
|
+
const { idle, redundant, tracked } = cleanupSuggestions();
|
|
14595
|
+
const count = idle.length + redundant.length;
|
|
14596
|
+
const waitDays = Math.max(0, CLEANUP_IDLE_DAYS - tracked);
|
|
14597
|
+
|
|
14598
|
+
if (!count && !waitDays) {
|
|
14599
|
+
return;
|
|
14600
|
+
}
|
|
14601
|
+
|
|
14602
|
+
area.appendChild(makeFoldSection({
|
|
14603
|
+
id: 'cleanup',
|
|
14604
|
+
title: `🧹 정리 도우미${count ? '' : ' · 준비 중'}`,
|
|
14605
|
+
count,
|
|
14606
|
+
defaultOpen: false,
|
|
14607
|
+
render: body => {
|
|
14608
|
+
if (waitDays) {
|
|
14609
|
+
body.appendChild(makeNote(`어떤 차단이 실제로 쓰이는지 기록을 모으는 중입니다. ${waitDays}일 뒤부터 오래 안 걸린 항목을 추천합니다.`));
|
|
14610
|
+
}
|
|
14611
|
+
|
|
14612
|
+
const group = (title, items, meta) => {
|
|
14613
|
+
if (!items.length) {
|
|
14614
|
+
return;
|
|
14615
|
+
}
|
|
14616
|
+
|
|
14617
|
+
body.appendChild(makeSectionTitle(`${title} · ${items.length}개`));
|
|
14618
|
+
renderPaged(body, items, item => {
|
|
14619
|
+
const info = describeHitKey(item.key);
|
|
14620
|
+
|
|
14621
|
+
return makeRow({
|
|
14622
|
+
title: info.label,
|
|
14623
|
+
chip: info.kind,
|
|
14624
|
+
chipClass: 'scope',
|
|
14625
|
+
meta: meta(item),
|
|
14626
|
+
actions: [{ label: '해제', className: 'on', onClick: () => {
|
|
14627
|
+
removeBlockKeys([item.key]);
|
|
14628
|
+
rerenderModal();
|
|
14629
|
+
} }]
|
|
14630
|
+
});
|
|
14631
|
+
});
|
|
14632
|
+
body.appendChild(makeButton(`${title} 모두 해제`, 'wide', () => {
|
|
14633
|
+
if (confirm(`${title} ${items.length}개를 차단 목록에서 뺄까요? (바로 되돌릴 수 있습니다)`)) {
|
|
14634
|
+
removeBlockKeys(items.map(item => item.key));
|
|
14635
|
+
rerenderModal();
|
|
14636
|
+
}
|
|
14637
|
+
}));
|
|
14638
|
+
};
|
|
14639
|
+
|
|
14640
|
+
group('필요 없는 단어', redundant, item => `"${item.cover}" 가 이미 걸러 줌`);
|
|
14641
|
+
group(`${CLEANUP_IDLE_DAYS}일 넘게 안 걸린 항목`, idle, item => `${item.days}일 동안 걸린 글·댓글 없음 (모든 기기)`);
|
|
14642
|
+
}
|
|
14643
|
+
}));
|
|
14644
|
+
};
|
|
14645
|
+
|
|
14646
|
+
// =========================================================
|
|
14647
|
+
|
|
14648
|
+
// =========================================================
|
|
14649
|
+
|
|
14650
|
+
const systemNotifySupported = () => typeof window.Notification === 'function';
|
|
14651
|
+
|
|
14652
|
+
const canSystemNotify = () =>
|
|
14653
|
+
settings.alarmSystemNotify === true && systemNotifySupported() && window.Notification.permission === 'granted';
|
|
14654
|
+
|
|
14655
|
+
const showSystemAlarm = (item, extra = 0) => {
|
|
14656
|
+
try {
|
|
14657
|
+
const note = new window.Notification(`디시 알림 · ${item.tag}`, {
|
|
14658
|
+
body: extra ? `${item.text}\n외 ${extra}개` : item.text,
|
|
14659
|
+
tag: `dcx-${item.key}`,
|
|
14660
|
+
silent: false
|
|
14661
|
+
});
|
|
14662
|
+
|
|
14663
|
+
note.onclick = () => {
|
|
14664
|
+
try {
|
|
14665
|
+
window.focus();
|
|
14666
|
+
} catch {
|
|
14667
|
+
}
|
|
14668
|
+
|
|
14669
|
+
note.close();
|
|
14670
|
+
|
|
14671
|
+
if (extra) {
|
|
14672
|
+
openBlockModal('recent');
|
|
14673
|
+
} else {
|
|
14674
|
+
openAlarm(item);
|
|
14675
|
+
}
|
|
14676
|
+
};
|
|
14677
|
+
} catch {
|
|
14678
|
+
}
|
|
14679
|
+
};
|
|
14680
|
+
|
|
14681
|
+
const requestSystemNotify = async () => {
|
|
14682
|
+
if (!systemNotifySupported()) {
|
|
14683
|
+
showToast('이 브라우저는 기기 알림을 지원하지 않습니다');
|
|
14684
|
+
return false;
|
|
14685
|
+
}
|
|
14686
|
+
|
|
14687
|
+
let permission = window.Notification.permission;
|
|
14688
|
+
|
|
14689
|
+
if (permission === 'default') {
|
|
14690
|
+
try {
|
|
14691
|
+
permission = await window.Notification.requestPermission();
|
|
14692
|
+
} catch {
|
|
14693
|
+
permission = 'denied';
|
|
14694
|
+
}
|
|
14695
|
+
}
|
|
14696
|
+
|
|
14697
|
+
if (permission !== 'granted') {
|
|
14698
|
+
showToast('🔕 브라우저에서 이 사이트의 알림을 허용해야 기기 알림을 띄울 수 있습니다', { duration: 6000 });
|
|
14699
|
+
return false;
|
|
14700
|
+
}
|
|
14701
|
+
|
|
14702
|
+
showToast('🔔 다른 탭을 보고 있어도 디시 알림을 기기 알림으로 띄웁니다');
|
|
14703
|
+
return true;
|
|
14704
|
+
};
|
|
14705
|
+
|
|
14706
|
+
// =========================================================
|
|
14707
|
+
|
|
14708
|
+
// =========================================================
|
|
14709
|
+
|
|
14710
|
+
const PREVIEW_DELAY = 450;
|
|
14711
|
+
const PREVIEW_CLOSE_DELAY = 380;
|
|
14712
|
+
const PREVIEW_CACHE_MS = 5 * 60 * 1000;
|
|
14713
|
+
const PREVIEW_CACHE_MAX = 30;
|
|
14714
|
+
const PREVIEW_TITLE_SELECTOR = 'table.gall_list td.gall_tit > a:not(.reply_numbox)';
|
|
14715
|
+
const PREVIEW_MOVIE_RE = /^https:\/\/gall\.dcinside\.com\/board\/movie\//i;
|
|
14716
|
+
|
|
14717
|
+
const previewCache = new Map();
|
|
14718
|
+
let previewEl = null;
|
|
14719
|
+
let previewKey = '';
|
|
14720
|
+
let previewHref = '';
|
|
14721
|
+
let previewAnchor = null;
|
|
14722
|
+
let previewHoverTimer = null;
|
|
14723
|
+
let previewCloseTimer = null;
|
|
14724
|
+
let previewThemeObserver = null;
|
|
14725
|
+
|
|
14726
|
+
const previewEnabled = () => IS_PC_SITE && settings.hoverPreview !== false;
|
|
14727
|
+
const previewOpen = () => !!previewEl && previewEl.classList.contains('show');
|
|
14728
|
+
|
|
14729
|
+
const ensurePreviewEl = () => {
|
|
14730
|
+
if (previewEl && previewEl.isConnected) {
|
|
14731
|
+
return previewEl;
|
|
14732
|
+
}
|
|
14733
|
+
|
|
14734
|
+
previewEl = h('div', 'dc-ui');
|
|
14735
|
+
previewEl.id = 'dc-preview';
|
|
14736
|
+
previewEl.setAttribute('role', 'dialog');
|
|
14737
|
+
previewEl.setAttribute('aria-label', '글 미리보기');
|
|
14738
|
+
previewEl.innerHTML =
|
|
14739
|
+
'<div class="dc-pv-head">' +
|
|
14740
|
+
'<div class="dc-pv-title"></div>' +
|
|
14741
|
+
'<div class="dc-pv-meta"></div>' +
|
|
14742
|
+
'<div class="dc-pv-actions"></div>' +
|
|
14743
|
+
'<div class="dc-pv-notes"></div>' +
|
|
14744
|
+
'</div>' +
|
|
14745
|
+
'<div class="dc-pv-body"></div>';
|
|
14746
|
+
previewEl.addEventListener('mouseenter', () => clearTimeout(previewCloseTimer));
|
|
14747
|
+
previewEl.addEventListener('mouseleave', () => schedulePreviewClose());
|
|
14748
|
+
document.body.appendChild(previewEl);
|
|
14749
|
+
|
|
14750
|
+
return previewEl;
|
|
14751
|
+
};
|
|
14752
|
+
|
|
14753
|
+
const stopPreviewTheme = () => {
|
|
14754
|
+
previewThemeObserver?.disconnect();
|
|
14755
|
+
previewThemeObserver = null;
|
|
14756
|
+
};
|
|
14757
|
+
|
|
14758
|
+
const closePreview = () => {
|
|
14759
|
+
clearTimeout(previewHoverTimer);
|
|
14760
|
+
clearTimeout(previewCloseTimer);
|
|
14761
|
+
stopPreviewTheme();
|
|
14762
|
+
|
|
14763
|
+
if (previewEl) {
|
|
14764
|
+
previewEl.classList.remove('show');
|
|
14765
|
+
|
|
14766
|
+
previewEl.querySelector('.dc-pv-body').textContent = '';
|
|
14767
|
+
previewEl.querySelector('.dc-pv-notes').textContent = '';
|
|
14768
|
+
}
|
|
14769
|
+
|
|
14770
|
+
previewAnchor?.classList.remove('dc-pv-anchor');
|
|
14771
|
+
previewAnchor = null;
|
|
14772
|
+
previewKey = '';
|
|
14773
|
+
};
|
|
14774
|
+
|
|
14775
|
+
const schedulePreviewClose = () => {
|
|
14776
|
+
clearTimeout(previewCloseTimer);
|
|
14777
|
+
previewCloseTimer = setTimeout(closePreview, PREVIEW_CLOSE_DELAY);
|
|
14778
|
+
};
|
|
14779
|
+
|
|
14780
|
+
const goToPreviewPost = () => {
|
|
14781
|
+
const href = previewHref;
|
|
14782
|
+
|
|
14783
|
+
closePreview();
|
|
14784
|
+
|
|
14785
|
+
if (href) {
|
|
14786
|
+
location.href = href;
|
|
14787
|
+
}
|
|
14788
|
+
};
|
|
14789
|
+
|
|
14790
|
+
const cleanPreviewBody = source => {
|
|
14791
|
+
const body = source.cloneNode(true);
|
|
14792
|
+
let hiddenMedia = 0;
|
|
14793
|
+
|
|
14794
|
+
body.removeAttribute('style');
|
|
14795
|
+
|
|
14796
|
+
body.querySelectorAll('script, style, link, object, embed, form, noscript, .dc-ui, #zzbang_div').forEach(node => node.remove());
|
|
14797
|
+
|
|
14798
|
+
body.querySelectorAll('iframe').forEach(node => {
|
|
14799
|
+
let src = node.getAttribute('src') || '';
|
|
14800
|
+
|
|
14801
|
+
try {
|
|
14802
|
+
src = new URL(src, 'https://gall.dcinside.com/').href;
|
|
14803
|
+
} catch {
|
|
14804
|
+
src = '';
|
|
14805
|
+
}
|
|
14806
|
+
|
|
14807
|
+
if (PREVIEW_MOVIE_RE.test(src)) {
|
|
14808
|
+
node.setAttribute('src', src);
|
|
14809
|
+
node.removeAttribute('style');
|
|
14810
|
+
node.removeAttribute('width');
|
|
14811
|
+
node.removeAttribute('height');
|
|
14812
|
+
node.setAttribute('allowfullscreen', '');
|
|
14813
|
+
node.setAttribute('allow', 'autoplay; fullscreen; picture-in-picture');
|
|
14814
|
+
node.className = 'dc-pv-movie';
|
|
14815
|
+
return;
|
|
14816
|
+
}
|
|
14817
|
+
|
|
14818
|
+
const link = body.ownerDocument.createElement('a');
|
|
14819
|
+
|
|
14820
|
+
link.href = src;
|
|
14821
|
+
link.textContent = '🎬 삽입된 동영상 열기';
|
|
14822
|
+
link.className = 'dc-pv-embed';
|
|
14823
|
+
node.replaceWith(link);
|
|
14824
|
+
});
|
|
14825
|
+
|
|
14826
|
+
body.querySelectorAll('*').forEach(node => {
|
|
14827
|
+
[...node.attributes].forEach(attr => {
|
|
14828
|
+
if (/^on/i.test(attr.name) || (/^(href|src)$/i.test(attr.name) && /^\s*javascript:/i.test(attr.value))) {
|
|
14829
|
+
node.removeAttribute(attr.name);
|
|
14830
|
+
}
|
|
14831
|
+
});
|
|
14832
|
+
});
|
|
14833
|
+
|
|
14834
|
+
body.querySelectorAll('img, video').forEach(media => {
|
|
14835
|
+
const lazy = media.getAttribute('data-original') || media.getAttribute('data-src') || media.getAttribute('data-lazy-src');
|
|
14836
|
+
|
|
14837
|
+
if (media.tagName === 'IMG' && lazy && /^https?:\/\//.test(lazy)) {
|
|
14838
|
+
media.setAttribute('src', lazy);
|
|
14839
|
+
}
|
|
14840
|
+
|
|
14841
|
+
const candidates = collectMediaCandidates(media);
|
|
14842
|
+
|
|
14843
|
+
if (isDcconElement(media, candidates)) {
|
|
14844
|
+
if (settings.blockDccon !== false) {
|
|
14845
|
+
media.remove();
|
|
14846
|
+
}
|
|
14847
|
+
|
|
14848
|
+
return;
|
|
14849
|
+
}
|
|
14850
|
+
|
|
14851
|
+
if (findBlockedMediaKey(resolveMediaIds(media, candidates))) {
|
|
14852
|
+
const stub = body.ownerDocument.createElement('div');
|
|
14853
|
+
|
|
14854
|
+
stub.className = 'dc-pv-blocked';
|
|
14855
|
+
stub.textContent = '🚫 차단한 이미지';
|
|
14856
|
+
(media.closest('a') || media).replaceWith(stub);
|
|
14857
|
+
hiddenMedia += 1;
|
|
14858
|
+
return;
|
|
14859
|
+
}
|
|
14860
|
+
|
|
14861
|
+
media.removeAttribute('loading');
|
|
14862
|
+
media.removeAttribute('width');
|
|
14863
|
+
media.removeAttribute('height');
|
|
14864
|
+
|
|
14865
|
+
if (media.tagName === 'VIDEO') {
|
|
14866
|
+
media.setAttribute('playsinline', '');
|
|
14867
|
+
|
|
14868
|
+
if (media.hasAttribute('loop') && media.hasAttribute('muted')) {
|
|
14869
|
+
media.setAttribute('autoplay', '');
|
|
14870
|
+
media.muted = true;
|
|
14871
|
+
} else {
|
|
14872
|
+
media.removeAttribute('autoplay');
|
|
14873
|
+
media.setAttribute('controls', '');
|
|
14874
|
+
media.setAttribute('preload', 'metadata');
|
|
14875
|
+
}
|
|
14876
|
+
}
|
|
14877
|
+
});
|
|
14878
|
+
|
|
14879
|
+
return { body, hiddenMedia };
|
|
14880
|
+
};
|
|
14881
|
+
|
|
14882
|
+
const previewFrameCss = () => {
|
|
14883
|
+
const root = getComputedStyle(document.documentElement);
|
|
14884
|
+
const token = name => root.getPropertyValue(name).trim();
|
|
14885
|
+
const font = getComputedStyle(document.body).fontFamily || 'sans-serif';
|
|
14886
|
+
const dark = document.documentElement.classList.contains('dc-dark');
|
|
14887
|
+
|
|
14888
|
+
return `
|
|
14889
|
+
html { color-scheme: ${dark ? 'dark' : 'light'}; }
|
|
14890
|
+
html, body { margin: 0; padding: 0; background: ${token('--dc-surface')}; color: ${token('--dc-text')}; }
|
|
14891
|
+
body { padding: 14px 18px 28px; font: 14px/1.7 ${font}; word-break: break-word; overflow-wrap: anywhere; cursor: pointer; }
|
|
14892
|
+
::-webkit-scrollbar { width: 8px; } ::-webkit-scrollbar-thumb { background: ${token('--dc-border')}; border-radius: 4px; }
|
|
14893
|
+
* { max-width: 100% !important; box-sizing: border-box; white-space: normal !important; }
|
|
14894
|
+
pre, pre * { white-space: pre-wrap !important; }
|
|
14895
|
+
[style*="width"] { width: auto !important; }
|
|
14896
|
+
p { margin: 0 0 4px; }
|
|
14897
|
+
img, video { display: block; height: auto !important; margin: 6px 0; border-radius: 6px; }
|
|
14898
|
+
a { color: ${token('--dc-accent-ink')}; cursor: pointer; }
|
|
14899
|
+
video, iframe, a, button { cursor: auto; }
|
|
14900
|
+
a { cursor: pointer; }
|
|
14901
|
+
.dc-pv-movie { display: block; width: 100% !important; aspect-ratio: 16 / 9; height: auto; border: 0; border-radius: 8px; background: #000; }
|
|
14902
|
+
.dc-pv-embed { display: inline-block; margin: 6px 0; font-weight: 700; }
|
|
14903
|
+
.dc-pv-blocked { margin: 6px 0; padding: 14px; border: 1px dashed ${token('--dc-border')}; border-radius: 8px; color: ${token('--dc-text-dim')}; font-size: 12px; text-align: center; }
|
|
14904
|
+
${dark ? `[style*="background"] { background: transparent !important; }
|
|
14905
|
+
[style*="color: rgb(0, 0, 0)"], [style*="color:#000"], [style*="color: #000"], [style*="color:black"], font[color="#000000"] { color: inherit !important; }` : ''}
|
|
14906
|
+
`;
|
|
14907
|
+
};
|
|
14908
|
+
|
|
14909
|
+
const mountPreviewFrame = (container, data, href) => {
|
|
14910
|
+
const { body, hiddenMedia } = cleanPreviewBody(data.source);
|
|
14911
|
+
const frame = document.createElement('iframe');
|
|
14912
|
+
|
|
14913
|
+
frame.className = 'dc-pv-frame';
|
|
14914
|
+
frame.setAttribute('title', '글 본문');
|
|
14915
|
+
container.appendChild(frame);
|
|
14916
|
+
|
|
14917
|
+
const win = frame.contentWindow;
|
|
14918
|
+
const doc = frame.contentDocument;
|
|
14919
|
+
|
|
14920
|
+
if (!win || !doc) {
|
|
14921
|
+
return hiddenMedia;
|
|
14922
|
+
}
|
|
14923
|
+
|
|
14924
|
+
doc.open();
|
|
14925
|
+
doc.write('<!doctype html><html><head><meta charset="utf-8"><base target="_blank"><style id="dc-pv-css"></style></head><body></body></html>');
|
|
14926
|
+
doc.close();
|
|
14927
|
+
|
|
14928
|
+
try {
|
|
14929
|
+
const target = new URL(href, location.href);
|
|
14930
|
+
|
|
14931
|
+
win.history.replaceState(null, '', target.pathname + target.search);
|
|
14932
|
+
} catch {
|
|
14933
|
+
}
|
|
14934
|
+
|
|
14935
|
+
doc.getElementById('dc-pv-css').textContent = previewFrameCss();
|
|
14936
|
+
doc.body.appendChild(doc.importNode(body, true));
|
|
14937
|
+
|
|
14938
|
+
doc.querySelectorAll('video[data-src]').forEach(video => {
|
|
14939
|
+
const gif = video.getAttribute('data-src');
|
|
14940
|
+
let swapped = false;
|
|
14941
|
+
const toGif = () => {
|
|
14942
|
+
if (swapped || !video.isConnected) {
|
|
14943
|
+
return;
|
|
14944
|
+
}
|
|
14945
|
+
|
|
14946
|
+
swapped = true;
|
|
14947
|
+
|
|
14948
|
+
const img = doc.createElement('img');
|
|
14949
|
+
|
|
14950
|
+
img.src = gif;
|
|
14951
|
+
img.alt = '움짤';
|
|
14952
|
+
video.replaceWith(img);
|
|
14953
|
+
};
|
|
14954
|
+
|
|
14955
|
+
if (!video.canPlayType('video/mp4')) {
|
|
14956
|
+
toGif();
|
|
14957
|
+
return;
|
|
14958
|
+
}
|
|
14959
|
+
|
|
14960
|
+
video.addEventListener('error', toGif, true);
|
|
14961
|
+
video.querySelectorAll('source').forEach(source => source.addEventListener('error', toGif));
|
|
14962
|
+
});
|
|
14963
|
+
|
|
14964
|
+
doc.addEventListener('click', event => {
|
|
14965
|
+
const target = event.target;
|
|
14966
|
+
|
|
14967
|
+
if (target && target.closest && target.closest('a[href], video, iframe, button, input, textarea, select, summary')) {
|
|
14968
|
+
return;
|
|
14969
|
+
}
|
|
14970
|
+
|
|
14971
|
+
event.preventDefault();
|
|
14972
|
+
goToPreviewPost();
|
|
14973
|
+
});
|
|
14974
|
+
|
|
14975
|
+
doc.addEventListener('keydown', event => {
|
|
14976
|
+
if (event.key === 'Escape') {
|
|
14977
|
+
closePreview();
|
|
14978
|
+
}
|
|
14979
|
+
});
|
|
14980
|
+
|
|
14981
|
+
doc.documentElement.style.overscrollBehavior = 'contain';
|
|
14982
|
+
|
|
14983
|
+
stopPreviewTheme();
|
|
14984
|
+
previewThemeObserver = new MutationObserver(() => {
|
|
14985
|
+
const css = doc.getElementById('dc-pv-css');
|
|
14986
|
+
|
|
14987
|
+
if (css) {
|
|
14988
|
+
css.textContent = previewFrameCss();
|
|
14989
|
+
}
|
|
14990
|
+
});
|
|
14991
|
+
previewThemeObserver.observe(document.documentElement, { attributes: true, attributeFilter: ['class'] });
|
|
14992
|
+
|
|
14993
|
+
return hiddenMedia;
|
|
14994
|
+
};
|
|
14995
|
+
|
|
14996
|
+
const readPreviewDoc = (html, key) => {
|
|
14997
|
+
const doc = new DOMParser().parseFromString(html, 'text/html');
|
|
14998
|
+
const source = doc.querySelector('.write_div') || doc.querySelector(POST_BODY_SELECTOR);
|
|
14999
|
+
const head = doc.querySelector('.gallview_head');
|
|
15000
|
+
|
|
15001
|
+
if (!source) {
|
|
15002
|
+
return { missing: true };
|
|
15003
|
+
}
|
|
15004
|
+
|
|
15005
|
+
const writer = head?.querySelector('.gall_writer');
|
|
15006
|
+
const text = normalizeWord(`${head?.querySelector('.title_subject')?.textContent || ''} ${source.textContent}`).slice(0, BODY_TEXT_LIMIT);
|
|
15007
|
+
const word = findWord(blockedKeywords, text);
|
|
15008
|
+
|
|
15009
|
+
if (key) {
|
|
15010
|
+
bodyScanCache[key] = { s: bodyScanSignature(), w: word, m: '', t: Date.now() };
|
|
15011
|
+
saveBodyScanSoon();
|
|
15012
|
+
}
|
|
15013
|
+
|
|
15014
|
+
return {
|
|
15015
|
+
title: cleanText(head?.querySelector('.title_subject')?.textContent || ''),
|
|
15016
|
+
head: cleanText(head?.querySelector('.title_headtext')?.textContent || '').replace(/^\[|\]$/g, ''),
|
|
15017
|
+
nick: writer?.getAttribute('data-nick') || cleanText(writer?.querySelector('.nickname')?.textContent || ''),
|
|
15018
|
+
ip: writer?.getAttribute('data-ip') || '',
|
|
15019
|
+
code: writer?.getAttribute('data-uid') || '',
|
|
15020
|
+
date: head?.querySelector('.gall_date')?.getAttribute('title') || cleanText(head?.querySelector('.gall_date')?.textContent || ''),
|
|
15021
|
+
stats: (cleanText(head?.querySelector('.fr')?.textContent || '').match(/(조회|추천|댓글)\s*[\d,]+/g) || []).join(' · '),
|
|
15022
|
+
source,
|
|
15023
|
+
word
|
|
15024
|
+
};
|
|
15025
|
+
};
|
|
15026
|
+
|
|
15027
|
+
const renderPreview = (data, href, key) => {
|
|
15028
|
+
const el = ensurePreviewEl();
|
|
15029
|
+
const titleEl = el.querySelector('.dc-pv-title');
|
|
15030
|
+
const metaEl = el.querySelector('.dc-pv-meta');
|
|
15031
|
+
const actions = el.querySelector('.dc-pv-actions');
|
|
15032
|
+
const notes = el.querySelector('.dc-pv-notes');
|
|
15033
|
+
const bodyEl = el.querySelector('.dc-pv-body');
|
|
15034
|
+
|
|
15035
|
+
previewHref = href;
|
|
15036
|
+
actions.textContent = '';
|
|
15037
|
+
notes.textContent = '';
|
|
15038
|
+
bodyEl.textContent = '';
|
|
15039
|
+
stopPreviewTheme();
|
|
15040
|
+
|
|
15041
|
+
if (data.missing) {
|
|
15042
|
+
titleEl.textContent = '본문을 불러올 수 없습니다';
|
|
15043
|
+
metaEl.textContent = '삭제됐거나 성인 인증이 필요한 글입니다.';
|
|
15044
|
+
} else {
|
|
15045
|
+
titleEl.textContent = `${data.head ? `[${data.head}] ` : ''}${data.title || '(제목 없음)'}`;
|
|
15046
|
+
metaEl.textContent = [data.nick && `${data.nick}${data.ip ? `(${data.ip})` : ''}`, data.date, data.stats].filter(Boolean).join(' · ');
|
|
15047
|
+
|
|
15048
|
+
const showBody = () => {
|
|
15049
|
+
const hidden = mountPreviewFrame(bodyEl, data, href);
|
|
15050
|
+
|
|
15051
|
+
if (hidden) {
|
|
15052
|
+
notes.appendChild(h('div', 'dc-pv-note', `차단한 이미지 ${hidden}개를 뺐습니다`));
|
|
15053
|
+
}
|
|
15054
|
+
};
|
|
15055
|
+
|
|
15056
|
+
if (data.word) {
|
|
15057
|
+
const warn = h('div', 'dc-pv-warn', `🚫 본문에 차단 단어 "${data.word}" 가 있어 가렸습니다${settings.prefetchBodies !== false ? ' · 목록에서도 가려집니다' : ''}`);
|
|
15058
|
+
|
|
15059
|
+
warn.appendChild(makeButton('그래도 보기', 'mini', () => {
|
|
15060
|
+
warn.remove();
|
|
15061
|
+
showBody();
|
|
15062
|
+
}));
|
|
15063
|
+
bodyEl.appendChild(warn);
|
|
15064
|
+
} else {
|
|
15065
|
+
showBody();
|
|
15066
|
+
}
|
|
15067
|
+
}
|
|
15068
|
+
|
|
15069
|
+
const open = h('a', 'dc-btn mini primary', '글 열기');
|
|
15070
|
+
|
|
15071
|
+
open.href = href;
|
|
15072
|
+
open.addEventListener('click', event => {
|
|
15073
|
+
if (!event.ctrlKey && !event.metaKey && !event.shiftKey && event.button === 0) {
|
|
15074
|
+
event.preventDefault();
|
|
15075
|
+
goToPreviewPost();
|
|
15076
|
+
}
|
|
15077
|
+
});
|
|
15078
|
+
actions.appendChild(open);
|
|
15079
|
+
|
|
15080
|
+
if (!data.missing && key) {
|
|
15081
|
+
const match = /[?&]id=([^&]+)&no=(\d+)/.exec(href) || [];
|
|
15082
|
+
const kind = /\/mgallery\//.test(href) ? 'M' : /\/mini\//.test(href) ? 'N' : /\/person\//.test(href) ? 'P' : 'G';
|
|
15083
|
+
const info = match[1] ? normalizeScrap({ k: kind, id: decodeURIComponent(match[1]), no: match[2], t: data.title, g: '', d: Date.now() }) : null;
|
|
15084
|
+
|
|
15085
|
+
if (info && isValidScrap(info)) {
|
|
15086
|
+
const scrapBtn = makeButton(isScrapped(info) ? '📌 스크랩됨' : '📌 스크랩', 'mini', () => {
|
|
15087
|
+
toggleScrap(info);
|
|
15088
|
+
scrapBtn.textContent = isScrapped(info) ? '📌 스크랩됨' : '📌 스크랩';
|
|
15089
|
+
});
|
|
15090
|
+
|
|
15091
|
+
actions.appendChild(scrapBtn);
|
|
15092
|
+
}
|
|
15093
|
+
|
|
15094
|
+
const userKey = data.code ? `id::${data.code}` : data.ip ? `ip::${data.ip}` : data.nick ? `nick::${data.nick}` : '';
|
|
15095
|
+
|
|
15096
|
+
if (userKey && !blockedUsers.includes(userKey)) {
|
|
15097
|
+
actions.appendChild(makeButton('🚫 작성자 차단', 'mini danger-ghost', () => {
|
|
15098
|
+
blockedUsers = sortUserKeys([...blockedUsers, userKey]);
|
|
15099
|
+
saveUsers();
|
|
15100
|
+
refreshAfterBlockChange();
|
|
15101
|
+
closePreview();
|
|
15102
|
+
offerUndo(`✓ ${describeHitKey(userKey).kind} ${describeHitKey(userKey).label} 차단`, () => {
|
|
15103
|
+
blockedUsers = blockedUsers.filter(item => item !== userKey);
|
|
15104
|
+
saveUsers();
|
|
15105
|
+
refreshAfterBlockChange();
|
|
15106
|
+
});
|
|
15107
|
+
}));
|
|
15108
|
+
}
|
|
15109
|
+
}
|
|
15110
|
+
|
|
15111
|
+
actions.appendChild(h('span', 'dc-pv-hint', '본문을 누르면 글로 이동'));
|
|
15112
|
+
|
|
15113
|
+
const close = makeButton('✕', 'mini dc-pv-close', closePreview);
|
|
15114
|
+
|
|
15115
|
+
close.setAttribute('aria-label', '미리보기 닫기');
|
|
15116
|
+
actions.appendChild(close);
|
|
15117
|
+
};
|
|
15118
|
+
|
|
15119
|
+
const showPreviewFor = async anchor => {
|
|
15120
|
+
if (!previewEnabled() || !anchor || !anchor.isConnected) {
|
|
15121
|
+
return;
|
|
15122
|
+
}
|
|
15123
|
+
|
|
15124
|
+
const row = anchor.closest('tr');
|
|
15125
|
+
|
|
15126
|
+
if (row && ((row.classList.contains('dc-user-blocked') && !row.classList.contains('dc-user-revealed')) || row.classList.contains('dc-head-hidden'))) {
|
|
15127
|
+
return;
|
|
15128
|
+
}
|
|
15129
|
+
|
|
15130
|
+
const href = anchor.href;
|
|
15131
|
+
const key = extractPostPath(href);
|
|
15132
|
+
|
|
15133
|
+
if (!key) {
|
|
15134
|
+
return;
|
|
15135
|
+
}
|
|
15136
|
+
|
|
15137
|
+
previewAnchor?.classList.remove('dc-pv-anchor');
|
|
15138
|
+
previewAnchor = anchor;
|
|
15139
|
+
previewKey = key;
|
|
15140
|
+
previewHref = href;
|
|
15141
|
+
anchor.classList.add('dc-pv-anchor');
|
|
15142
|
+
|
|
15143
|
+
const el = ensurePreviewEl();
|
|
15144
|
+
|
|
15145
|
+
el.classList.add('show');
|
|
15146
|
+
|
|
15147
|
+
const cached = previewCache.get(key);
|
|
15148
|
+
|
|
15149
|
+
if (cached && Date.now() - cached.at < PREVIEW_CACHE_MS) {
|
|
15150
|
+
renderPreview(cached.data, href, key);
|
|
15151
|
+
return;
|
|
15152
|
+
}
|
|
15153
|
+
|
|
15154
|
+
stopPreviewTheme();
|
|
15155
|
+
el.querySelector('.dc-pv-title').textContent = cleanText(anchor.textContent);
|
|
15156
|
+
el.querySelector('.dc-pv-meta').textContent = '';
|
|
15157
|
+
el.querySelector('.dc-pv-actions').textContent = '';
|
|
15158
|
+
el.querySelector('.dc-pv-notes').textContent = '';
|
|
15159
|
+
|
|
15160
|
+
const body = el.querySelector('.dc-pv-body');
|
|
15161
|
+
const paused = netPausedUntil();
|
|
15162
|
+
|
|
15163
|
+
body.textContent = '';
|
|
15164
|
+
|
|
15165
|
+
if (paused) {
|
|
15166
|
+
body.appendChild(h('div', 'dc-pv-note', `⏸ 디시 요청 제한 중이라 ${Math.max(1, Math.ceil((paused - Date.now()) / 60000))}분 뒤에 미리볼 수 있습니다`));
|
|
15167
|
+
return;
|
|
15168
|
+
}
|
|
15169
|
+
|
|
15170
|
+
body.appendChild(h('div', 'dc-pv-loading', '불러오는 중…'));
|
|
15171
|
+
|
|
15172
|
+
try {
|
|
15173
|
+
const html = await siteFetchText(href, { credentials: 'same-origin', cache: 'no-cache' });
|
|
15174
|
+
const data = readPreviewDoc(html, key);
|
|
15175
|
+
|
|
15176
|
+
previewCache.set(key, { at: Date.now(), data });
|
|
15177
|
+
|
|
15178
|
+
while (previewCache.size > PREVIEW_CACHE_MAX) {
|
|
15179
|
+
previewCache.delete(previewCache.keys().next().value);
|
|
15180
|
+
}
|
|
15181
|
+
|
|
15182
|
+
if (previewKey === key) {
|
|
15183
|
+
renderPreview(data, href, key);
|
|
15184
|
+
|
|
15185
|
+
if (data.word && settings.prefetchBodies !== false) {
|
|
15186
|
+
refreshAfterBlockChange();
|
|
15187
|
+
}
|
|
15188
|
+
}
|
|
15189
|
+
} catch (error) {
|
|
15190
|
+
if (previewKey !== key) {
|
|
15191
|
+
return;
|
|
15192
|
+
}
|
|
15193
|
+
|
|
15194
|
+
body.textContent = '';
|
|
15195
|
+
body.appendChild(h('div', 'dc-pv-note', error instanceof NetPausedError
|
|
15196
|
+
? '⏸ 디시 요청 제한 중이라 잠시 뒤에 미리볼 수 있습니다'
|
|
15197
|
+
: '불러오지 못했습니다. 잠시 뒤 다시 올려 보세요.'));
|
|
15198
|
+
}
|
|
15199
|
+
};
|
|
15200
|
+
|
|
15201
|
+
const initHoverPreview = () => {
|
|
15202
|
+
if (!IS_PC_SITE) {
|
|
15203
|
+
return;
|
|
15204
|
+
}
|
|
15205
|
+
|
|
15206
|
+
document.addEventListener('mouseover', event => {
|
|
15207
|
+
if (!previewEnabled()) {
|
|
15208
|
+
return;
|
|
15209
|
+
}
|
|
15210
|
+
|
|
15211
|
+
const anchor = event.target instanceof Element ? event.target.closest(PREVIEW_TITLE_SELECTOR) : null;
|
|
15212
|
+
|
|
15213
|
+
if (!anchor || anchor.closest('.dc-ui')) {
|
|
15214
|
+
return;
|
|
15215
|
+
}
|
|
15216
|
+
|
|
15217
|
+
if (anchor === previewAnchor) {
|
|
15218
|
+
clearTimeout(previewCloseTimer);
|
|
15219
|
+
return;
|
|
15220
|
+
}
|
|
15221
|
+
|
|
15222
|
+
clearTimeout(previewHoverTimer);
|
|
15223
|
+
previewHoverTimer = setTimeout(() => {
|
|
15224
|
+
clearTimeout(previewCloseTimer);
|
|
15225
|
+
showPreviewFor(anchor);
|
|
15226
|
+
}, PREVIEW_DELAY);
|
|
15227
|
+
});
|
|
15228
|
+
|
|
15229
|
+
document.addEventListener('mouseout', event => {
|
|
15230
|
+
const anchor = event.target instanceof Element ? event.target.closest(PREVIEW_TITLE_SELECTOR) : null;
|
|
15231
|
+
|
|
15232
|
+
if (!anchor || (event.relatedTarget instanceof Node && anchor.contains(event.relatedTarget))) {
|
|
15233
|
+
return;
|
|
15234
|
+
}
|
|
15235
|
+
|
|
15236
|
+
clearTimeout(previewHoverTimer);
|
|
15237
|
+
|
|
15238
|
+
if (previewOpen() && !(event.relatedTarget instanceof Node && previewEl.contains(event.relatedTarget))) {
|
|
15239
|
+
schedulePreviewClose();
|
|
15240
|
+
}
|
|
15241
|
+
});
|
|
15242
|
+
|
|
15243
|
+
document.addEventListener('mousedown', event => {
|
|
15244
|
+
if (previewOpen() && !(event.target instanceof Node && previewEl.contains(event.target))) {
|
|
15245
|
+
closePreview();
|
|
15246
|
+
}
|
|
15247
|
+
}, true);
|
|
15248
|
+
|
|
15249
|
+
window.addEventListener('scroll', () => {
|
|
15250
|
+
if (previewOpen() && !previewEl.matches(':hover')) {
|
|
15251
|
+
closePreview();
|
|
15252
|
+
}
|
|
15253
|
+
}, { passive: true });
|
|
15254
|
+
|
|
15255
|
+
window.addEventListener('resize', () => {
|
|
15256
|
+
if (previewOpen()) {
|
|
15257
|
+
closePreview();
|
|
15258
|
+
}
|
|
15259
|
+
});
|
|
15260
|
+
|
|
15261
|
+
document.addEventListener('keydown', event => {
|
|
15262
|
+
if (event.key === 'Escape' && previewOpen()) {
|
|
15263
|
+
closePreview();
|
|
15264
|
+
}
|
|
15265
|
+
});
|
|
15266
|
+
|
|
15267
|
+
window.addEventListener('pagehide', closePreview);
|
|
15268
|
+
};
|
|
15269
|
+
|
|
15270
|
+
const togglePreviewForCursor = () => {
|
|
15271
|
+
if (!previewEnabled()) {
|
|
15272
|
+
return false;
|
|
15273
|
+
}
|
|
15274
|
+
|
|
15275
|
+
if (previewOpen()) {
|
|
15276
|
+
closePreview();
|
|
15277
|
+
return true;
|
|
15278
|
+
}
|
|
15279
|
+
|
|
15280
|
+
const anchor = keyCursor && keyCursor.isConnected ? keyCursor.querySelector('td.gall_tit > a:not(.reply_numbox)') : null;
|
|
15281
|
+
|
|
15282
|
+
if (!anchor) {
|
|
15283
|
+
return false;
|
|
15284
|
+
}
|
|
15285
|
+
|
|
15286
|
+
showPreviewFor(anchor);
|
|
15287
|
+
return true;
|
|
15288
|
+
};
|
|
14162
15289
|
// =========================================================
|
|
14163
15290
|
// 14. 플로팅 버튼
|
|
14164
15291
|
// =========================================================
|
|
@@ -15147,6 +16274,7 @@
|
|
|
15147
16274
|
initRecent();
|
|
15148
16275
|
initSiteAlarms();
|
|
15149
16276
|
initKeyboardShortcuts();
|
|
16277
|
+
initHoverPreview();
|
|
15150
16278
|
restoreListSnapshot();
|
|
15151
16279
|
processElements();
|
|
15152
16280
|
|