Linux.po

对 linux.do 的增强脚本

Verzia zo dňa 25.01.2025. Pozri najnovšiu verziu.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey, Greasemonkey alebo Violentmonkey.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey, % alebo Violentmonkey.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey, % alebo Violentmonkey.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey alebo Userscripts.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie, ako napríklad Tampermonkey.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie správcu používateľských skriptov.

(Už mám správcu používateľských skriptov, nechajte ma ho nainštalovať!)

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

(Už mám správcu používateľských štýlov, nechajte ma ho nainštalovať!)

// ==UserScript==
// @name         Linux.po
// @namespace    http://tampermonkey.net/
// @version      0.1.7
// @description  对 linux.do 的增强脚本
// @author       PRO-2684
// @match        https://linux.do/*
// @run-at       document-start
// @icon         data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAABOxJREFUWEfNV0tslFUU/s7/nmmHmWE6NRUqKZXER9OIuNFA3Ei6w6CUWiPGjRiaSLtoWZi4MnFBu2gxgYgLjRgqtNHIrsGFBqIbAdPUR4LQYIXGTseZ6WPmfx+9f+dvphRIDWPxzm7+e8/3nXPPOfc7hLssZiYAKhHZ4RZm3gtgD4Am27Z3EpEsvjGzp2naRQCTAM4R0ZcVZzQADhHxnaAEyKrFzALYKRtPADjuOE7n2NgYLly4iPHxcVy6dEVAl88SduzYjtbWVuzatRNtbW1QVXUYQBcR5ct2lm1WAq4iwMxa6DUzD7qu2z04OIRjxz7A1NQNF4BfUxOTa2pqZKKl48yMxcVFb3Fx3gMgNTZuUQ4ffhs9Pd1QFGXoHxI95X3LtkMSKwgws05EFjNvBDAxMjLacOhQF7LZjF1XV6/GYjESoJ7nwff9FZGTJAmyLAdk5ufneXZ2xkml0tqJE8fR3r5vGkALEf0VYqwiEHpeLBY3RyKRqb6+IxgY6DfT6YeMWCwGx3EC42tZgqSqqoIIMpk/zd7ePqO//yhKpVJjNBr9ozLKQQTCOy97nu3oeAVnz56xmpqa9Tt5uxYSYk8YlcnJa9b+/R36mTOfi79T5UgEOUEi28MMZeZbfX1HGgYG+q3m5m26ZVlrxbrnPl3Xce3aVau3t0/v7z86TUQPlx0nQSBIDJFwo6Oj3e3t7WZz8zbDtperryokNE0TJMyRkVFj376Xg8QMsMNSy2Qyufr6ehE4RCKRVUl2vyzEdZRKJVFEmJmZQTqdTooSDQjYtn365s2bnRMTE3Y8Htc8z0e5wu4Xd/m8yF9ZllAoFOyWlhZt06ZNw5qmvRoQyOVybBgGDMMQaX7H5lQ1JgCbpkmmaSKZTBLlcrm9RPQFM7ue5ylVBLqrKVmWXSJSmPklKhQKHzPzG+ImAIi+vR4rwCKiTyifz38D4HkAoo0Gj8s6rBDrW0FA9Pf1Ar7dN08QWFt//Y/C8r8g8MCv4MEm4QMvw7ARAeyC16cRgWQXKDcikdz5fI4hGWBlfVoxuSbBN5FIJJeenPy8e5rMG53y3GWb1aQGXim3qlaBJIGcvO1t2K6xsWU4EVPKjxFzgnKZXHysfukpkqoGudKQ6H8ACm0z4GRaPEV5+olZe5LInmUejPz2WXf0+wMmElEDfnUFCSQNyBfN4rOnjNKjrw3VEfUI7BWSLOvxrdrLbzVov560EI/q8KsjySDpQKFo2Y8d1Bee/nA6JVdIsrIiCgRiocAb3SiyG77bDeX3ry3EI7ooDrCI3b+VCQyIwYkUoFCy3Ede0OeeOw+liFQ8HsjzJVEaXlKoDbNF3gwVU7U/HoT2y0cmamFAiQK+GJTWmpwSIKmAWwQWYNqPv2ksPHUScNCYitJqWR6SuMqsbyOygkjUYsK4fqohOv46YMJGBCpkgwKtJiJye6WQtOSx0F6eySjBgQGt2PopzK0HppUFtAjPQ4wQ856jWZZ5UFrwuo3J92Bcfx9YdFwQfEEl+FUuDx4ceGBIqFEVc+s7MJvehV8rD6XWOppVXMfyIJljTvjAcWnB7VQzX0HNnoc8dwly4YeV+PFn4G3YASe1G076Rfi1yrAEdIlSq8yz2wv8rpklBpafAVWUaHholnkv+dgjWWiC5+6sEDIeZOWir2OSJZyrqxjPRak9cY/x/G9pa4SNlgBs7AAAAABJRU5ErkJggg==
// @license      gpl-3.0
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_deleteValue
// @grant        GM_registerMenuCommand
// @grant        GM_unregisterMenuCommand
// @grant        GM_addValueChangeListener
// @require      https://github.com/PRO-2684/GM_config/releases/download/v1.2.1/config.min.js#md5=525526b8f0b6b8606cedf08c651163c2
// ==/UserScript==

