// ==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 = `
<span class="icon ion-trash-b"></span>
전체삭제
`
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')
}
}