Greasy Fork

Greasy Fork is available in English.

水源显示回复可见

可在水源论坛显示“仅回复可见”的回帖内容(UI极其简陋的初版本)

当前为 2021-10-01 提交的版本,查看 最新版本

// ==UserScript==
// @name         水源显示回复可见
// @namespace    CCCC_David
// @version      0.1.0
// @description  可在水源论坛显示“仅回复可见”的回帖内容(UI极其简陋的初版本)
// @author       CCCC_David
// @match        https://shuiyuan.sjtu.edu.cn/*
// @grant        none
// ==/UserScript==

(async () => {
    'use strict';

    const supportsTrustedTypes = window.trustedTypes?.createPolicy;
    const allowedPolicy = supportsTrustedTypes ? trustedTypes.createPolicy('allowedPolicy', { createHTML: x => x }) : null;
    const createTrustedHTML = (html) => supportsTrustedTypes ? allowedPolicy.createHTML(html) : html;

    const topicId = parseInt(window.location.pathname.match(/^\/t\/topic\/(\d+)$/)?.[1], 10);
    if (Number.isNaN(topicId)) { // Not a topic page.
        return;
    }

    const topicInfoResponse = await fetch(`/t/${topicId}.json`, {
        method: 'GET',
        headers: {
            'Discourse-Present': 'true',
            'Discourse-Logged-In': 'true',
            'X-Requested-With': 'XMLHttpRequest',
            'X-CSRF-Token': document.querySelector('meta[name=csrf-token]').getAttribute('content'),
        },
        mode: 'same-origin',
        credentials: 'include',
    });
    const topicInfo = await topicInfoResponse.json();
    const isPrivateReply = topicInfo.private_replies;
    const numReplies = topicInfo.posts_count;

    if (!isPrivateReply) { // Not a private reply page.
        return;
    }

    const fetchReply = async (replyId) => {
        const replyURL = `/t/topic/${topicId}/${replyId}`;
        const response = await fetch(`/onebox?url=${encodeURIComponent(replyURL)}`, {
            method: 'GET',
            headers: {
                'Discourse-Present': 'true',
                'Discourse-Logged-In': 'true',
                'X-Requested-With': 'XMLHttpRequest',
                'X-CSRF-Token': document.querySelector('meta[name=csrf-token]').getAttribute('content'),
            },
            mode: 'same-origin',
            credentials: 'include',
        });
        return response.text();
    };

    const addViewer = (parent) => {
        if (document.getElementById('show-private-reply-id')) {
            return;
        }
        const viewerContainer = document.createElement('div');
        viewerContainer.innerHTML = createTrustedHTML(`
            <div id="show-private-reply-content"></div>
            <span id="show-private-reply-id">0</span>&nbsp;
            <button id="show-private-reply-inc-id" class="btn">+</button>&nbsp;
            <button id="show-private-reply-dec-id" class="btn">-</button>
        `);
        parent.appendChild(viewerContainer);
        const replyIdElement = document.getElementById('show-private-reply-id');
        const replyContentElement = document.getElementById('show-private-reply-content');
        document.getElementById('show-private-reply-inc-id').addEventListener('click', async () => {
            let replyId = parseInt(replyIdElement.textContent, 10);
            if (replyId < numReplies) {
                replyId += 1;
            }
            replyIdElement.textContent = replyId.toString();
            const reply = await fetchReply(replyId);
            replyContentElement.innerHTML = createTrustedHTML(reply);
        });
        document.getElementById('show-private-reply-dec-id').addEventListener('click', async () => {
            let replyId = parseInt(replyIdElement.textContent, 10);
            if (replyId > 1) {
                replyId -= 1;
            }
            replyIdElement.textContent = replyId.toString();
            const reply = await fetchReply(replyId);
            replyContentElement.innerHTML = createTrustedHTML(reply);
        });
    };

    const observer = new MutationObserver((mutationsList) => {
        for (const mutation of mutationsList) {
            if (mutation.type === 'childList') {
                for (const el of mutation.addedNodes) {
                    if (el.classList?.contains('post-stream')) {
                        addViewer(el);
                    }
                }
            } else if (mutation.type === 'attributes') {
                if (!mutation.oldValue?.match(/(?:^|\s)post-stream(?:\s|$)/) &&
                    mutation.target.classList?.contains('post-stream')) {
                    addViewer(mutation.target);
                }
            }
        }
    });

    observer.observe(document.body, {
        subtree: true,
        childList: true,
        attributeFilter: ['class'],
        attributeOldValue: true,
    });

    const postStreamContainer = document.getElementsByClassName('post-stream')[0];
    if (postStreamContainer) {
        addViewer(postStreamContainer);
    }
})();