您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
디시인사이드(dcinside) 갤러리에서 사용할 수 있는 다양한 단축키를 제공합니다.
当前为
// ==UserScript== // @name dcinside shortcut // @namespace http://tampermonkey.net/ // @version 1.1.0 // @description 디시인사이드(dcinside) 갤러리에서 사용할 수 있는 다양한 단축키를 제공합니다. // - 글 목록에 번호 추가 (1~100번까지 표시) // - 숫자 키 (1~9, 0)로 해당 번호의 글로 이동 (0은 10번 글) // - ` or . + 숫자 입력 + ` or .으로 특정 번호의 글로 이동 (1~100번) // - ALT + 숫자 (1~9, 0): 즐겨찾는 갤러리 등록/이동 // - ALT + `: 즐겨찾는 갤러리 목록 표시/숨기기 // - W: 글쓰기 페이지로 이동 // - C: 댓글 입력창으로 커서 이동 // - D: 댓글 새로고침 및 스크롤 // - R: 페이지 새로고침 // - Q: 페이지 최상단으로 스크롤 // - E: 글 목록으로 스크롤 // - F: 전체글 보기로 이동 // - G: 개념글 보기로 이동 // - A: 이전 페이지로 이동 // - S: 다음 페이지로 이동 // - Z: 이전 글로 이동 // - X: 다음 글로 이동 // @author 노노하꼬 // @match *://gall.dcinside.com/* // @match *://www.dcinside.com/ // @icon https://www.google.com/s2/favicons?sz=64&domain=dcinside.com // @grant GM_setValue // @grant GM_getValue // @license MIT // @supportURL https://gallog.dcinside.com/nonohako/guestbook // ==/UserScript== (function() { 'use strict'; // Constants const FAVORITE_GALLERIES_KEY = 'dcinside_favorite_galleries'; const isTampermonkey = typeof GM_setValue !== 'undefined' && typeof GM_getValue !== 'undefined'; // Storage Module const Storage = { async getFavorites() { let favorites = {}; try { if (isTampermonkey) { favorites = GM_getValue(FAVORITE_GALLERIES_KEY, {}); } else { const data = localStorage.getItem(FAVORITE_GALLERIES_KEY) || this.getCookie(FAVORITE_GALLERIES_KEY); favorites = data ? JSON.parse(data) : {}; } } catch (error) { console.error('Failed to retrieve favorites:', error); } return favorites; }, saveFavorites(favorites) { try { const data = JSON.stringify(favorites); if (isTampermonkey) { GM_setValue(FAVORITE_GALLERIES_KEY, favorites); } else { localStorage.setItem(FAVORITE_GALLERIES_KEY, data); this.setCookie(FAVORITE_GALLERIES_KEY, data); } } catch (error) { console.error('Failed to save favorites:', error); alert('즐겨찾기 저장에 실패했습니다. 브라우저의 저장소 설정을 확인해주세요.'); } }, getCookie(name) { const value = document.cookie.match(`(^|;)\\s*${name}=([^;]+)`); return value ? decodeURIComponent(value[2]) : null; }, setCookie(name, value) { const date = new Date(); date.setFullYear(date.getFullYear() + 1); document.cookie = `${name}=${encodeURIComponent(value)}; expires=${date.toUTCString()}; path=/; domain=.dcinside.com`; }, async getAltNumberEnabled() { if (isTampermonkey) { return GM_getValue('altNumberEnabled', true); // 기본값: 활성화 } else { const data = localStorage.getItem('altNumberEnabled') || this.getCookie('altNumberEnabled'); return data !== null ? JSON.parse(data) : true; } }, saveAltNumberEnabled(enabled) { try { const data = JSON.stringify(enabled); if (isTampermonkey) { GM_setValue('altNumberEnabled', enabled); } else { localStorage.setItem('altNumberEnabled', data); this.setCookie('altNumberEnabled', data); } } catch (error) { console.error('Failed to save altNumberEnabled:', error); } } }; // UI Module const UI = { createElement(tag, styles, props = {}) { const el = document.createElement(tag); Object.assign(el.style, styles); Object.assign(el, props); return el; }, async showFavorites() { const container = this.createElement('div', { position: 'fixed', top: '50%', left: '50%', transform: 'translate(-50%, -50%)', backgroundColor: '#ffffff', padding: '20px', borderRadius: '16px', boxShadow: '0 8px 24px rgba(0,0,0,0.15)', zIndex: '10000', width: '360px', maxHeight: '80vh', overflowY: 'auto', fontFamily: "'Roboto', sans-serif", border: '1px solid #e0e0e0', transition: 'opacity 0.2s ease-in-out', opacity: '0' }); setTimeout(() => container.style.opacity = '1', 10); this.loadRobotoFont(); container.appendChild(this.createTitle()); const list = this.createList(); container.appendChild(list); container.appendChild(this.createAddContainer()); container.appendChild(this.createToggleAltNumber()); // 새로 추가: 토글 버튼 container.appendChild(this.createCloseButton(container)); document.body.appendChild(container); await this.updateFavoritesList(list); }, loadRobotoFont() { if (!document.querySelector('link[href*="Roboto"]')) { document.head.appendChild(this.createElement('link', {}, { rel: 'stylesheet', href: 'https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700&display=swap' })); } }, createToggleAltNumber() { const container = this.createElement('div', { display: 'flex', alignItems: 'center', justifyContent: 'space-between', margin: '15px 0', padding: '10px', backgroundColor: '#f5f5f5', borderRadius: '10px' }); const label = this.createElement('span', { fontSize: '14px', fontWeight: '500', color: '#424242' }, { textContent: 'ALT + 숫자 단축키 사용' }); const checkbox = this.createElement('input', { marginLeft: '10px' }, { type: 'checkbox' }); Storage.getAltNumberEnabled().then(enabled => { checkbox.checked = enabled; }); checkbox.addEventListener('change', async () => { await Storage.saveAltNumberEnabled(checkbox.checked); UI.showAlert(`ALT + 숫자 단축키가 ${checkbox.checked ? '활성화' : '비활성화'}되었습니다.`); }); container.appendChild(label); container.appendChild(checkbox); return container; }, createTitle() { return this.createElement('h3', { fontSize: '18px', fontWeight: '700', color: '#212121', margin: '0 0 15px 0', paddingBottom: '10px', borderBottom: '1px solid #e0e0e0' }, { textContent: '즐겨찾는 갤러리' }); }, createList() { return this.createElement('ul', { listStyle: 'none', margin: '0', padding: '0', maxHeight: '50vh', overflowY: 'auto' }); }, async updateFavoritesList(list) { list.innerHTML = ''; const favorites = await Storage.getFavorites(); Object.entries(favorites).forEach(([key, gallery]) => { list.appendChild(this.createFavoriteItem(key, gallery)); }); }, createFavoriteItem(key, gallery) { const item = this.createElement('li', { display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '12px 15px', margin: '5px 0', backgroundColor: '#fafafa', borderRadius: '10px', transition: 'background-color 0.2s ease', cursor: 'pointer' }); item.addEventListener('mouseenter', () => item.style.backgroundColor = '#f0f0f0'); item.addEventListener('mouseleave', () => item.style.backgroundColor = '#fafafa'); item.addEventListener('click', () => this.navigateToGallery(gallery)); // Ensure we display the gallery name properly const name = gallery.name || gallery.galleryName || gallery.galleryId || 'Unknown Gallery'; item.appendChild(this.createElement('span', { fontSize: '15px', fontWeight: '400', color: '#424242', flexGrow: '1', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }, { textContent: `${key}: ${name}` })); item.appendChild(this.createRemoveButton(key)); return item; }, createRemoveButton(key) { const button = this.createElement('button', { backgroundColor: 'transparent', color: '#757575', border: 'none', borderRadius: '50%', width: '24px', height: '24px', fontSize: '16px', lineHeight: '1', cursor: 'pointer', transition: 'color 0.2s ease, background-color 0.2s ease' }, { textContent: '✕' }); button.addEventListener('mouseenter', () => { button.style.color = '#d32f2f'; button.style.backgroundColor = '#ffebee'; }); button.addEventListener('mouseleave', () => { button.style.color = '#757575'; button.style.backgroundColor = 'transparent'; }); button.addEventListener('click', async (e) => { e.stopPropagation(); const favorites = await Storage.getFavorites(); delete favorites[key]; Storage.saveFavorites(favorites); await this.updateFavoritesList(button.closest('ul')); }); return button; }, createAddContainer() { const container = this.createElement('div', { display: 'flex', alignItems: 'center', justifyContent: 'center', gap: '8px', margin: '15px 0', padding: '15px', backgroundColor: '#f5f5f5', borderRadius: '10px' }); const input = this.createElement('input', { width: '45px', padding: '8px', border: '1px solid #e0e0e0', borderRadius: '8px', fontSize: '14px', textAlign: 'center', outline: 'none', transition: 'border-color 0.2s ease', backgroundColor: '#ffffff' }, { type: 'text', placeholder: '0-9' }); input.addEventListener('focus', () => input.style.borderColor = '#1976d2'); input.addEventListener('blur', () => input.style.borderColor = '#e0e0e0'); const button = this.createElement('button', { padding: '8px 16px', backgroundColor: '#1976d2', color: '#ffffff', border: 'none', borderRadius: '8px', fontSize: '14px', fontWeight: '500', cursor: 'pointer', transition: 'background-color 0.2s ease', flexGrow: '1' }, { textContent: '즐겨찾기 추가' }); button.addEventListener('mouseenter', () => button.style.backgroundColor = '#1565c0'); button.addEventListener('mouseleave', () => button.style.backgroundColor = '#1976d2'); button.addEventListener('click', (e) => { e.stopPropagation(); const digit = input.value.trim(); if (!/^[0-9]$/.test(digit)) { alert('0부터 9까지의 숫자를 입력해주세요.'); return; } Gallery.handleFavoriteKey(digit); input.value = ''; }); container.appendChild(input); container.appendChild(button); return container; }, createCloseButton(container) { const button = this.createElement('button', { display: 'block', width: '100%', padding: '10px', marginTop: '15px', backgroundColor: '#1976d2', color: '#ffffff', border: 'none', borderRadius: '10px', fontSize: '15px', fontWeight: '500', cursor: 'pointer', transition: 'background-color 0.2s ease' }, { textContent: 'Close' }); button.addEventListener('mouseenter', () => button.style.backgroundColor = '#1565c0'); button.addEventListener('mouseleave', () => button.style.backgroundColor = '#1976d2'); button.addEventListener('click', () => { container.style.opacity = '0'; setTimeout(() => document.body.removeChild(container), 200); }); return button; }, navigateToGallery(gallery) { const url = gallery.galleryType === 'board' ? `https://gall.dcinside.com/board/lists?id=${gallery.galleryId}` : `https://gall.dcinside.com/${gallery.galleryType}/board/lists?id=${gallery.galleryId}`; window.location.href = url; }, showAlert(message) { const alert = this.createElement('div', { position: 'fixed', top: '20px', left: '50%', transform: 'translateX(-50%)', backgroundColor: 'rgba(0, 0, 0, 0.8)', color: 'white', padding: '15px 20px', borderRadius: '8px', fontSize: '14px', zIndex: '10000', transition: 'opacity 0.3s ease' }, { textContent: message }); document.body.appendChild(alert); setTimeout(() => { alert.style.opacity = '0'; setTimeout(() => document.body.removeChild(alert), 300); }, 2000); } }; // Gallery Module const Gallery = { isMainPage() { const { href } = window.location; return href.includes('/lists') && href.includes('id='); }, getInfo() { if (!this.isMainPage()) return { galleryType: '', galleryId: '', galleryName: '' }; const { href } = window.location; const galleryType = href.includes('/person/') ? 'person' : href.includes('mgallery') ? 'mgallery' : href.includes('mini') ? 'mini' : 'board'; const galleryId = href.match(/id=([^&]+)/)?.[1] || ''; const nameEl = document.querySelector('div.fl.clear h2 a'); const galleryName = nameEl ? Array.from(nameEl.childNodes) .filter(node => node.nodeType === Node.TEXT_NODE) .map(node => node.textContent.trim()) .join('') || galleryId : galleryId; return { galleryType, galleryId, galleryName }; }, async handleFavoriteKey(key) { const favorites = await Storage.getFavorites(); const info = this.getInfo(); if (favorites[key]) { UI.navigateToGallery(favorites[key]); } else if (this.isMainPage()) { // Ensure galleryName is saved as 'name' for UI compatibility favorites[key] = { galleryType: info.galleryType, galleryId: info.galleryId, name: info.galleryName }; Storage.saveFavorites(favorites); UI.showAlert(`${info.galleryName}이(가) ${key}번에 등록되었습니다.`); const list = document.querySelector('ul[style*="max-height: 50vh"]'); if (list) await UI.updateFavoritesList(list); } else { alert('즐겨찾기 등록은 갤러리 메인 페이지에서만 가능합니다.'); } }, getPageInfo() { const { href } = window.location; const galleryType = href.includes('mgallery') ? 'mgallery' : href.includes('mini') ? 'mini' : href.includes('person') ? 'person' : 'board'; const galleryId = href.match(/id=([^&]+)/)?.[1] || ''; const currentPage = parseInt(href.match(/page=(\d+)/)?.[1] || '1', 10); const isRecommendMode = href.includes('exception_mode=recommend'); return { galleryType, galleryId, currentPage, isRecommendMode }; } }; // Post Navigation Module const Posts = { isValidPost(numCell, titleCell, subjectCell) { if (!numCell || !titleCell) return false; const row = numCell.closest('tr'); if (row?.classList.contains('block-disable') || row?.classList.contains('list_trend') || row?.style.display === 'none') return false; const numText = numCell.textContent.trim().replace(/\[\d+\]\s*|\[\+\d+\]\s*|\[\-\d+\]\s*/, ''); if (['AD', '공지', '설문', 'Notice'].includes(numText) || isNaN(numText)) return false; if (titleCell.querySelector('em.icon_notice')) return false; if (subjectCell?.textContent.trim().match(/AD|공지|설문|뉴스|고정|이슈/)) return false; return true; }, getValidPosts() { const rows = document.querySelectorAll('table.gall_list tbody tr'); const validPosts = []; let currentIndex = -1; rows.forEach((row, index) => { const numCell = row.querySelector('td.gall_num'); const titleCell = row.querySelector('td.gall_tit'); const subjectCell = row.querySelector('td.gall_subject'); if (!this.isValidPost(numCell, titleCell, subjectCell)) return; const link = titleCell.querySelector('a:first-child'); if (link) { validPosts.push({ row, link }); if (numCell.querySelector('.sp_img.crt_icon')) currentIndex = validPosts.length - 1; } }); return { validPosts, currentIndex }; }, addNumberLabels() { const tbody = document.querySelector('table.gall_list tbody'); if (!tbody || tbody.querySelector('.number-label')) return; const { validPosts } = this.getValidPosts(); validPosts.slice(0, 100).forEach((post, i) => { const numCell = post.row.querySelector('td.gall_num'); if (numCell.querySelector('.sp_img.crt_icon')) return; const label = UI.createElement('span', { color: '#ff6600', fontWeight: 'bold' }, { className: 'number-label', textContent: `[${i + 1}] ` }); numCell.prepend(label); }); }, navigate(number) { const { validPosts } = this.getValidPosts(); const index = parseInt(number, 10) - 1; if (index >= 0 && index < validPosts.length) { validPosts[index].link.click(); return true; } return false; } }; // Event Handlers const Events = { numberInput: { mode: false, buffer: '', timeout: null, display: null }, handleKeydown(event) { if (event.altKey && !event.ctrlKey && !event.shiftKey && !event.metaKey) { if (event.key >= '0' && event.key <= '9') { event.preventDefault(); // 단축키 활성화 여부 확인 Storage.getAltNumberEnabled().then(enabled => { if (enabled) { Gallery.handleFavoriteKey(event.key); } }); } else if (event.key === '`') { event.preventDefault(); const ui = document.querySelector('div[style*="position: fixed; top: 50%"]'); ui ? ui.remove() : UI.showFavorites(); } } else if (!event.ctrlKey && !event.altKey && !event.shiftKey && !event.metaKey) { this.handleNavigationKeys(event); } }, handleNavigationKeys(event) { const active = document.activeElement; if (active && ['TEXTAREA', 'INPUT'].includes(active.tagName) || active.isContentEditable) return; if (['`', '.'].includes(event.key)) { event.preventDefault(); this.toggleNumberInput(event.key); return; } if (this.numberInput.mode) { this.handleNumberInput(event); return; } if (event.key >= '0' && event.key <= '9') { const index = event.key === '0' ? 9 : parseInt(event.key, 10) - 1; const { validPosts } = Posts.getValidPosts(); if (index < validPosts.length) validPosts[index].link.click(); return; } this.handleShortcuts(event.key.toUpperCase()); }, toggleNumberInput(key) { if (this.numberInput.mode && this.numberInput.buffer) { Posts.navigate(this.numberInput.buffer); this.exitNumberInput(); } else { this.numberInput.mode = true; this.numberInput.buffer = ''; this.updateNumberDisplay('Post number: '); this.resetNumberTimeout(); } }, handleNumberInput(event) { event.preventDefault(); if (event.key >= '0' && event.key <= '9') { this.numberInput.buffer += event.key; this.updateNumberDisplay(`Post number: ${this.numberInput.buffer}`); this.resetNumberTimeout(); } else if (event.key === 'Enter' && this.numberInput.buffer) { Posts.navigate(this.numberInput.buffer); this.exitNumberInput(); } else if (event.key === 'Escape') { this.exitNumberInput(); } }, updateNumberDisplay(text) { if (!this.numberInput.display) { this.numberInput.display = UI.createElement('div', { position: 'fixed', top: '10px', right: '10px', backgroundColor: 'rgba(0,0,0,0.7)', color: 'white', padding: '10px 15px', borderRadius: '5px', fontSize: '16px', fontWeight: 'bold', zIndex: '9999' }); document.body.appendChild(this.numberInput.display); } this.numberInput.display.textContent = text; }, resetNumberTimeout() { clearTimeout(this.numberInput.timeout); this.numberInput.timeout = setTimeout(() => this.exitNumberInput(), 3000); }, exitNumberInput() { this.numberInput.mode = false; this.numberInput.buffer = ''; clearTimeout(this.numberInput.timeout); this.numberInput.timeout = null; if (this.numberInput.display) { this.numberInput.display.remove(); this.numberInput.display = null; } }, async handleShortcuts(key) { const { galleryType, galleryId, currentPage, isRecommendMode } = Gallery.getPageInfo(); const baseUrl = `${galleryType === 'board' ? '' : galleryType}/board/lists/?id=${galleryId}`; const recommendUrl = `${baseUrl}&exception_mode=recommend`; const navigate = url => document.readyState === 'complete' ? window.location.href = url : window.addEventListener('load', () => window.location.href = url, { once: true }); switch (key) { case 'W': document.querySelector('button#btn_write')?.click(); break; case 'C': document.querySelector('textarea[id^="memo_"]')?.focus(); break; case 'D': document.querySelector('button.btn_cmt_refresh')?.click(); break; case 'R': location.reload(); break; case 'Q': window.scrollTo(0, 0); break; case 'E': document.querySelector('table.gall_list')?.scrollIntoView({ block: 'start' }); break; case 'F': navigate(`https://gall.dcinside.com/${baseUrl}`); break; // 개념글 -> 일반 목록 case 'G': navigate(`https://gall.dcinside.com/${recommendUrl}`); break; // 일반 -> 개념글 case 'A': if (currentPage > 1) navigate(`https://gall.dcinside.com/${isRecommendMode ? recommendUrl : baseUrl}&page=${currentPage - 1}`); break; case 'S': navigate(`https://gall.dcinside.com/${isRecommendMode ? recommendUrl : baseUrl}&page=${currentPage + 1}`); break; case 'Z': await this.navigatePrevPost(galleryType, galleryId, currentPage); break; case 'X': await this.navigateNextPost(galleryType, galleryId, currentPage); break; } }, async navigatePrevPost(galleryType, galleryId, currentPage) { const crtIcon = document.querySelector('td.gall_num .sp_img.crt_icon'); if (!crtIcon) return; const currentPostNo = parseInt(window.location.href.match(/no=(\d+)/)?.[1] || NaN, 10); if (isNaN(currentPostNo)) return; let row = crtIcon.closest('tr')?.previousElementSibling; while (row && !Posts.isValidPost(row.querySelector('td.gall_num'), row.querySelector('td.gall_tit'), row.querySelector('td.gall_subject'))) { row = row.previousElementSibling; } if (row) { row.querySelector('td.gall_tit a:first-child')?.click(); } else if (currentPage > 1) { const prevUrl = `https://gall.dcinside.com/${galleryType === 'board' ? '' : galleryType}/board/lists/?id=${galleryId}&page=${currentPage - 1}`; const doc = await this.fetchPage(prevUrl); const lastValidLink = this.getLastValidPostLink(doc); if (lastValidLink) window.location.href = lastValidLink; } else { const doc = await this.fetchPage(window.location.href); const newPosts = this.getNewerPosts(doc, currentPostNo); if (newPosts.length) { window.location.href = newPosts.find(p => p.num === currentPostNo - 1)?.link || newPosts[0].link; } else { UI.showAlert('첫 게시글입니다.'); } } }, async navigateNextPost(galleryType, galleryId, currentPage) { const nextLink = document.querySelector('a.next') || this.getNextValidLink(); if (nextLink) { window.location.href = nextLink.href; } else { const currentPostNo = parseInt(window.location.href.match(/no=(\d+)/)?.[1] || NaN, 10); if (isNaN(currentPostNo)) return; const nextUrl = `https://gall.dcinside.com/${galleryType === 'board' ? '' : galleryType}/board/lists/?id=${galleryId}&page=${currentPage + 1}`; const doc = await this.fetchPage(nextUrl); const nextPosts = this.getValidPostsFromDoc(doc).filter(p => p.num < currentPostNo); if (nextPosts.length) window.location.href = nextPosts[0].link; } }, getNextValidLink() { const crtIcon = document.querySelector('td.gall_num .sp_img.crt_icon'); if (!crtIcon) return null; let row = crtIcon.closest('tr')?.nextElementSibling; while (row && !Posts.isValidPost(row.querySelector('td.gall_num'), row.querySelector('td.gall_tit'), row.querySelector('td.gall_subject'))) { row = row.nextElementSibling; } return row?.querySelector('td.gall_tit a:first-child'); }, async fetchPage(url) { const response = await fetch(url); const text = await response.text(); return new DOMParser().parseFromString(text, 'text/html'); }, getLastValidPostLink(doc) { const rows = Array.from(doc.querySelectorAll('table.gall_list tbody tr')); for (let i = rows.length - 1; i >= 0; i--) { const row = rows[i]; if (Posts.isValidPost(row.querySelector('td.gall_num'), row.querySelector('td.gall_tit'), row.querySelector('td.gall_subject'))) { return row.querySelector('td.gall_tit a:first-child')?.href; } } return null; }, getNewerPosts(doc, currentNo) { const posts = this.getValidPostsFromDoc(doc); return posts.filter(p => p.num > currentNo).sort((a, b) => a.num - b.num); }, getValidPostsFromDoc(doc) { return Array.from(doc.querySelectorAll('table.gall_list tbody tr')) .filter(row => Posts.isValidPost(row.querySelector('td.gall_num'), row.querySelector('td.gall_tit'), row.querySelector('td.gall_subject'))) .map(row => { const num = parseInt(row.querySelector('td.gall_num').textContent.trim().replace(/\[\d+\]\s*/, ''), 10); return { num, link: row.querySelector('td.gall_tit a:first-child')?.href }; }); } }; // Initialization function init() { document.addEventListener('keydown', e => Events.handleKeydown(e)); document.readyState === 'complete' ? Posts.addNumberLabels() : window.addEventListener('load', Posts.addNumberLabels, { once: true }); const observer = new MutationObserver(() => setTimeout(Posts.addNumberLabels, 100)); const tbody = document.querySelector('table.gall_list tbody'); if (tbody) observer.observe(tbody, { childList: true, subtree: true, characterData: true }); const bodyObserver = new MutationObserver(() => { if (!document.querySelector('.number-label')) Posts.addNumberLabels(); }); bodyObserver.observe(document.body, { childList: true, subtree: true }); } init(); })();