// ==UserScript== // @name Arca.RevampedCommentSection // @namespace Arca.RevampedCommentSection // @author 자바 // @description 아카라이브 댓글 목록 개선 프로젝트 // @version 1.0.3 // @include https://arca.live/* // @include https://*.arca.live/* // @run-at document-ready // @grant GM_addStyle // ==/UserScript== GM_addStyle(/*css*/` .comment-wrapper.fold > .comment-wrapper { display: none; } .comment-wrapper.foldable > .comment-item .info-row { cursor: pointer; } .comment-wrapper.foldable > .comment-item .folder { font-family: monospace; font-weight: bold; } .comment-wrapper.foldable > .comment-item .folder:after { content: "[-]"; } .comment-wrapper.fold > .comment-item .folder:after { content: "[+]"; } `) /** * 댓글 요소에서 삭제 버튼을 찾습니다 * @param {HTMLElement} comment * @returns {HTMLElement} */ function findDeleteAnchor (comment) { for (let anchor of comment.querySelectorAll('a')) { if (anchor.textContent.trim() === '삭제') { return anchor } } } /** * 타래 삭제 버튼을 눌렀을 때 실행될 메소드 * @this {HTMLElement} * @param {MouseEvent} e */ function onDeleteClick (e) { e.preventDefault() // 삭제할 댓글 아이디 가져오기 const parent = this.closest('.comment-wrapper') const comments = [ parent, ...parent.querySelectorAll('.comment-wrapper') ] // 정말 삭제할건지 물어보기 if (!confirm(`정말 해당 댓글과 답글 ${comments.length - 1}개를 제거하시겠습니까?`)) { return } const promises = [] const errors = [] for (let comment of comments) { const anchor = findDeleteAnchor(comment) if (anchor) { promises.push( fetch(anchor.href, { method: 'POST', headers: {'Content-type': 'application/x-www-form-urlencoded'}, body: `_csrf=${document.querySelector('input[name=_csrf]').value}` }).catch(e => errors.push(e)) ) } } return Promise.all(promises) .finally(() => { if (errors.length) { const error = errors.map((e, idx) => ` ${idx}: ${e.message}`).join('\n') alert(`댓글 삭제 중 ${errors.length}개의 오류가 발생했습니다\n${error}`) } location.reload() }) } /** * 타래 접기 버튼을 눌렀을 때 실행될 메소드 * @this {HTMLElement} * @param {MouseEvent} e */ function onFolderClick (e) { if (!e.target.matches('a, button')) { const command = this.closest('.comment-wrapper') command.classList.toggle('fold') } } const deleteBtnTemplate = document.createElement('a') deleteBtnTemplate.href = '#' deleteBtnTemplate.innerHTML = ` 전체삭제 ` const folderBtnTemplate = document.createElement('span') folderBtnTemplate.classList.add('folder') // 답글이 있는 댓글만 모두 불러오기 for (let comment of document.querySelectorAll('.comment-wrapper')) { if (!comment.querySelector(':scope > .comment-wrapper')) { continue } // 삭제 권한이 있다면 전체 삭제 버튼 추가하기 const anchor = findDeleteAnchor(comment) if (anchor) { const button = deleteBtnTemplate.cloneNode(true) button.addEventListener('click', onDeleteClick) anchor.parentElement.insertBefore(button, anchor) anchor.parentElement.insertBefore( document.createTextNode('\u00A0|\u00A0'), anchor) } // 댓글 접는 버튼 추가하기 const folder = folderBtnTemplate.cloneNode(true) const info = comment.querySelector('.info-row') info.prepend(folder) info.addEventListener('click', onFolderClick) comment.classList.add('foldable') // 3개 이상 댓글이 있다면 접은 상태로 만들기 if (comment.querySelectorAll('.comment-wrapper').length > 3) { folder.click() } } // 선택된 댓글이 있다면 모두 펼치기 if (location.hash) { for (let comment of document.querySelectorAll(`${location.hash}, ${location.hash} .comment-wrapper`)) { comment.classList.remove('fold') } }