@kangsicj/dcinsideenhancer 1.2.2 → 1.3.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 +2 -1
- package/README.md +2 -1
- package/dist/DCInsideEnhancer.meta.js +1 -1
- package/dist/DCInsideEnhancer.user.js +374 -40
- package/package.json +1 -1
package/README.en.md
CHANGED
|
@@ -64,7 +64,8 @@ One userscript for DCInside on mobile and PC: smart blocking, endless lists, ima
|
|
|
64
64
|
- Come back from a post and you're **right where you left off**
|
|
65
65
|
- Cleaner read-post colors and a back-to-top button
|
|
66
66
|
- Keyboard shortcuts on PC: **J/K** next/previous post, Enter to open, P to preview, S to scrap, / to search
|
|
67
|
-
- On PC,
|
|
67
|
+
- On PC, **press and hold** a title to preview the whole post, iPhone-style — blocked images and words stay hidden there too
|
|
68
|
+
- **Pin the order** of recently visited galleries — it stays put wherever you go
|
|
68
69
|
|
|
69
70
|
</td>
|
|
70
71
|
</tr>
|
package/README.md
CHANGED
|
@@ -64,7 +64,8 @@
|
|
|
64
64
|
- 글을 보고 돌아오면 **보던 위치 그대로**
|
|
65
65
|
- 읽은 글 색 정리, 맨 위로 버튼
|
|
66
66
|
- PC 키보드 단축키: **J/K** 다음·이전 글, Enter 열기, P 미리보기, S 스크랩, / 검색
|
|
67
|
-
- PC는
|
|
67
|
+
- PC는 아이폰처럼 제목을 **길게 누르면 글 전체 미리보기** — 차단 이미지·단어는 미리보기에서도 가림
|
|
68
|
+
- 최근 방문 갤러리 **순서 고정** — 어디를 가도 정한 순서 그대로
|
|
68
69
|
|
|
69
70
|
</td>
|
|
70
71
|
</tr>
|
|
@@ -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.3.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
|
|
@@ -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.3.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.
|
|
78
|
+
const SCRIPT_VERSION = '1.3.0';
|
|
79
79
|
const PROJECT_URL = 'https://github.com/kangsicj/DCInsideEnhancer';
|
|
80
80
|
const DONATE_URL = 'https://fairy.hada.io/@jinny#support';
|
|
81
81
|
|
|
@@ -106,6 +106,7 @@
|
|
|
106
106
|
syncLock: 'dc_sync_lock',
|
|
107
107
|
syncRt: 'dc_sync_rt',
|
|
108
108
|
recent: 'dc_recent_galls',
|
|
109
|
+
recentOrder: 'dc_recent_order',
|
|
109
110
|
recentNative: 'dc_recent_native',
|
|
110
111
|
searchHistory: 'dc_search_history',
|
|
111
112
|
blockHits: 'dc_block_hits',
|
|
@@ -221,11 +222,13 @@
|
|
|
221
222
|
hideAdSlots: true,
|
|
222
223
|
hideNewsRows: true,
|
|
223
224
|
hideCommentAds: true,
|
|
225
|
+
hideCommentFab: true,
|
|
224
226
|
hideSilbeAlarm: true,
|
|
225
227
|
alarmBubble: true,
|
|
226
228
|
alarmKeepPolling: true,
|
|
227
229
|
keyboardShortcuts: true,
|
|
228
230
|
hoverPreview: true,
|
|
231
|
+
previewHover: false,
|
|
229
232
|
alarmSystemNotify: false,
|
|
230
233
|
pcFitWidth: true,
|
|
231
234
|
autoHideScrollbar: true,
|
|
@@ -474,7 +477,17 @@
|
|
|
474
477
|
html.dc-pc-fit .dcwrap :is(.cmt_info, .reply_info) > .fr { flex: 0 0 auto; float: none !important; margin-left: 10px; }
|
|
475
478
|
|
|
476
479
|
html.dc-pc-fit .dcwrap .writing_view_box { float: none !important; width: auto !important; }
|
|
477
|
-
html.dc-pc-fit .dcwrap .writing_view_box .write_div {
|
|
480
|
+
html.dc-pc-fit .dcwrap .writing_view_box .write_div {
|
|
481
|
+
width: auto !important;
|
|
482
|
+
max-width: min(100%, 900px) !important;
|
|
483
|
+
margin-left: auto !important;
|
|
484
|
+
margin-right: auto !important;
|
|
485
|
+
}
|
|
486
|
+
html.dc-pc-fit .dcwrap .writing_view_box .write_div :is(img, video, iframe) { margin-left: auto; margin-right: auto; }
|
|
487
|
+
html.dc-pc-fit .dcwrap .writing_view_box .write_div img { display: block; height: auto; }
|
|
488
|
+
|
|
489
|
+
html.dc-pc-fit .dcwrap .comment_count { display: flow-root; }
|
|
490
|
+
html.dc-pc-fit .dcwrap :is(.cmt_list, .comment_box) { clear: both; }
|
|
478
491
|
|
|
479
492
|
html.dc-pc-fit .dcwrap .bottom_paging_wrap {
|
|
480
493
|
display: flex !important;
|
|
@@ -507,6 +520,7 @@
|
|
|
507
520
|
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),
|
|
508
521
|
html.dc-ad-slots-off :is(.adv-group, li.adv-inner, section.grid.clkhigh, #home-ad, .kakao_ad_area) { display: none !important; }
|
|
509
522
|
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; }
|
|
523
|
+
html.dc-comment-fab-off :is(#cmt_fab, .fab-wrap:has(> #cmt_fab)) { display: none !important; }
|
|
510
524
|
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; }
|
|
511
525
|
html.dc-silbe-alarm-off :is(.alarm-popup.silbe-alarm, .rtbAlarmPopup) { display: none !important; }
|
|
512
526
|
html.dc-alarm-bubble :is(.alarm-popup.alarm-default, .alarmPopup) { display: none !important; }
|
|
@@ -1223,6 +1237,9 @@
|
|
|
1223
1237
|
#dc-custom-modal .dc-btn.mini.id-type { color: var(--dc-type-id); }
|
|
1224
1238
|
#dc-custom-modal .dc-btn.mini.nick-type { color: var(--dc-text-dim); }
|
|
1225
1239
|
|
|
1240
|
+
#dc-custom-modal .dc-btn:disabled { opacity: .35; cursor: default; }
|
|
1241
|
+
#dc-custom-modal .dc-btn.on-state { background: var(--dc-success); }
|
|
1242
|
+
|
|
1226
1243
|
#dc-custom-modal .dc-btn.mini.on {
|
|
1227
1244
|
border-color: var(--dc-danger);
|
|
1228
1245
|
background: var(--dc-danger);
|
|
@@ -2030,6 +2047,25 @@
|
|
|
2030
2047
|
font-weight: 700;
|
|
2031
2048
|
}
|
|
2032
2049
|
|
|
2050
|
+
#dc-preview-backdrop {
|
|
2051
|
+
position: fixed;
|
|
2052
|
+
inset: 0;
|
|
2053
|
+
z-index: 99996;
|
|
2054
|
+
background: rgba(0,0,0,.18);
|
|
2055
|
+
-webkit-backdrop-filter: blur(14px) saturate(1.1);
|
|
2056
|
+
backdrop-filter: blur(14px) saturate(1.1);
|
|
2057
|
+
opacity: 0;
|
|
2058
|
+
visibility: hidden;
|
|
2059
|
+
transition: opacity .2s ease, visibility 0s linear .2s;
|
|
2060
|
+
}
|
|
2061
|
+
|
|
2062
|
+
html.dc-dark #dc-preview-backdrop { background: rgba(0,0,0,.4); }
|
|
2063
|
+
#dc-preview-backdrop.show { opacity: 1; visibility: visible; transition: opacity .2s ease, visibility 0s; }
|
|
2064
|
+
|
|
2065
|
+
#dc-preview.dc-pv-peek { transform: var(--dc-pv-from, translate(-50%, -50%) scale(.9)); transition: opacity .18s ease, transform .32s cubic-bezier(.2,.9,.3,1.15), visibility 0s linear .32s; }
|
|
2066
|
+
#dc-preview.dc-pv-peek.show { transform: translate(-50%, -50%); transition: opacity .18s ease, transform .32s cubic-bezier(.2,.9,.3,1.15), visibility 0s; }
|
|
2067
|
+
#dc-preview.dc-pv-peek { box-shadow: 0 24px 80px rgba(0,0,0,.35); }
|
|
2068
|
+
|
|
2033
2069
|
.dc-pv-anchor { text-decoration: underline !important; text-decoration-color: var(--dc-accent-ink) !important; }
|
|
2034
2070
|
|
|
2035
2071
|
.dc-key-cursor { outline: 2px solid var(--dc-accent-ink) !important; outline-offset: -2px; background: var(--dc-accent-soft) !important; }
|
|
@@ -3158,6 +3194,7 @@
|
|
|
3158
3194
|
root.classList.toggle('dc-ad-slots-off', settings.hideAdSlots !== false);
|
|
3159
3195
|
root.classList.toggle('dc-news-rows-off', settings.hideNewsRows !== false);
|
|
3160
3196
|
root.classList.toggle('dc-comment-ads-off', settings.hideCommentAds !== false);
|
|
3197
|
+
root.classList.toggle('dc-comment-fab-off', settings.hideCommentFab !== false);
|
|
3161
3198
|
root.classList.toggle('dc-silbe-alarm-off', settings.hideSilbeAlarm !== false);
|
|
3162
3199
|
root.classList.toggle('dc-alarm-bubble', settings.alarmBubble !== false);
|
|
3163
3200
|
root.classList.toggle('dc-hide-scrollbar', settings.autoHideScrollbar !== false && root.classList.contains('dc-classic-scrollbar'));
|
|
@@ -9507,7 +9544,10 @@
|
|
|
9507
9544
|
link.addEventListener('click', () => closeBlockModal());
|
|
9508
9545
|
actionsEl.appendChild(link);
|
|
9509
9546
|
} else {
|
|
9510
|
-
|
|
9547
|
+
const btn = makeButton(action.label, `mini ${action.className || ''}`, action.onClick);
|
|
9548
|
+
|
|
9549
|
+
btn.disabled = !!action.disabled;
|
|
9550
|
+
actionsEl.appendChild(btn);
|
|
9511
9551
|
}
|
|
9512
9552
|
});
|
|
9513
9553
|
|
|
@@ -10405,6 +10445,14 @@
|
|
|
10405
10445
|
off: '댓글돌이를 그대로 보여 줍니다.',
|
|
10406
10446
|
refresh: 'root'
|
|
10407
10447
|
},
|
|
10448
|
+
{
|
|
10449
|
+
group: 'clean',
|
|
10450
|
+
key: 'hideCommentFab',
|
|
10451
|
+
name: "떠 있는 '댓글로 이동' 버튼 숨기기 (모바일)",
|
|
10452
|
+
on: '글 화면 오른쪽 아래에 떠 있는 댓글 버튼을 숨깁니다. 본문 아래 [댓글] 링크와 차단 관리 버튼은 그대로입니다.',
|
|
10453
|
+
off: '댓글로 이동 버튼을 보여 줍니다.',
|
|
10454
|
+
refresh: 'root'
|
|
10455
|
+
},
|
|
10408
10456
|
{
|
|
10409
10457
|
group: 'clean',
|
|
10410
10458
|
key: 'hideAdSlots',
|
|
@@ -10490,9 +10538,18 @@
|
|
|
10490
10538
|
{
|
|
10491
10539
|
group: 'list',
|
|
10492
10540
|
key: 'hoverPreview',
|
|
10493
|
-
name: '
|
|
10494
|
-
on: '목록
|
|
10495
|
-
off: '
|
|
10541
|
+
name: '제목을 길게 누르면 글 미리보기 (PC)',
|
|
10542
|
+
on: '아이폰처럼 목록 제목을 길게 누르면 화면이 흐려지며 글 전체가 가운데에 떠오릅니다. 짧게 누르면 평소처럼 글로 갑니다. 창에서 본문을 누르면 글로 이동, 바깥을 누르거나 Esc 로 닫습니다. 스크랩·작성자 차단도 바로 할 수 있고, 차단한 이미지·단어는 미리보기에서도 가립니다. 키보드로 고른 글은 P 로 엽니다.',
|
|
10543
|
+
off: '미리보기를 쓰지 않습니다.',
|
|
10544
|
+
refresh: 'root'
|
|
10545
|
+
},
|
|
10546
|
+
{
|
|
10547
|
+
group: 'list',
|
|
10548
|
+
key: 'previewHover',
|
|
10549
|
+
parent: 'hoverPreview',
|
|
10550
|
+
name: '마우스를 올려 멈춰도 미리보기 (PC)',
|
|
10551
|
+
on: '제목 위에 마우스를 잠시 멈추면 누르지 않아도 미리보기가 뜹니다. 창 밖으로 마우스가 나가면 닫힙니다.',
|
|
10552
|
+
off: '길게 눌렀을 때만 미리보기가 뜹니다.',
|
|
10496
10553
|
refresh: 'root'
|
|
10497
10554
|
},
|
|
10498
10555
|
{
|
|
@@ -11759,7 +11816,7 @@
|
|
|
11759
11816
|
{ key: 'words', label: '단어', fields: ['keywords', 'titles'] },
|
|
11760
11817
|
{ key: 'heads', label: '말머리', fields: ['heads'] },
|
|
11761
11818
|
{ key: 'scraps', label: '스크랩', fields: ['scraps'] },
|
|
11762
|
-
{ key: 'recent', label: '최근 갤러리', fields: ['recent'] },
|
|
11819
|
+
{ key: 'recent', label: '최근 갤러리', fields: ['recent', 'recentOrder'] },
|
|
11763
11820
|
{ key: 'search', label: '검색 기록', fields: ['search'] },
|
|
11764
11821
|
{ key: 'settings', label: '설정', fields: ['settings'] }
|
|
11765
11822
|
];
|
|
@@ -11929,6 +11986,7 @@
|
|
|
11929
11986
|
scraps: scraps.map(item => ({ ...item })),
|
|
11930
11987
|
settings: syncedSettings(),
|
|
11931
11988
|
recent: collectRecentForSync(),
|
|
11989
|
+
recentOrder: collectRecentOrderForSync(),
|
|
11932
11990
|
search: collectSearchForSync(),
|
|
11933
11991
|
hits: collectHitsForSync()
|
|
11934
11992
|
});
|
|
@@ -12038,6 +12096,7 @@
|
|
|
12038
12096
|
scraps: mergeScraps(local.scraps, base && base.scraps, remote && remote.scraps),
|
|
12039
12097
|
settings: mergeSettings(local.settings, base && base.settings, remote && remote.settings),
|
|
12040
12098
|
recent: mergeRecent(local.recent, base && base.recent, remote && remote.recent),
|
|
12099
|
+
recentOrder: mergeRecentOrder(local.recentOrder, remote && remote.recentOrder),
|
|
12041
12100
|
search: mergeSearchHistory(local.search, base && base.search, remote && remote.search),
|
|
12042
12101
|
hits: mergeBlockHits(local.hits, remote && remote.hits)
|
|
12043
12102
|
};
|
|
@@ -12093,6 +12152,7 @@
|
|
|
12093
12152
|
const data = collectSyncData();
|
|
12094
12153
|
|
|
12095
12154
|
delete data.recent;
|
|
12155
|
+
delete data.recentOrder;
|
|
12096
12156
|
delete data.search;
|
|
12097
12157
|
delete data.hits;
|
|
12098
12158
|
|
|
@@ -12100,6 +12160,7 @@
|
|
|
12100
12160
|
};
|
|
12101
12161
|
|
|
12102
12162
|
const applySyncData = data => {
|
|
12163
|
+
const orderChanged = data && 'recentOrder' in data ? applyRecentOrderFromSync(data.recentOrder) : false;
|
|
12103
12164
|
const recentChanged = data && 'recent' in data ? applyRecentFromSync(data.recent) : false;
|
|
12104
12165
|
const searchChanged = data && 'search' in data ? applySearchFromSync(data.search) : false;
|
|
12105
12166
|
const hitsChanged = data && 'hits' in data ? applyHitsFromSync(data.hits) : false;
|
|
@@ -12157,7 +12218,7 @@
|
|
|
12157
12218
|
refreshAfterBlockChange();
|
|
12158
12219
|
}
|
|
12159
12220
|
|
|
12160
|
-
return changed || recentChanged || searchChanged || hitsChanged;
|
|
12221
|
+
return changed || recentChanged || orderChanged || searchChanged || hitsChanged;
|
|
12161
12222
|
};
|
|
12162
12223
|
|
|
12163
12224
|
const saveSyncUndo = (before, incoming, summary, from) => {
|
|
@@ -13401,6 +13462,38 @@
|
|
|
13401
13462
|
};
|
|
13402
13463
|
};
|
|
13403
13464
|
|
|
13465
|
+
const readRecentOrder = () => {
|
|
13466
|
+
const stored = readObject(STORAGE.recentOrder);
|
|
13467
|
+
|
|
13468
|
+
return {
|
|
13469
|
+
fixed: stored.fixed === true,
|
|
13470
|
+
keys: Array.isArray(stored.keys) ? stored.keys.filter(key => typeof key === 'string').slice(0, 200) : [],
|
|
13471
|
+
at: Number(stored.at) || 0
|
|
13472
|
+
};
|
|
13473
|
+
};
|
|
13474
|
+
|
|
13475
|
+
let recentOrder = readRecentOrder();
|
|
13476
|
+
|
|
13477
|
+
const saveRecentOrder = patch => {
|
|
13478
|
+
recentOrder = { ...recentOrder, ...patch, at: Date.now() };
|
|
13479
|
+
writeJson(STORAGE.recentOrder, recentOrder);
|
|
13480
|
+
};
|
|
13481
|
+
|
|
13482
|
+
const orderRecent = list => {
|
|
13483
|
+
const byTime = [...list].sort((a, b) => b.at - a.at);
|
|
13484
|
+
|
|
13485
|
+
if (!recentOrder.fixed) {
|
|
13486
|
+
return byTime;
|
|
13487
|
+
}
|
|
13488
|
+
|
|
13489
|
+
const index = new Map(recentOrder.keys.map((key, i) => [key, i]));
|
|
13490
|
+
const known = byTime.filter(item => index.has(recentKeyOf(item))).sort((a, b) => index.get(recentKeyOf(a)) - index.get(recentKeyOf(b)));
|
|
13491
|
+
|
|
13492
|
+
const fresh = byTime.filter(item => !index.has(recentKeyOf(item))).reverse();
|
|
13493
|
+
|
|
13494
|
+
return [...known, ...fresh];
|
|
13495
|
+
};
|
|
13496
|
+
|
|
13404
13497
|
const tidyRecent = list => {
|
|
13405
13498
|
const best = new Map();
|
|
13406
13499
|
|
|
@@ -13413,7 +13506,62 @@
|
|
|
13413
13506
|
}
|
|
13414
13507
|
});
|
|
13415
13508
|
|
|
13416
|
-
|
|
13509
|
+
const kept = [...best.values()].sort((a, b) => b.at - a.at).slice(0, RECENT_LIMIT);
|
|
13510
|
+
const ordered = orderRecent(kept);
|
|
13511
|
+
|
|
13512
|
+
if (recentOrder.fixed) {
|
|
13513
|
+
const keys = ordered.map(recentKeyOf);
|
|
13514
|
+
|
|
13515
|
+
if (JSON.stringify(keys) !== JSON.stringify(recentOrder.keys.filter(key => keys.includes(key)))) {
|
|
13516
|
+
recentOrder = { ...recentOrder, keys };
|
|
13517
|
+
writeJson(STORAGE.recentOrder, recentOrder);
|
|
13518
|
+
}
|
|
13519
|
+
}
|
|
13520
|
+
|
|
13521
|
+
return ordered;
|
|
13522
|
+
};
|
|
13523
|
+
|
|
13524
|
+
const setRecentFixed = fixed => {
|
|
13525
|
+
saveRecentOrder({ fixed, keys: recentGalls.map(recentKeyOf) });
|
|
13526
|
+
recentGalls = tidyRecent(recentGalls);
|
|
13527
|
+
writeJson(STORAGE.recent, recentGalls);
|
|
13528
|
+
exportNativeRecent();
|
|
13529
|
+
reorderNativeRecentDom();
|
|
13530
|
+
};
|
|
13531
|
+
|
|
13532
|
+
const moveRecent = (item, step) => {
|
|
13533
|
+
const list = [...recentGalls];
|
|
13534
|
+
const from = list.indexOf(item);
|
|
13535
|
+
const to = Math.max(0, Math.min(list.length - 1, from + step));
|
|
13536
|
+
|
|
13537
|
+
if (from < 0 || from === to) {
|
|
13538
|
+
return;
|
|
13539
|
+
}
|
|
13540
|
+
|
|
13541
|
+
list.splice(to, 0, ...list.splice(from, 1));
|
|
13542
|
+
saveRecentOrder({ fixed: true, keys: list.map(recentKeyOf) });
|
|
13543
|
+
recentGalls = tidyRecent(list);
|
|
13544
|
+
writeJson(STORAGE.recent, recentGalls);
|
|
13545
|
+
exportNativeRecent();
|
|
13546
|
+
reorderNativeRecentDom();
|
|
13547
|
+
};
|
|
13548
|
+
|
|
13549
|
+
const reorderNativeRecentDom = () => {
|
|
13550
|
+
if (!IS_PC_SITE || !recentOrder.fixed) {
|
|
13551
|
+
return;
|
|
13552
|
+
}
|
|
13553
|
+
|
|
13554
|
+
const index = new Map(recentGalls.map((item, i) => [item.id, i]));
|
|
13555
|
+
|
|
13556
|
+
document.querySelectorAll('#visit_history .vst_listbox, .visit_tablist .under_listbox, #visit_history_lyr .vst_list').forEach(box => {
|
|
13557
|
+
const items = [...box.children].filter(li => li.tagName === 'LI');
|
|
13558
|
+
const idOf = li => li.querySelector('[data-id]')?.getAttribute('data-id') || (/lately_gall_(\S+)/.exec(li.className) || [])[1] || '';
|
|
13559
|
+
const sorted = [...items].sort((a, b) => (index.has(idOf(a)) ? index.get(idOf(a)) : 999) - (index.has(idOf(b)) ? index.get(idOf(b)) : 999));
|
|
13560
|
+
|
|
13561
|
+
if (sorted.some((li, i) => li !== items[i])) {
|
|
13562
|
+
sorted.forEach(li => box.appendChild(li));
|
|
13563
|
+
}
|
|
13564
|
+
});
|
|
13417
13565
|
};
|
|
13418
13566
|
|
|
13419
13567
|
let recentGalls = tidyRecent(readArray(STORAGE.recent));
|
|
@@ -13636,6 +13784,31 @@
|
|
|
13636
13784
|
saveRecent();
|
|
13637
13785
|
};
|
|
13638
13786
|
|
|
13787
|
+
const watchNativeRecentDom = () => {
|
|
13788
|
+
if (!IS_PC_SITE) {
|
|
13789
|
+
return;
|
|
13790
|
+
}
|
|
13791
|
+
|
|
13792
|
+
const target = document.getElementById('visit_history');
|
|
13793
|
+
|
|
13794
|
+
if (!target) {
|
|
13795
|
+
return;
|
|
13796
|
+
}
|
|
13797
|
+
|
|
13798
|
+
let timer = null;
|
|
13799
|
+
|
|
13800
|
+
new MutationObserver(() => {
|
|
13801
|
+
if (!recentOrder.fixed) {
|
|
13802
|
+
return;
|
|
13803
|
+
}
|
|
13804
|
+
|
|
13805
|
+
clearTimeout(timer);
|
|
13806
|
+
timer = setTimeout(reorderNativeRecentDom, 60);
|
|
13807
|
+
}).observe(target, { childList: true, subtree: true });
|
|
13808
|
+
|
|
13809
|
+
reorderNativeRecentDom();
|
|
13810
|
+
};
|
|
13811
|
+
|
|
13639
13812
|
const initRecentEarly = () => {
|
|
13640
13813
|
importNativeRecent();
|
|
13641
13814
|
exportNativeRecent();
|
|
@@ -13645,10 +13818,12 @@
|
|
|
13645
13818
|
importNativeRecent();
|
|
13646
13819
|
recordGalleryVisit();
|
|
13647
13820
|
exportNativeRecent();
|
|
13821
|
+
watchNativeRecentDom();
|
|
13648
13822
|
|
|
13649
13823
|
const follow = () => {
|
|
13650
|
-
if (document.visibilityState === 'visible' && importNativeRecent()) {
|
|
13824
|
+
if (document.visibilityState === 'visible' && (importNativeRecent() || recentOrder.fixed)) {
|
|
13651
13825
|
exportNativeRecent();
|
|
13826
|
+
reorderNativeRecentDom();
|
|
13652
13827
|
}
|
|
13653
13828
|
};
|
|
13654
13829
|
|
|
@@ -13658,6 +13833,31 @@
|
|
|
13658
13833
|
|
|
13659
13834
|
const collectRecentForSync = () => recentGalls.map(item => ({ ...item }));
|
|
13660
13835
|
|
|
13836
|
+
const collectRecentOrderForSync = () => ({ ...recentOrder, keys: [...recentOrder.keys] });
|
|
13837
|
+
|
|
13838
|
+
const mergeRecentOrder = (local, remote) => {
|
|
13839
|
+
if (!remote || typeof remote !== 'object' || !Array.isArray(remote.keys)) {
|
|
13840
|
+
return local;
|
|
13841
|
+
}
|
|
13842
|
+
|
|
13843
|
+
return (Number(remote.at) || 0) > (Number(local && local.at) || 0) ? remote : local;
|
|
13844
|
+
};
|
|
13845
|
+
|
|
13846
|
+
const applyRecentOrderFromSync = data => {
|
|
13847
|
+
if (!data || typeof data !== 'object' || !Array.isArray(data.keys) || (Number(data.at) || 0) <= recentOrder.at) {
|
|
13848
|
+
return false;
|
|
13849
|
+
}
|
|
13850
|
+
|
|
13851
|
+
recentOrder = { fixed: data.fixed === true, keys: data.keys.filter(key => typeof key === 'string').slice(0, 200), at: Number(data.at) };
|
|
13852
|
+
writeJson(STORAGE.recentOrder, recentOrder);
|
|
13853
|
+
recentGalls = tidyRecent(recentGalls);
|
|
13854
|
+
writeJson(STORAGE.recent, recentGalls);
|
|
13855
|
+
exportNativeRecent();
|
|
13856
|
+
reorderNativeRecentDom();
|
|
13857
|
+
|
|
13858
|
+
return true;
|
|
13859
|
+
};
|
|
13860
|
+
|
|
13661
13861
|
const mergeRecent = (local = [], base, remote) => {
|
|
13662
13862
|
if (!Array.isArray(remote)) {
|
|
13663
13863
|
return tidyRecent(local || []);
|
|
@@ -14246,18 +14446,35 @@
|
|
|
14246
14446
|
return;
|
|
14247
14447
|
}
|
|
14248
14448
|
|
|
14449
|
+
const orderRow = h('div', 'dc-btn-row');
|
|
14450
|
+
|
|
14451
|
+
orderRow.appendChild(makeButton(recentOrder.fixed ? '📌 순서 고정 중 · 끄기' : '📌 지금 순서로 고정', recentOrder.fixed ? 'wide on-state' : 'wide', () => {
|
|
14452
|
+
setRecentFixed(!recentOrder.fixed);
|
|
14453
|
+
showToast(recentOrder.fixed ? '📌 최근 방문 순서를 고정했습니다 · ↑↓ 로 바꿀 수 있어요' : '최근 방문 순서 고정을 풀었습니다 (방문한 순서로)');
|
|
14454
|
+
rerenderModal();
|
|
14455
|
+
}));
|
|
14456
|
+
area.appendChild(orderRow);
|
|
14457
|
+
|
|
14458
|
+
if (recentOrder.fixed) {
|
|
14459
|
+
area.appendChild(makeNote('고정한 순서는 디시 화면의 최근 방문 목록에도 그대로 보이고, 어느 갤러리를 가도 바뀌지 않습니다. 처음 가는 갤러리는 맨 뒤에 붙습니다.'));
|
|
14460
|
+
}
|
|
14461
|
+
|
|
14249
14462
|
const filterRow = makeFilterRow(recentGalls.length);
|
|
14250
14463
|
|
|
14251
14464
|
if (filterRow) {
|
|
14252
14465
|
area.appendChild(filterRow);
|
|
14253
14466
|
}
|
|
14254
14467
|
|
|
14255
|
-
recentGalls.forEach(item => {
|
|
14468
|
+
recentGalls.forEach((item, i) => {
|
|
14256
14469
|
area.appendChild(makeRow({
|
|
14257
14470
|
title: item.n || item.id,
|
|
14258
14471
|
chip: RECENT_KIND_LABEL[item.k] || '',
|
|
14259
14472
|
meta: [item.id, item.at ? timeAgo(item.at) : ''].filter(Boolean).join(' · '),
|
|
14260
14473
|
actions: [
|
|
14474
|
+
...(recentOrder.fixed ? [
|
|
14475
|
+
{ label: '↑', className: 'dc-icon-btn', onClick: () => { moveRecent(item, -1); rerenderModal(); }, disabled: i === 0 },
|
|
14476
|
+
{ label: '↓', className: 'dc-icon-btn', onClick: () => { moveRecent(item, 1); rerenderModal(); }, disabled: i === recentGalls.length - 1 }
|
|
14477
|
+
] : []),
|
|
14261
14478
|
{ label: '열기', href: recentUrl(item) },
|
|
14262
14479
|
{
|
|
14263
14480
|
label: '지우기',
|
|
@@ -14525,7 +14742,7 @@
|
|
|
14525
14742
|
|
|
14526
14743
|
// =========================================================
|
|
14527
14744
|
|
|
14528
|
-
const CLEANUP_IDLE_DAYS =
|
|
14745
|
+
const CLEANUP_IDLE_DAYS = 7;
|
|
14529
14746
|
|
|
14530
14747
|
const cleanupSuggestions = () => {
|
|
14531
14748
|
flushBlockHits();
|
|
@@ -14762,7 +14979,11 @@
|
|
|
14762
14979
|
'</div>' +
|
|
14763
14980
|
'<div class="dc-pv-body"></div>';
|
|
14764
14981
|
previewEl.addEventListener('mouseenter', () => clearTimeout(previewCloseTimer));
|
|
14765
|
-
previewEl.addEventListener('mouseleave', () =>
|
|
14982
|
+
previewEl.addEventListener('mouseleave', () => {
|
|
14983
|
+
if (previewMode === 'hover') {
|
|
14984
|
+
schedulePreviewClose();
|
|
14985
|
+
}
|
|
14986
|
+
});
|
|
14766
14987
|
document.body.appendChild(previewEl);
|
|
14767
14988
|
|
|
14768
14989
|
return previewEl;
|
|
@@ -14780,6 +15001,7 @@
|
|
|
14780
15001
|
|
|
14781
15002
|
clearTimeout(previewCloseTimer);
|
|
14782
15003
|
stopPreviewTheme();
|
|
15004
|
+
previewBackdrop?.classList.remove('show');
|
|
14783
15005
|
|
|
14784
15006
|
if (previewEl) {
|
|
14785
15007
|
previewEl.classList.remove('show');
|
|
@@ -14957,6 +15179,20 @@
|
|
|
14957
15179
|
doc.getElementById('dc-pv-css').textContent = previewFrameCss();
|
|
14958
15180
|
doc.body.appendChild(doc.importNode(body, true));
|
|
14959
15181
|
|
|
15182
|
+
doc.querySelectorAll('img').forEach(img => {
|
|
15183
|
+
let tries = 0;
|
|
15184
|
+
const original = img.getAttribute('src') || '';
|
|
15185
|
+
|
|
15186
|
+
img.addEventListener('error', () => {
|
|
15187
|
+
if (tries >= 4 || !/dcinside\.(com|co\.kr)\//.test(original)) {
|
|
15188
|
+
return;
|
|
15189
|
+
}
|
|
15190
|
+
|
|
15191
|
+
tries += 1;
|
|
15192
|
+
setTimeout(() => img.setAttribute('src', `${original}&${tries + 1}`), 250 * tries);
|
|
15193
|
+
});
|
|
15194
|
+
});
|
|
15195
|
+
|
|
14960
15196
|
doc.querySelectorAll('video[data-src]').forEach(video => {
|
|
14961
15197
|
const gif = video.getAttribute('data-src');
|
|
14962
15198
|
let swapped = false;
|
|
@@ -15220,18 +15456,135 @@
|
|
|
15220
15456
|
}
|
|
15221
15457
|
};
|
|
15222
15458
|
|
|
15459
|
+
const PREVIEW_HOLD_MS = 350;
|
|
15460
|
+
let previewMode = 'peek';
|
|
15461
|
+
let previewBackdrop = null;
|
|
15462
|
+
|
|
15463
|
+
const ensurePreviewBackdrop = () => {
|
|
15464
|
+
if (previewBackdrop && previewBackdrop.isConnected) {
|
|
15465
|
+
return previewBackdrop;
|
|
15466
|
+
}
|
|
15467
|
+
|
|
15468
|
+
previewBackdrop = h('div', 'dc-ui');
|
|
15469
|
+
previewBackdrop.id = 'dc-preview-backdrop';
|
|
15470
|
+
previewBackdrop.addEventListener('mousedown', event => {
|
|
15471
|
+
event.preventDefault();
|
|
15472
|
+
event.stopPropagation();
|
|
15473
|
+
closePreview();
|
|
15474
|
+
});
|
|
15475
|
+
document.body.appendChild(previewBackdrop);
|
|
15476
|
+
|
|
15477
|
+
return previewBackdrop;
|
|
15478
|
+
};
|
|
15479
|
+
|
|
15480
|
+
const openPreview = (anchor, mode) => {
|
|
15481
|
+
previewMode = mode;
|
|
15482
|
+
|
|
15483
|
+
const el = ensurePreviewEl();
|
|
15484
|
+
|
|
15485
|
+
el.classList.toggle('dc-pv-peek', mode === 'peek');
|
|
15486
|
+
|
|
15487
|
+
try {
|
|
15488
|
+
const r = anchor.getBoundingClientRect();
|
|
15489
|
+
const ox = Math.round(r.left + Math.min(r.width, 240) / 2 - window.innerWidth / 2);
|
|
15490
|
+
const oy = Math.round(r.top + r.height / 2 - window.innerHeight / 2);
|
|
15491
|
+
|
|
15492
|
+
el.style.setProperty('--dc-pv-from', `translate(calc(-50% + ${ox}px), calc(-50% + ${oy}px)) scale(.35)`);
|
|
15493
|
+
} catch {
|
|
15494
|
+
el.style.removeProperty('--dc-pv-from');
|
|
15495
|
+
}
|
|
15496
|
+
|
|
15497
|
+
if (mode === 'peek') {
|
|
15498
|
+
ensurePreviewBackdrop().classList.add('show');
|
|
15499
|
+
} else {
|
|
15500
|
+
previewBackdrop?.classList.remove('show');
|
|
15501
|
+
}
|
|
15502
|
+
|
|
15503
|
+
showPreviewFor(anchor);
|
|
15504
|
+
};
|
|
15505
|
+
|
|
15223
15506
|
const initHoverPreview = () => {
|
|
15224
15507
|
if (!IS_PC_SITE) {
|
|
15225
15508
|
return;
|
|
15226
15509
|
}
|
|
15227
15510
|
|
|
15511
|
+
const hoverOn = () => previewEnabled() && settings.previewHover === true;
|
|
15512
|
+
|
|
15228
15513
|
let hoverAnchor = null;
|
|
15229
15514
|
let pressedAnchor = null;
|
|
15515
|
+
let holdTimer = null;
|
|
15516
|
+
let holdStart = null;
|
|
15517
|
+
let swallowClick = null;
|
|
15518
|
+
|
|
15519
|
+
document.addEventListener('mousedown', event => {
|
|
15520
|
+
const anchor = event.target instanceof Element ? event.target.closest(PREVIEW_TITLE_SELECTOR) : null;
|
|
15521
|
+
|
|
15522
|
+
if (!anchor || anchor.closest('.dc-ui')) {
|
|
15523
|
+
if (previewOpen() && !(event.target instanceof Node && previewEl.contains(event.target))) {
|
|
15524
|
+
closePreview();
|
|
15525
|
+
}
|
|
15526
|
+
|
|
15527
|
+
return;
|
|
15528
|
+
}
|
|
15529
|
+
|
|
15530
|
+
pressedAnchor = anchor;
|
|
15531
|
+
clearTimeout(previewHoverTimer);
|
|
15532
|
+
|
|
15533
|
+
if (!previewEnabled() || event.button !== 0 || event.ctrlKey || event.metaKey || event.shiftKey || event.altKey) {
|
|
15534
|
+
return;
|
|
15535
|
+
}
|
|
15536
|
+
|
|
15537
|
+
holdStart = { x: event.clientX, y: event.clientY };
|
|
15538
|
+
clearTimeout(holdTimer);
|
|
15539
|
+
holdTimer = setTimeout(() => {
|
|
15540
|
+
holdTimer = null;
|
|
15541
|
+
swallowClick = anchor;
|
|
15542
|
+
|
|
15543
|
+
window.getSelection?.()?.removeAllRanges?.();
|
|
15544
|
+
openPreview(anchor, 'peek');
|
|
15545
|
+
}, PREVIEW_HOLD_MS);
|
|
15546
|
+
}, true);
|
|
15547
|
+
|
|
15548
|
+
document.addEventListener('mousemove', event => {
|
|
15549
|
+
if (holdTimer && holdStart && Math.hypot(event.clientX - holdStart.x, event.clientY - holdStart.y) > 8) {
|
|
15550
|
+
clearTimeout(holdTimer);
|
|
15551
|
+
holdTimer = null;
|
|
15552
|
+
}
|
|
15553
|
+
}, { passive: true, capture: true });
|
|
15554
|
+
|
|
15555
|
+
const cancelHold = () => {
|
|
15556
|
+
clearTimeout(holdTimer);
|
|
15557
|
+
holdTimer = null;
|
|
15558
|
+
};
|
|
15559
|
+
|
|
15560
|
+
document.addEventListener('mouseup', cancelHold, true);
|
|
15561
|
+
document.addEventListener('dragstart', event => {
|
|
15562
|
+
if (swallowClick || holdTimer) {
|
|
15563
|
+
event.preventDefault();
|
|
15564
|
+
}
|
|
15565
|
+
|
|
15566
|
+
cancelHold();
|
|
15567
|
+
}, true);
|
|
15568
|
+
|
|
15569
|
+
document.addEventListener('click', event => {
|
|
15570
|
+
if (!swallowClick) {
|
|
15571
|
+
return;
|
|
15572
|
+
}
|
|
15573
|
+
|
|
15574
|
+
const anchor = event.target instanceof Element ? event.target.closest(PREVIEW_TITLE_SELECTOR) : null;
|
|
15575
|
+
|
|
15576
|
+
if (anchor === swallowClick) {
|
|
15577
|
+
event.preventDefault();
|
|
15578
|
+
event.stopImmediatePropagation();
|
|
15579
|
+
}
|
|
15580
|
+
|
|
15581
|
+
swallowClick = null;
|
|
15582
|
+
}, true);
|
|
15230
15583
|
|
|
15231
15584
|
const armHover = anchor => {
|
|
15232
15585
|
clearTimeout(previewHoverTimer);
|
|
15233
15586
|
|
|
15234
|
-
if (!
|
|
15587
|
+
if (!hoverOn() || anchor === pressedAnchor || (previewOpen() && previewMode === 'peek')) {
|
|
15235
15588
|
return;
|
|
15236
15589
|
}
|
|
15237
15590
|
|
|
@@ -15243,18 +15596,14 @@
|
|
|
15243
15596
|
}
|
|
15244
15597
|
|
|
15245
15598
|
clearTimeout(previewCloseTimer);
|
|
15246
|
-
|
|
15599
|
+
openPreview(anchor, 'hover');
|
|
15247
15600
|
}, switching ? PREVIEW_SWITCH_DELAY : PREVIEW_DELAY);
|
|
15248
15601
|
};
|
|
15249
15602
|
|
|
15250
15603
|
document.addEventListener('mouseover', event => {
|
|
15251
15604
|
const anchor = event.target instanceof Element ? event.target.closest(PREVIEW_TITLE_SELECTOR) : null;
|
|
15252
15605
|
|
|
15253
|
-
if (!anchor || anchor.closest('.dc-ui')) {
|
|
15254
|
-
return;
|
|
15255
|
-
}
|
|
15256
|
-
|
|
15257
|
-
if (anchor === hoverAnchor) {
|
|
15606
|
+
if (!anchor || anchor.closest('.dc-ui') || anchor === hoverAnchor) {
|
|
15258
15607
|
return;
|
|
15259
15608
|
}
|
|
15260
15609
|
|
|
@@ -15265,7 +15614,7 @@
|
|
|
15265
15614
|
return;
|
|
15266
15615
|
}
|
|
15267
15616
|
|
|
15268
|
-
if (previewOpen()) {
|
|
15617
|
+
if (previewOpen() && previewMode === 'hover') {
|
|
15269
15618
|
clearTimeout(previewCloseTimer);
|
|
15270
15619
|
}
|
|
15271
15620
|
|
|
@@ -15282,15 +15631,6 @@
|
|
|
15282
15631
|
}
|
|
15283
15632
|
}, { passive: true });
|
|
15284
15633
|
|
|
15285
|
-
document.addEventListener('mousedown', event => {
|
|
15286
|
-
const anchor = event.target instanceof Element ? event.target.closest(PREVIEW_TITLE_SELECTOR) : null;
|
|
15287
|
-
|
|
15288
|
-
if (anchor) {
|
|
15289
|
-
pressedAnchor = anchor;
|
|
15290
|
-
clearTimeout(previewHoverTimer);
|
|
15291
|
-
}
|
|
15292
|
-
}, true);
|
|
15293
|
-
|
|
15294
15634
|
document.addEventListener('mouseout', event => {
|
|
15295
15635
|
const anchor = event.target instanceof Element ? event.target.closest(PREVIEW_TITLE_SELECTOR) : null;
|
|
15296
15636
|
|
|
@@ -15308,19 +15648,13 @@
|
|
|
15308
15648
|
|
|
15309
15649
|
clearTimeout(previewHoverTimer);
|
|
15310
15650
|
|
|
15311
|
-
if (previewOpen() && !(event.relatedTarget instanceof Node && previewEl.contains(event.relatedTarget))) {
|
|
15651
|
+
if (previewOpen() && previewMode === 'hover' && !(event.relatedTarget instanceof Node && previewEl.contains(event.relatedTarget))) {
|
|
15312
15652
|
schedulePreviewClose();
|
|
15313
15653
|
}
|
|
15314
15654
|
});
|
|
15315
15655
|
|
|
15316
|
-
document.addEventListener('mousedown', event => {
|
|
15317
|
-
if (previewOpen() && !(event.target instanceof Node && previewEl.contains(event.target))) {
|
|
15318
|
-
closePreview();
|
|
15319
|
-
}
|
|
15320
|
-
}, true);
|
|
15321
|
-
|
|
15322
15656
|
window.addEventListener('scroll', () => {
|
|
15323
|
-
if (previewOpen() && !previewEl.matches(':hover')) {
|
|
15657
|
+
if (previewOpen() && previewMode === 'hover' && !previewEl.matches(':hover')) {
|
|
15324
15658
|
closePreview();
|
|
15325
15659
|
}
|
|
15326
15660
|
}, { passive: true });
|
|
@@ -15356,7 +15690,7 @@
|
|
|
15356
15690
|
return false;
|
|
15357
15691
|
}
|
|
15358
15692
|
|
|
15359
|
-
|
|
15693
|
+
openPreview(anchor, 'peek');
|
|
15360
15694
|
return true;
|
|
15361
15695
|
};
|
|
15362
15696
|
// =========================================================
|