@kangsicj/dcinsideenhancer 1.1.0 → 1.1.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 +2 -1
- package/README.md +2 -1
- package/dist/DCInsideEnhancer.meta.js +1 -1
- package/dist/DCInsideEnhancer.user.js +306 -19
- package/package.json +1 -1
package/README.en.md
CHANGED
|
@@ -62,6 +62,7 @@ One userscript for DCInside on mobile and PC: smart blocking, endless lists, ima
|
|
|
62
62
|
- Gaps left by blocked posts are **refilled** from the next page
|
|
63
63
|
- Come back from a post and you're **right where you left off**
|
|
64
64
|
- Cleaner read-post colors and a back-to-top button
|
|
65
|
+
- Keyboard shortcuts on PC: **J/K** next/previous post, Enter to open, S to scrap, / to search
|
|
65
66
|
|
|
66
67
|
</td>
|
|
67
68
|
</tr>
|
|
@@ -77,7 +78,7 @@ One userscript for DCInside on mobile and PC: smart blocking, endless lists, ima
|
|
|
77
78
|
<td valign="top">
|
|
78
79
|
|
|
79
80
|
#### 🔄 Same setup on every device
|
|
80
|
-
- Block lists, scraps, settings
|
|
81
|
+
- Block lists, scraps, settings, **recently visited galleries** and mobile search history stay in sync automatically
|
|
81
82
|
- Link over the same Wi-Fi, with a 6-digit code or a link
|
|
82
83
|
- Data is **encrypted on your device** — only you can read it
|
|
83
84
|
- Get notified about incoming changes, with **undo**
|
package/README.md
CHANGED
|
@@ -62,6 +62,7 @@
|
|
|
62
62
|
- 차단으로 빈 자리는 다음 페이지 글로 **채워 둠**
|
|
63
63
|
- 글을 보고 돌아오면 **보던 위치 그대로**
|
|
64
64
|
- 읽은 글 색 정리, 맨 위로 버튼
|
|
65
|
+
- PC 키보드 단축키: **J/K** 다음·이전 글, Enter 열기, S 스크랩, / 검색
|
|
65
66
|
|
|
66
67
|
</td>
|
|
67
68
|
</tr>
|
|
@@ -77,7 +78,7 @@
|
|
|
77
78
|
<td valign="top">
|
|
78
79
|
|
|
79
80
|
#### 🔄 모든 기기에서 같은 설정
|
|
80
|
-
- 차단 목록 · 스크랩 · 설정 · **최근 방문
|
|
81
|
+
- 차단 목록 · 스크랩 · 설정 · **최근 방문 갤러리** · 모바일 검색 기록을 자동으로 맞춤
|
|
81
82
|
- 같은 와이파이, 6자리 코드, 연결 링크로 간단 연결
|
|
82
83
|
- 기기에서 **암호화**해 보내므로 내용은 나만 볼 수 있음
|
|
83
84
|
- 받은 변경은 알림으로 알려 주고 **되돌리기** 가능
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// @name DCInside Enhancer
|
|
3
3
|
// @name:ko 디시 인핸서
|
|
4
4
|
// @namespace https://github.com/kangsicj/DCInsideEnhancer
|
|
5
|
-
// @version 1.1.
|
|
5
|
+
// @version 1.1.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
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// @name DCInside Enhancer
|
|
3
3
|
// @name:ko 디시 인핸서
|
|
4
4
|
// @namespace https://github.com/kangsicj/DCInsideEnhancer
|
|
5
|
-
// @version 1.1.
|
|
5
|
+
// @version 1.1.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.1.
|
|
78
|
+
const SCRIPT_VERSION = '1.1.1';
|
|
79
79
|
const PROJECT_URL = 'https://github.com/kangsicj/DCInsideEnhancer';
|
|
80
80
|
const DONATE_URL = 'https://fairy.hada.io/@jinny#support';
|
|
81
81
|
|
|
@@ -107,6 +107,7 @@
|
|
|
107
107
|
syncRt: 'dc_sync_rt',
|
|
108
108
|
recent: 'dc_recent_galls',
|
|
109
109
|
recentNative: 'dc_recent_native',
|
|
110
|
+
searchHistory: 'dc_search_history',
|
|
110
111
|
alarmSeen: 'dc_alarm_marks',
|
|
111
112
|
seenVersion: 'dc_seen_version'
|
|
112
113
|
};
|
|
@@ -222,6 +223,7 @@
|
|
|
222
223
|
hideSilbeAlarm: true,
|
|
223
224
|
alarmBubble: true,
|
|
224
225
|
alarmKeepPolling: true,
|
|
226
|
+
keyboardShortcuts: true,
|
|
225
227
|
pcFitWidth: true,
|
|
226
228
|
autoHideScrollbar: true,
|
|
227
229
|
scrapIntercept: true,
|
|
@@ -275,6 +277,8 @@
|
|
|
275
277
|
--dc-type-id: #6d538d;
|
|
276
278
|
--dc-shadow: 0 8px 24px rgba(0,0,0,.16);
|
|
277
279
|
--dc-shadow-sm: 0 2px 8px rgba(0,0,0,.12);
|
|
280
|
+
--dc-raised: #ffffff;
|
|
281
|
+
--dc-raised-shadow: 0 1px 2px rgba(0,0,0,.08), 0 2px 8px rgba(0,0,0,.08);
|
|
278
282
|
--dc-overlay: rgba(0,0,0,.45);
|
|
279
283
|
--dc-ghost-bg: rgba(128,128,128,.12);
|
|
280
284
|
--dc-ghost-border: rgba(128,128,128,.38);
|
|
@@ -300,6 +304,8 @@
|
|
|
300
304
|
--dc-type-id: #9c86b1;
|
|
301
305
|
--dc-shadow: 0 8px 28px rgba(0,0,0,.55);
|
|
302
306
|
--dc-shadow-sm: 0 2px 10px rgba(0,0,0,.45);
|
|
307
|
+
--dc-raised: #383b44;
|
|
308
|
+
--dc-raised-shadow: inset 0 0 0 1px rgba(255,255,255,.07), 0 1px 2px rgba(0,0,0,.3);
|
|
303
309
|
--dc-overlay: rgba(0,0,0,.6);
|
|
304
310
|
--dc-ghost-bg: rgba(150,150,150,.11);
|
|
305
311
|
--dc-ghost-border: rgba(150,150,150,.30);
|
|
@@ -949,6 +955,10 @@
|
|
|
949
955
|
|
|
950
956
|
#dc-custom-modal .dc-head-btn {
|
|
951
957
|
flex: 0 0 auto;
|
|
958
|
+
display: inline-flex;
|
|
959
|
+
align-items: center;
|
|
960
|
+
justify-content: center;
|
|
961
|
+
box-sizing: border-box;
|
|
952
962
|
height: 30px;
|
|
953
963
|
padding: 0 12px;
|
|
954
964
|
border: 1px solid var(--dc-border);
|
|
@@ -957,7 +967,7 @@
|
|
|
957
967
|
color: var(--dc-text-dim);
|
|
958
968
|
font-size: 12px;
|
|
959
969
|
font-weight: 700;
|
|
960
|
-
line-height:
|
|
970
|
+
line-height: 1;
|
|
961
971
|
cursor: pointer;
|
|
962
972
|
}
|
|
963
973
|
|
|
@@ -978,7 +988,15 @@
|
|
|
978
988
|
|
|
979
989
|
#dc-custom-modal .dc-modal-tab {
|
|
980
990
|
flex: 1 1 0;
|
|
991
|
+
display: flex;
|
|
992
|
+
align-items: center;
|
|
993
|
+
justify-content: center;
|
|
994
|
+
gap: 3px;
|
|
995
|
+
min-width: 0;
|
|
996
|
+
box-sizing: border-box;
|
|
981
997
|
height: 36px;
|
|
998
|
+
margin: 0;
|
|
999
|
+
line-height: 1;
|
|
982
1000
|
padding: 0 2px;
|
|
983
1001
|
border: none;
|
|
984
1002
|
border-radius: 9px;
|
|
@@ -992,11 +1010,14 @@
|
|
|
992
1010
|
}
|
|
993
1011
|
|
|
994
1012
|
#dc-custom-modal .dc-modal-tab.active {
|
|
995
|
-
background: var(--dc-
|
|
1013
|
+
background: var(--dc-raised);
|
|
996
1014
|
color: var(--dc-accent-ink);
|
|
997
|
-
box-shadow: var(--dc-shadow
|
|
1015
|
+
box-shadow: var(--dc-raised-shadow);
|
|
998
1016
|
}
|
|
999
1017
|
|
|
1018
|
+
#dc-custom-modal .dc-modal-tab .dc-tab-ico { flex: 0 0 auto; font-size: 13px; line-height: 1; }
|
|
1019
|
+
#dc-custom-modal .dc-modal-tab .dc-tab-txt { min-width: 0; overflow: hidden; text-overflow: ellipsis; line-height: 1.2; }
|
|
1020
|
+
|
|
1000
1021
|
#dc-custom-modal .dc-section-title {
|
|
1001
1022
|
display: flex;
|
|
1002
1023
|
align-items: center;
|
|
@@ -1830,6 +1851,8 @@
|
|
|
1830
1851
|
|
|
1831
1852
|
#dc-custom-modal .dc-row.dc-row-new .dc-row-title { font-weight: 800; }
|
|
1832
1853
|
|
|
1854
|
+
.dc-key-cursor { outline: 2px solid var(--dc-accent-ink) !important; outline-offset: -2px; background: var(--dc-accent-soft) !important; }
|
|
1855
|
+
|
|
1833
1856
|
#dc-custom-modal .dc-about {
|
|
1834
1857
|
margin-top: 14px;
|
|
1835
1858
|
padding: 12px 13px;
|
|
@@ -3234,7 +3257,7 @@
|
|
|
3234
3257
|
|
|
3235
3258
|
window.addEventListener('beforeunload', noteNetLeaving);
|
|
3236
3259
|
|
|
3237
|
-
|
|
3260
|
+
window.addEventListener('click', event => {
|
|
3238
3261
|
const link = event.target instanceof Element ? event.target.closest('a[href]') : null;
|
|
3239
3262
|
|
|
3240
3263
|
if (!link || event.defaultPrevented || event.button > 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) {
|
|
@@ -3253,7 +3276,7 @@
|
|
|
3253
3276
|
}
|
|
3254
3277
|
} catch {
|
|
3255
3278
|
}
|
|
3256
|
-
}
|
|
3279
|
+
});
|
|
3257
3280
|
|
|
3258
3281
|
try {
|
|
3259
3282
|
if (window.navigation && typeof window.navigation.addEventListener === 'function') {
|
|
@@ -5823,6 +5846,38 @@
|
|
|
5823
5846
|
// =========================================================
|
|
5824
5847
|
|
|
5825
5848
|
let toastTimer = null;
|
|
5849
|
+
const toastListeners = [];
|
|
5850
|
+
|
|
5851
|
+
const notifyToastListeners = () => toastListeners.forEach(fn => {
|
|
5852
|
+
try {
|
|
5853
|
+
fn();
|
|
5854
|
+
} catch {
|
|
5855
|
+
}
|
|
5856
|
+
});
|
|
5857
|
+
|
|
5858
|
+
const placeToast = toast => {
|
|
5859
|
+
toast.style.bottom = '';
|
|
5860
|
+
|
|
5861
|
+
const fab = document.getElementById('dc-custom-fab');
|
|
5862
|
+
|
|
5863
|
+
if (!fab || getComputedStyle(fab).display === 'none') {
|
|
5864
|
+
return;
|
|
5865
|
+
}
|
|
5866
|
+
|
|
5867
|
+
const f = fab.getBoundingClientRect();
|
|
5868
|
+
const t = toast.getBoundingClientRect();
|
|
5869
|
+
|
|
5870
|
+
if (!f.width || !t.width) {
|
|
5871
|
+
return;
|
|
5872
|
+
}
|
|
5873
|
+
|
|
5874
|
+
const overlapX = t.left < f.right + 6 && t.right > f.left - 6;
|
|
5875
|
+
const overlapY = t.top < f.bottom + 6 && t.bottom > f.top - 6;
|
|
5876
|
+
|
|
5877
|
+
if (overlapX && overlapY) {
|
|
5878
|
+
toast.style.bottom = `${Math.round(window.innerHeight - f.top + 10)}px`;
|
|
5879
|
+
}
|
|
5880
|
+
};
|
|
5826
5881
|
|
|
5827
5882
|
const showToast = (text, { actionLabel = '', onAction = null, duration = 3500 } = {}) => {
|
|
5828
5883
|
let toast = document.getElementById('dc-toast');
|
|
@@ -5851,8 +5906,13 @@
|
|
|
5851
5906
|
};
|
|
5852
5907
|
|
|
5853
5908
|
toast.classList.add('show');
|
|
5909
|
+
placeToast(toast);
|
|
5910
|
+
notifyToastListeners();
|
|
5854
5911
|
clearTimeout(toastTimer);
|
|
5855
|
-
toastTimer = setTimeout(() =>
|
|
5912
|
+
toastTimer = setTimeout(() => {
|
|
5913
|
+
toast.classList.remove('show');
|
|
5914
|
+
setTimeout(notifyToastListeners, 220);
|
|
5915
|
+
}, duration);
|
|
5856
5916
|
};
|
|
5857
5917
|
|
|
5858
5918
|
const offerUndo = (text, undo) => {
|
|
@@ -10138,6 +10198,14 @@
|
|
|
10138
10198
|
off: '모든 댓글을 보여 줍니다.',
|
|
10139
10199
|
refresh: 'all'
|
|
10140
10200
|
},
|
|
10201
|
+
{
|
|
10202
|
+
group: 'list',
|
|
10203
|
+
key: 'keyboardShortcuts',
|
|
10204
|
+
name: '키보드 단축키 (PC)',
|
|
10205
|
+
on: '목록에서 J/K 로 글을 고르고 Enter 로 엽니다. 글에서는 J/K 로 다음·이전 글, S 스크랩, / 검색창, B 차단 관리, ? 안내.',
|
|
10206
|
+
off: '단축키를 쓰지 않습니다.',
|
|
10207
|
+
refresh: 'none'
|
|
10208
|
+
},
|
|
10141
10209
|
{
|
|
10142
10210
|
group: 'list',
|
|
10143
10211
|
key: 'autoFillList',
|
|
@@ -11295,7 +11363,11 @@
|
|
|
11295
11363
|
'<button id="dc-modal-close-btn" class="dc-head-btn" type="button">닫기</button>' +
|
|
11296
11364
|
'</div>' +
|
|
11297
11365
|
`<div class="dc-modal-tabs">${Object.keys(MODAL_VIEWS)
|
|
11298
|
-
.map(key =>
|
|
11366
|
+
.map(key => {
|
|
11367
|
+
const [ico, ...rest] = MODAL_VIEWS[key].tab.split(' ');
|
|
11368
|
+
|
|
11369
|
+
return `<button class="dc-modal-tab" type="button" data-dc-view="${key}"><span class="dc-tab-ico" aria-hidden="true">${ico}</span><span class="dc-tab-txt">${rest.join(' ')}</span></button>`;
|
|
11370
|
+
})
|
|
11299
11371
|
.join('')}</div>` +
|
|
11300
11372
|
'</div>' +
|
|
11301
11373
|
'<div id="dc-modal-content"></div>';
|
|
@@ -11385,6 +11457,7 @@
|
|
|
11385
11457
|
{ key: 'heads', label: '말머리', fields: ['heads'] },
|
|
11386
11458
|
{ key: 'scraps', label: '스크랩', fields: ['scraps'] },
|
|
11387
11459
|
{ key: 'recent', label: '최근 갤러리', fields: ['recent'] },
|
|
11460
|
+
{ key: 'search', label: '검색 기록', fields: ['search'] },
|
|
11388
11461
|
{ key: 'settings', label: '설정', fields: ['settings'] }
|
|
11389
11462
|
];
|
|
11390
11463
|
|
|
@@ -11552,7 +11625,8 @@
|
|
|
11552
11625
|
heads: JSON.parse(JSON.stringify(hiddenHeadMap || {})),
|
|
11553
11626
|
scraps: scraps.map(item => ({ ...item })),
|
|
11554
11627
|
settings: syncedSettings(),
|
|
11555
|
-
recent: collectRecentForSync()
|
|
11628
|
+
recent: collectRecentForSync(),
|
|
11629
|
+
search: collectSearchForSync()
|
|
11556
11630
|
});
|
|
11557
11631
|
|
|
11558
11632
|
const skippedFields = skip => SYNC_PARTS.filter(part => skip.includes(part.key)).flatMap(part => part.fields);
|
|
@@ -11659,7 +11733,8 @@
|
|
|
11659
11733
|
heads: mergeHeads(local.heads, base && base.heads, remote && remote.heads),
|
|
11660
11734
|
scraps: mergeScraps(local.scraps, base && base.scraps, remote && remote.scraps),
|
|
11661
11735
|
settings: mergeSettings(local.settings, base && base.settings, remote && remote.settings),
|
|
11662
|
-
recent: mergeRecent(local.recent, base && base.recent, remote && remote.recent)
|
|
11736
|
+
recent: mergeRecent(local.recent, base && base.recent, remote && remote.recent),
|
|
11737
|
+
search: mergeSearchHistory(local.search, base && base.search, remote && remote.search)
|
|
11663
11738
|
};
|
|
11664
11739
|
|
|
11665
11740
|
skippedFields(skip).forEach(field => {
|
|
@@ -11713,12 +11788,14 @@
|
|
|
11713
11788
|
const data = collectSyncData();
|
|
11714
11789
|
|
|
11715
11790
|
delete data.recent;
|
|
11791
|
+
delete data.search;
|
|
11716
11792
|
|
|
11717
11793
|
return JSON.stringify(data);
|
|
11718
11794
|
};
|
|
11719
11795
|
|
|
11720
11796
|
const applySyncData = data => {
|
|
11721
11797
|
const recentChanged = data && 'recent' in data ? applyRecentFromSync(data.recent) : false;
|
|
11798
|
+
const searchChanged = data && 'search' in data ? applySearchFromSync(data.search) : false;
|
|
11722
11799
|
const clean = sanitizeBackup({ app: BACKUP_APP, ...data });
|
|
11723
11800
|
const before = blockSyncData();
|
|
11724
11801
|
|
|
@@ -11773,7 +11850,7 @@
|
|
|
11773
11850
|
refreshAfterBlockChange();
|
|
11774
11851
|
}
|
|
11775
11852
|
|
|
11776
|
-
return changed || recentChanged;
|
|
11853
|
+
return changed || recentChanged || searchChanged;
|
|
11777
11854
|
};
|
|
11778
11855
|
|
|
11779
11856
|
const saveSyncUndo = (before, incoming, summary, from) => {
|
|
@@ -13134,8 +13211,12 @@
|
|
|
13134
13211
|
const nativeKeys = new Set(native.map(recentKeyOf));
|
|
13135
13212
|
const snapshot = readArray(STORAGE.recentNative).filter(key => typeof key === 'string');
|
|
13136
13213
|
|
|
13137
|
-
if (snapshot.length
|
|
13138
|
-
const
|
|
13214
|
+
if (snapshot.length) {
|
|
13215
|
+
const snapSet = new Set(snapshot);
|
|
13216
|
+
const added = native.filter(item => !snapSet.has(recentKeyOf(item))).length;
|
|
13217
|
+
const overflow = Math.max(0, snapshot.length + added - RECENT_NATIVE_CAP);
|
|
13218
|
+
const kept = snapshot.slice(0, snapshot.length - overflow);
|
|
13219
|
+
const removed = new Set(kept.filter(key => !nativeKeys.has(key)));
|
|
13139
13220
|
|
|
13140
13221
|
if (removed.size) {
|
|
13141
13222
|
recentGalls = recentGalls.filter(item => !removed.has(recentKeyOf(item)));
|
|
@@ -13489,24 +13570,40 @@
|
|
|
13489
13570
|
}
|
|
13490
13571
|
|
|
13491
13572
|
const fab = document.getElementById('dc-custom-fab');
|
|
13492
|
-
const rect = fab && fab.
|
|
13573
|
+
const rect = fab && getComputedStyle(fab).display !== 'none' ? fab.getBoundingClientRect() : null;
|
|
13493
13574
|
const vw = window.innerWidth;
|
|
13494
13575
|
const vh = window.innerHeight;
|
|
13576
|
+
let bottom = 144;
|
|
13495
13577
|
|
|
13496
13578
|
if (rect && rect.width) {
|
|
13497
13579
|
const onRight = rect.left + rect.width / 2 > vw / 2;
|
|
13498
13580
|
|
|
13499
13581
|
bubbleBox.style.left = onRight ? '' : `${Math.max(8, rect.left)}px`;
|
|
13500
13582
|
bubbleBox.style.right = onRight ? `${Math.max(8, vw - rect.right)}px` : '';
|
|
13501
|
-
|
|
13583
|
+
bottom = Math.max(8, vh - rect.top + 10);
|
|
13502
13584
|
bubbleBox.classList.toggle('dc-left', !onRight);
|
|
13503
13585
|
} else {
|
|
13504
13586
|
bubbleBox.style.left = '';
|
|
13505
13587
|
bubbleBox.style.right = '15px';
|
|
13506
|
-
bubbleBox.
|
|
13588
|
+
bubbleBox.classList.remove('dc-left');
|
|
13589
|
+
}
|
|
13590
|
+
|
|
13591
|
+
const toast = document.getElementById('dc-toast');
|
|
13592
|
+
|
|
13593
|
+
if (toast && toast.classList.contains('show')) {
|
|
13594
|
+
const t = toast.getBoundingClientRect();
|
|
13595
|
+
const box = bubbleBox.getBoundingClientRect();
|
|
13596
|
+
|
|
13597
|
+
if (t.width && t.left < box.right && t.right > box.left) {
|
|
13598
|
+
bottom = Math.max(bottom, vh - t.top + 8);
|
|
13599
|
+
}
|
|
13507
13600
|
}
|
|
13601
|
+
|
|
13602
|
+
bubbleBox.style.bottom = `${Math.round(bottom)}px`;
|
|
13508
13603
|
};
|
|
13509
13604
|
|
|
13605
|
+
toastListeners.push(() => placeBubbleBox());
|
|
13606
|
+
|
|
13510
13607
|
const ensureBubbleBox = () => {
|
|
13511
13608
|
if (bubbleBox && bubbleBox.isConnected) {
|
|
13512
13609
|
return bubbleBox;
|
|
@@ -13730,9 +13827,24 @@
|
|
|
13730
13827
|
}
|
|
13731
13828
|
};
|
|
13732
13829
|
|
|
13733
|
-
|
|
13734
|
-
if (alarmWatchTimer || !
|
|
13830
|
+
function initSiteAlarms() {
|
|
13831
|
+
if (alarmWatchTimer || !(IS_MOBILE_SITE || /^gall\.dcinside\.com$/i.test(location.hostname))) {
|
|
13832
|
+
paintAlarmBadge();
|
|
13833
|
+
return;
|
|
13834
|
+
}
|
|
13835
|
+
|
|
13836
|
+
if (!siteUserId() && !alarmStorageKeys().length) {
|
|
13735
13837
|
paintAlarmBadge();
|
|
13838
|
+
|
|
13839
|
+
if (!initSiteAlarms.retried) {
|
|
13840
|
+
initSiteAlarms.retried = true;
|
|
13841
|
+
[3000, 10000].forEach(delay => setTimeout(() => {
|
|
13842
|
+
if (!alarmWatchTimer && (siteUserId() || alarmStorageKeys().length)) {
|
|
13843
|
+
initSiteAlarms();
|
|
13844
|
+
}
|
|
13845
|
+
}, delay));
|
|
13846
|
+
}
|
|
13847
|
+
|
|
13736
13848
|
return;
|
|
13737
13849
|
}
|
|
13738
13850
|
|
|
@@ -13873,6 +13985,180 @@
|
|
|
13873
13985
|
})();
|
|
13874
13986
|
|
|
13875
13987
|
initRecentEarly();
|
|
13988
|
+
|
|
13989
|
+
// =========================================================
|
|
13990
|
+
|
|
13991
|
+
// =========================================================
|
|
13992
|
+
|
|
13993
|
+
const KEY_HELP = 'J/K 다음·이전 글 · Enter 열기 · S 스크랩 · / 검색 · B 차단 관리';
|
|
13994
|
+
|
|
13995
|
+
const typingTarget = el =>
|
|
13996
|
+
!!el && (el.isContentEditable || /^(INPUT|TEXTAREA|SELECT)$/.test(el.tagName) || !!el.closest?.('[contenteditable="true"]'));
|
|
13997
|
+
|
|
13998
|
+
const visibleListRows = () => {
|
|
13999
|
+
const listEl = pickListElement(document);
|
|
14000
|
+
|
|
14001
|
+
if (!listEl) {
|
|
14002
|
+
return [];
|
|
14003
|
+
}
|
|
14004
|
+
|
|
14005
|
+
return realListItems(listEl).filter(row => row.getClientRects().length && listItemKey(row) && !row.closest('.dc-ui'));
|
|
14006
|
+
};
|
|
14007
|
+
|
|
14008
|
+
let keyCursor = null;
|
|
14009
|
+
|
|
14010
|
+
const moveKeyCursor = step => {
|
|
14011
|
+
const rows = visibleListRows();
|
|
14012
|
+
|
|
14013
|
+
if (!rows.length) {
|
|
14014
|
+
return false;
|
|
14015
|
+
}
|
|
14016
|
+
|
|
14017
|
+
const here = currentPostKey();
|
|
14018
|
+
let index = keyCursor ? rows.indexOf(keyCursor) : -1;
|
|
14019
|
+
|
|
14020
|
+
if (currentPostInfo()) {
|
|
14021
|
+
const at = rows.findIndex(row => listItemKey(row) === here);
|
|
14022
|
+
const target = at >= 0 ? rows[at + step] : step > 0 ? rows[0] : null;
|
|
14023
|
+
const link = target && target.querySelector(POST_LINK_SELECTOR);
|
|
14024
|
+
|
|
14025
|
+
if (link) {
|
|
14026
|
+
location.href = link.href;
|
|
14027
|
+
return true;
|
|
14028
|
+
}
|
|
14029
|
+
|
|
14030
|
+
showToast(step > 0 ? '아래에 더 볼 글이 없습니다' : '첫 글입니다');
|
|
14031
|
+
return true;
|
|
14032
|
+
}
|
|
14033
|
+
|
|
14034
|
+
if (index < 0) {
|
|
14035
|
+
index = rows.findIndex(row => row.getBoundingClientRect().bottom > 60);
|
|
14036
|
+
index = Math.max(0, step > 0 ? index : index);
|
|
14037
|
+
} else {
|
|
14038
|
+
index = Math.min(rows.length - 1, Math.max(0, index + step));
|
|
14039
|
+
}
|
|
14040
|
+
|
|
14041
|
+
keyCursor?.classList.remove('dc-key-cursor');
|
|
14042
|
+
keyCursor = rows[index];
|
|
14043
|
+
keyCursor.classList.add('dc-key-cursor');
|
|
14044
|
+
keyCursor.scrollIntoView({ block: 'nearest' });
|
|
14045
|
+
|
|
14046
|
+
return true;
|
|
14047
|
+
};
|
|
14048
|
+
|
|
14049
|
+
const initKeyboardShortcuts = () => {
|
|
14050
|
+
document.addEventListener('keydown', event => {
|
|
14051
|
+
if (settings.keyboardShortcuts === false || event.defaultPrevented || event.ctrlKey || event.metaKey || event.altKey) {
|
|
14052
|
+
return;
|
|
14053
|
+
}
|
|
14054
|
+
|
|
14055
|
+
if (typingTarget(event.target) || typingTarget(document.activeElement)) {
|
|
14056
|
+
return;
|
|
14057
|
+
}
|
|
14058
|
+
|
|
14059
|
+
if (document.getElementById('dc-custom-modal')?.classList.contains('show')) {
|
|
14060
|
+
return;
|
|
14061
|
+
}
|
|
14062
|
+
|
|
14063
|
+
const key = event.key;
|
|
14064
|
+
let handled = false;
|
|
14065
|
+
|
|
14066
|
+
if (key === 'j' || key === 'J') {
|
|
14067
|
+
handled = moveKeyCursor(1);
|
|
14068
|
+
} else if (key === 'k' || key === 'K') {
|
|
14069
|
+
handled = moveKeyCursor(-1);
|
|
14070
|
+
} else if ((key === 'Enter' || key === 'o' || key === 'O') && keyCursor && keyCursor.isConnected) {
|
|
14071
|
+
const link = keyCursor.querySelector(POST_LINK_SELECTOR);
|
|
14072
|
+
|
|
14073
|
+
if (link) {
|
|
14074
|
+
location.href = link.href;
|
|
14075
|
+
handled = true;
|
|
14076
|
+
}
|
|
14077
|
+
} else if (key === 's' || key === 'S') {
|
|
14078
|
+
const info = currentPostInfo();
|
|
14079
|
+
|
|
14080
|
+
if (info) {
|
|
14081
|
+
toggleScrap(info);
|
|
14082
|
+
handled = true;
|
|
14083
|
+
}
|
|
14084
|
+
} else if (key === '/') {
|
|
14085
|
+
const box = document.querySelector('.in_keyword, #preSWord, input.js-keyword, input[name="search_keyword"], .top_search input[type="text"]');
|
|
14086
|
+
|
|
14087
|
+
if (box) {
|
|
14088
|
+
box.focus();
|
|
14089
|
+
handled = true;
|
|
14090
|
+
}
|
|
14091
|
+
} else if (key === 'b' || key === 'B') {
|
|
14092
|
+
openBlockModal(document.getElementById('dc-custom-fab')?.dataset.dcAlarm ? 'recent' : 'overview');
|
|
14093
|
+
handled = true;
|
|
14094
|
+
} else if (key === '?') {
|
|
14095
|
+
showToast(`⌨️ ${KEY_HELP}`, { duration: 6000 });
|
|
14096
|
+
handled = true;
|
|
14097
|
+
}
|
|
14098
|
+
|
|
14099
|
+
if (handled) {
|
|
14100
|
+
event.preventDefault();
|
|
14101
|
+
}
|
|
14102
|
+
});
|
|
14103
|
+
};
|
|
14104
|
+
|
|
14105
|
+
// =========================================================
|
|
14106
|
+
|
|
14107
|
+
// =========================================================
|
|
14108
|
+
|
|
14109
|
+
const SEARCH_HISTORY_MAX = 30;
|
|
14110
|
+
|
|
14111
|
+
const readSiteSearchHistory = () => {
|
|
14112
|
+
if (!IS_MOBILE_SITE) {
|
|
14113
|
+
return null;
|
|
14114
|
+
}
|
|
14115
|
+
|
|
14116
|
+
try {
|
|
14117
|
+
const list = JSON.parse(localStorage.getItem('searchHistory') || '[]');
|
|
14118
|
+
|
|
14119
|
+
return Array.isArray(list) ? list.filter(item => typeof item === 'string' && item.trim()).map(item => item.slice(0, 60)) : [];
|
|
14120
|
+
} catch {
|
|
14121
|
+
return [];
|
|
14122
|
+
}
|
|
14123
|
+
};
|
|
14124
|
+
|
|
14125
|
+
const collectSearchForSync = () => {
|
|
14126
|
+
const site = readSiteSearchHistory();
|
|
14127
|
+
const list = site || readArray(STORAGE.searchHistory).filter(item => typeof item === 'string');
|
|
14128
|
+
|
|
14129
|
+
return [...new Set(list)].slice(-SEARCH_HISTORY_MAX);
|
|
14130
|
+
};
|
|
14131
|
+
|
|
14132
|
+
const mergeSearchHistory = (local = [], base, remote) => {
|
|
14133
|
+
if (!Array.isArray(remote)) {
|
|
14134
|
+
return [...new Set(local || [])].slice(-SEARCH_HISTORY_MAX);
|
|
14135
|
+
}
|
|
14136
|
+
|
|
14137
|
+
return mergeList(local || [], Array.isArray(base) ? base : null, remote).slice(-SEARCH_HISTORY_MAX);
|
|
14138
|
+
};
|
|
14139
|
+
|
|
14140
|
+
const applySearchFromSync = list => {
|
|
14141
|
+
if (!Array.isArray(list)) {
|
|
14142
|
+
return false;
|
|
14143
|
+
}
|
|
14144
|
+
|
|
14145
|
+
const next = [...new Set(list.filter(item => typeof item === 'string'))].slice(-SEARCH_HISTORY_MAX);
|
|
14146
|
+
|
|
14147
|
+
if (JSON.stringify(next) === JSON.stringify(collectSearchForSync())) {
|
|
14148
|
+
return false;
|
|
14149
|
+
}
|
|
14150
|
+
|
|
14151
|
+
writeJson(STORAGE.searchHistory, next);
|
|
14152
|
+
|
|
14153
|
+
if (IS_MOBILE_SITE) {
|
|
14154
|
+
try {
|
|
14155
|
+
localStorage.setItem('searchHistory', JSON.stringify(next));
|
|
14156
|
+
} catch {
|
|
14157
|
+
}
|
|
14158
|
+
}
|
|
14159
|
+
|
|
14160
|
+
return true;
|
|
14161
|
+
};
|
|
13876
14162
|
// =========================================================
|
|
13877
14163
|
// 14. 플로팅 버튼
|
|
13878
14164
|
// =========================================================
|
|
@@ -14860,6 +15146,7 @@
|
|
|
14860
15146
|
initSync();
|
|
14861
15147
|
initRecent();
|
|
14862
15148
|
initSiteAlarms();
|
|
15149
|
+
initKeyboardShortcuts();
|
|
14863
15150
|
restoreListSnapshot();
|
|
14864
15151
|
processElements();
|
|
14865
15152
|
|