Greasy Fork

Greasy Fork is available in English.

游戏社区(TapTap)列表页贴子预览

TapTap游戏社区列表页贴子卡片新增预览按钮,可在列表页直接预览贴子内容。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name            游戏社区(TapTap)列表页贴子预览
// @namespace       游戏社区(TapTap)列表页贴子预览
// @license         GPL-3.0 License
// @version         1.3.3
// @description     TapTap游戏社区列表页贴子卡片新增预览按钮,可在列表页直接预览贴子内容。
// @author          QIAN
// @match           *://www.taptap.cn/app/*/topic*
// @grant           GM_addStyle
// @grant           GM_info
// @require         https://scriptcat.org/lib/513/2.0.1/ElementGetter.js?#sha256=V0EUYIfbOrr63nT8+W7BP1xEmWcumTLWu2PXFJHh5dg=
// @supportURL      https://github.com/QIUZAIYOU/Taptap-PostPreview
// @homepageURL     https://github.com/QIUZAIYOU/Taptap-PostPreview
// @icon            https://assets.tapimg.com/cupid-apps/web-app/favicon.2.ico
// ==/UserScript==
// ?#sha256=V0EUYIfbOrr63nT8+W7BP1xEmWcumTLWu2PXFJHh5dg=
(function () {
    'use strict';
    const selector = {
        tap: '#__nuxt',
        momentCardFooter: '.moment-card__footer',
        previewWrapper: '#previewWrapper',
        previewIframe: '#previewIframe',
        previewIframeMask: '#previewIframeMask',
        previewContentHeader: 'header',
        previewContentMain: 'main',
        previewButton: '.previewButton',
        voteButton: '.vote-button.moment-card__footer-button',
    }
    const styles = {
        PreviewWrapperStyle: '#previewWrapper{position:fixed;top:50%;left:50%;overflow:hidden;box-sizing:border-box;width:600px;height:97vh;border:2px solid #00d9c5;border-radius:10px;background:#191919;transform:translate(-50%,-50%)}#previewWrapper::backdrop{backdrop-filter:blur(3px)}#previewIframe{width:100%;height:100%;border:none;background:#191919}#previewIframeMask{position:absolute;display:flex;background:#191919;inset:0;align-items:center;justify-content:center}.previewButton{cursor: pointer}',
        PreviewIframeStyle: 'body{background:#191919!important}header{display:none!important}main{margin-left:0!important}'
    }
    const utils = {
        /**
         * 休眠
         * @param {Number} 时长
         * @returns
         */
        sleep(times) {
            return new Promise(resolve => setTimeout(resolve, times))
        },
        logger: {
            info(content) {
                console.info('%cTapTap预览', 'color:white;background:#006aff;padding:2px;border-radius:2px', content);
            },
            warn(content) {
                console.warn('%cTapTap贴子预览', 'color:white;background:#ff6d00;padding:2px;border-radius:2px', content);
            },
            error(content) {
                console.error('%cTapTap贴子预览', 'color:white;background:#f33;padding:2px;border-radius:2px', content);
            },
            debug(content) {
                console.info('%cTapTap贴子预览(调试)', 'color:white;background:#cc00ff;padding:2px;border-radius:2px', content);
            },
        },
        createElementAndInsert(HtmlString, target, method) {
            const element = elmGetter.create(HtmlString, target)
            target[method](element)
            return element
        },
        insertStyleToDocument(id, css) {
            const styleElement = GM_addStyle(css)
            styleElement.id = id
        },
        htmlStringToDom(htmlString) {
            const tempDiv = document.createElement('div');
            tempDiv.innerHTML = htmlString.trim();
            return tempDiv.firstChild;
        },
        isAsyncFunction(targetFunction) {
            return targetFunction.constructor.name === 'AsyncFunction'
        },
        reloadCurrentTab(...args) {
            if (args && args[0] === true) {
                location.reload()
            } else if (vals.auto_reload()) location.reload()
        },
        executeFunctionsSequentially(functionsArray) {
            if (functionsArray.length > 0) {
                const currentFunction = functionsArray.shift()
                if (utils.isAsyncFunction(currentFunction)) {
                    currentFunction().then(result => {
                        if (result) {
                            const { message, callback } = result
                            if (message) utils.logger.info(message)
                            if (callback && Array.isArray(callback)) utils.executeFunctionsSequentially(callback)
                        }
                        utils.executeFunctionsSequentially(functionsArray)
                    }).catch(error => {
                        utils.logger.error(error)
                        utils.reloadCurrentTab()
                    })
                } else {
                    const result = currentFunction()
                    if (result) {
                        const { message } = result
                        if (message) utils.logger.info(message)
                    }
                }
            }
        },
        checkElementExistence(elementsArray) {
            if (Array.isArray(elementsArray)) {
                return elementsArray.map(element => Boolean(element))
            } else {
                return [Boolean(elementsArray)]
            }
        },
        async getElementAndCheckExistence(selectors, ...args) {
            let delay = 7000, debug = false
            if (args.length === 1) {
                const type = typeof args[0]
                if (type === 'number') delay = args[0]
                if (type === 'boolean') debug = args[0]
            }
            if (args.length === 2) {
                delay = args[0]
                debug = args[1]
            }
            const result = await elmGetter.get(selectors, delay)
            if (debug) utils.logger.debug(utils.checkElementExistence(result))
            return result
        },
    }
    const modules = {
        async insertPreviewElementToDocument() {
            const existingPreviewWrapper = await utils.getElementAndCheckExistence(selector.previewWrapper);
            if (existingPreviewWrapper) existingPreviewWrapper.remove();

            const previewElementHtml = `
                <div id="previewWrapper" popover>
                    <div id="perViewFloat">
                        <div id="previewClose"></div>
                        <div id="previewEnterPost"><a href=""></a></div>
                    </div>
                    <div id="previewIframeMask">
                        <div class="loading-dots__wrapper" type="dots" loading="true">
                            <span class="loading-dots__dot" style="font-size: 6px;"></span>
                            <span class="loading-dots__dot" style="font-size: 6px;"></span>
                            <span class="loading-dots__dot" style="font-size: 6px;"></span>
                        </div>
                    </div>
                    <iframe id="previewIframe" title="previewIframe"></iframe>
                </div>
            `;
            const previewWrapper = utils.createElementAndInsert(previewElementHtml, document.body, 'append');
            const previewIframe = previewWrapper.querySelector(selector.previewIframe);
            const previewIframeMask = previewWrapper.querySelector(selector.previewIframeMask);
            const previewIframeWindow = previewIframe.contentWindow;

            previewWrapper.addEventListener('toggle', (event) => {
                if (event.newState === 'closed') {
                    previewIframe.src = '';
                    previewIframeMask.style.display = 'flex';
                    document.querySelector(selector.tap).style.pointerEvents = 'auto';
                }
            });

            previewIframe.addEventListener('load', () => {
                const previewContentHeader = previewIframeWindow.document.querySelector(selector.previewContentHeader);
                const previewContentMain = previewIframeWindow.document.querySelector(selector.previewContentMain);
                if (previewContentHeader && previewContentMain) {
                    previewContentHeader.style.display = 'none';
                    previewContentMain.style.marginLeft = '0';
                    previewIframeMask.style.display = 'none';
                }
            });
        },
        async insertPreviewButtonToMomentCard() {
            const previewButtonHtml = `
                <div class="moment-card__footer-button">
                    <span class="previewButton icon-button flex-center--y" data-booth-item="" data-track-prevent="click" data-booth-level="1">
                        <svg xmlns="http://www.w3.org/2000/svg" width="26" height="26" class="icon" viewBox="0 0 1024 1024">
                            <path fill="#d6d6d6" d="M935.253 494.933C849.067 294.827 686.933 170.667 512 170.667S174.933 294.827 88.747 494.933a42.667 42.667 0 0 0 0 34.134C174.933 729.173 337.067 853.333 512 853.333s337.067-124.16 423.253-324.266a42.667 42.667 0 0 0 0-34.134zM512 768c-135.253 0-263.253-97.707-337.067-256C248.747 353.707 376.747 256 512 256s263.253 97.707 337.067 256C775.253 670.293 647.253 768 512 768zm0-426.667A170.667 170.667 0 1 0 682.667 512 170.667 170.667 0 0 0 512 341.333zm0 256A85.333 85.333 0 1 1 597.333 512 85.333 85.333 0 0 1 512 597.333z"/>
                        </svg>
                    </span>
                </div>
                `;

            const [previewWrapper, previewIframe, voteButton] = await utils.getElementAndCheckExistence([selector.previewWrapper, selector.previewIframe, selector.voteButton]);
            const versionData = voteButton.dataset;
            await elmGetter.each(selector.momentCardFooter, async (momentCardFooter) => {
                const existingPreviewButton = momentCardFooter.querySelector(selector.previewButton);
                if (existingPreviewButton) existingPreviewButton.remove();
                const previewButton = utils.createElementAndInsert(previewButtonHtml, momentCardFooter, 'append');
                for (let version in versionData) {
                    if (versionData.hasOwnProperty(version)) {
                        previewButton.setAttribute(`data-${version}`, versionData[version]);
                    }
                }
                const tapElement = document.querySelector(selector.tap);
                const momentId = momentCardFooter.parentElement.dataset.eventObjKey.split(":")[1]
                const momentLink = `https://www.taptap.cn/moment/${momentId}`;
                previewButton.addEventListener('click', (event) => {
                    event.preventDefault();
                    event.stopPropagation();
                    previewWrapper.showPopover();
                    previewIframe.src = momentLink;
                    tapElement.style.pointerEvents = 'none';
                });
            });
        },
        thePrepFunction() {
            utils.insertStyleToDocument('PreviewWrapperStyle', styles.PreviewWrapperStyle)
        },
        theMainFunction() {
            modules.thePrepFunction()
            utils.logger.info(`脚本版本|${GM_info.script.version}`)
            utils.logger.info('当前标签|已激活|开始应用配置')
            const functionsArray = [
                modules.insertPreviewElementToDocument,
                modules.insertPreviewButtonToMomentCard
            ]
            utils.executeFunctionsSequentially(functionsArray)
        }
    }
    modules.theMainFunction()
})();