(function () {
    'use strict';
    const { name, version } = GM_info.script;
    const idPrefix = "linux-po-";
    const configDesc = {
        $default: {
            autoClose: false,
        },
        appearance: {
            name: "🎨 外观",
            title: "外观",
            type: "folder",
            items: {
                sidebarManager: {
                    name: "⬅️ 侧栏管理",
                    title: "允许你隐藏侧栏中的各个部分",
                    type: "folder",
                    items: {
                        $default: {
                            value: false,
                            input: "current",
                            processor: "not",
                            title: (prop, value, desc) => desc.name,
                            formatter: (prop, value, desc) => `${desc.name}: ${value ? "🫥" : "👀"}`,
                        },
                        customCategories: { name: "自定义板块" },
                        externalLinks: { name: "外部链接" },
                        categories: { name: "类别" },
                        tags: { name: "标签" },
                        messages: { name: "消息" },
                        channels: { name: "频道" },
                        directMessages: { name: "直接消息" },
                        chat: { name: "聊天" },
                        bottomMenu: { name: "底部菜单" },
                    },
                },
                postManager: {
                    name: "📝 帖子管理",
                    title: "允许你隐藏帖子的各个部分",
                    type: "folder",
                    items: {
                        $default: {
                            value: false,
                            input: "current",
                            processor: "not",
                            title: (prop, value, desc) => desc.name,
                            formatter: (prop, value, desc) => `${desc.name}: ${value ? "🫥" : "👀"}`,
                        },
                        secondaryName: { name: "次要名称" },
                        userTitle: { name: "头衔" },
                        userStatus: { name: "自定义状态" },
                        posterIcon: {
                            name: "🍰",
                            title: "加入社区纪念日以及生日图标 (discourse-cakeday)",
                        },
                        flair: {
                            name: "资质",
                            title: "展示在头像右下角",
                        },
                    },
                },
            },
        },
        accessibility: {
            name: "♿ 辅助功能",
            title: "辅助功能",
            type: "folder",
            items: {
                largerClickArea: {
                    name: "👆 增大点击区域",
                    title: "增大帖子列表中各帖子的可点击区域 (仅支持左键)",
                    type: "bool",
                    value: true,
                },
                showPostsFloor: {
                    name: "🔢 显示楼层",
                    title: "在帖子中显示楼层",
                    type: "bool",
                    value: false,
                },
                atBeforeUsername: {
                    name: "👤 @用户名",
                    title: "在用户名前添加 @ 符号",
                    type: "bool",
                    value: false,
                },
            },
        },
    };
    const config = new GM_config(configDesc);

    // Helper function for css
    function injectCSS(id, css) {
        const style = document.head.appendChild(document.createElement("style"));
        style.id = idPrefix + id;
        style.textContent = css;
        return style;
    }
    function cssHelper(id, enable) {
        const current = document.getElementById(idPrefix + id);
        if (current) {
            current.disabled = !enable;
        } else if (enable) {
            injectCSS(id, dynamicStyles[id]);
        }
    }
    /**
     * Generates CSS for hiding given sidebar section.
     */
    function hideSidebarSection(section) {
        return `#d-sidebar > .sidebar-sections div.sidebar-section[data-section-name="${section}"] { display: none; }`;
    }
    /**
     * Generates CSS for hiding given post section.
     */
    function hidePostSection(section) {
        return `.post-stream > .topic-post > article .names > .${section} { display: none; }`;
    }

    // Dynamic styles
    const dynamicStyles = {
        "appearance.sidebarManager.customCategories": hideSidebarSection("community"),
        "appearance.sidebarManager.externalLinks": hideSidebarSection("外部链接"),
        "appearance.sidebarManager.categories": hideSidebarSection("categories"),
        "appearance.sidebarManager.tags": hideSidebarSection("tags"),
        "appearance.sidebarManager.messages": hideSidebarSection("messages"),
        "appearance.sidebarManager.channels": hideSidebarSection("chat-channels"),
        "appearance.sidebarManager.directMessages": hideSidebarSection("chat-dms"),
        "appearance.sidebarManager.chat": "#d-sidebar > button[data-key='chat'] { display: none; }",
        "appearance.sidebarManager.bottomMenu": "#d-sidebar > div.sidebar-footer-wrapper { display: none; }",
        "appearance.postManager.secondaryName": hidePostSection("second"),
        "appearance.postManager.userTitle": hidePostSection("user-title"),
        "appearance.postManager.userStatus": hidePostSection("user-status-message-wrap"),
        "appearance.postManager.posterIcon": hidePostSection("poster-icon"),
        "appearance.postManager.flair": ".topic-avatar > .post-avatar > .avatar-flair { display: none; }",
        "accessibility.largerClickArea": ".topic-list-item > .main-link { cursor: pointer; }",
        "accessibility.showPostsFloor": `.post-stream > .topic-post > article[id^='post_'] {
            &::after {
                content: attr(id) '#'; color: var(--primary-med-or-secondary-med);
                position: absolute; right: 0; top: calc(0.8em + 1px);
                text-indent: -2.4em; overflow: hidden; /* Dirty trick to hide leading "post_" */
            }
            .embedded-posts > .reply .post-link-arrow > a.post-info::after {
                content: attr(href) '#'; display: inline-flex;
                text-indent: -7.4em; overflow: hidden; /* Dirty trick to hide leading "/t/topic/\\d{6}/" */
            }
        }
        .timeline-container > .topic-timeline > .timeline-scrollarea-wrapper > .timeline-date-wrapper > .now-date[href^='/t/topic/']::after {
            content: attr(href) '#'; display: inline-flex; margin-left: 0.2em;
            text-indent: -7.4em; overflow: hidden; /* Dirty trick to hide leading "/t/topic/\\d{6}/" */
        }`,
        "accessibility.atBeforeUsername": `
            span.username > a::before { content: "@"; }
            div.username::before { content: "@"; }
        `,
    };
    for (const prop in dynamicStyles) {
        cssHelper(prop, config.get(prop));
    }

    // Accessibility
    // Larger click area
    let largerClickAreaEnabled = false;
    /**
     * Handles the click event when larger click area is enabled.
     * @param {MouseEvent} e
     */
    function largerClickAreaHandler(e) {
        if (e.defaultPrevented || !e.isTrusted) return;
        const mainLink = e.target.closest(".topic-list-item > .main-link");
        if (mainLink) {
            e.preventDefault();
            const title = mainLink.querySelector(".title.raw-link.raw-topic-link");
            title?.click();
        }
    }
    /**
     * Enables or disables the larger click area feature.
     * @param {boolean} enable
     */
    function largerClickArea(enable) {
        if (enable && !largerClickAreaEnabled) {
            document.body.addEventListener("click", largerClickAreaHandler);
            largerClickAreaEnabled = true;
        } else if (!enable && largerClickAreaEnabled) {
            document.body.removeEventListener("click", largerClickAreaHandler);
            largerClickAreaEnabled = false;
        }
    }

    // Callbacks
    const callbacks = {
        "accessibility.largerClickArea": largerClickArea,
    };
    for (const [prop, callback] of Object.entries(callbacks)) {
        callback(config.get(prop));
    }
    config.addEventListener("set", e => {
        if (e.detail.prop in dynamicStyles) {
            cssHelper(e.detail.prop, e.detail.after);
        }
        if (e.detail.prop in callbacks) {
            callbacks[e.detail.prop](e.detail.after);
        }
    });

    // https://linux.do/emojis.json
})();