Custom Title

【使用前先看介绍/有问题可反馈】自定义标题 (Custom Title): 支持自定义标题

目前為 2022-08-05 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         Custom Title
// @namespace    http://tampermonkey.net/
// @version      1.0.0
// @description  【使用前先看介绍/有问题可反馈】自定义标题 (Custom Title): 支持自定义标题
// @author       cc
// @include      *
// @license      MIT
// @grant        GM_setValue
// @grant        GM_getValue
// ==/UserScript==

(function() {
    'use strict';

    // storage
    let storage;
    const _VERSION_ = '1.0.0';
    const focusKey = 'KeyT';            // T
    const clearKey = 'Digit1';          // 1
    const expiredTime = 30 * 86400 * 1000;      // 30 day
    const interval = 86400 * 1000;      // 1 day

    function notify(msg) {
        console.log(`%c${msg}`, 'background-color: yellow; color: black;');
    }

    function getDefaultStorage() {
        return {
            lastUpdateTime: new Date().getTime(),
            titleInfoDict: {},
        }
    }

    function loadStorage() {
        storage = GM_getValue('storage');
        if (!storage) {
            storage = getDefaultStorage();
            GM_setValue('storage', storage);
            notify('[Custom Title]: storage initialized');
            console.log(storage);
        } else {
            notify('[Custom Title]: storage down here');
            console.log(storage);
            clearExpiredTitle();
        }
    }

    function applyTitle() {
        let titleInfo = storage.titleInfoDict[location.href];
        if (titleInfo && document.title !== titleInfo.newTitle) {
            document.title = titleInfo.newTitle;
            notify('[Custom Title]: applied title');
        } else if (titleInfo) {
            notify('[Custom Title]: no need to replace title');
        } else {
            notify('[Custom Title]: no title can be appied');
        }
    }

    // functions
    function updateTitle() {
        let titleInfo = storage.titleInfoDict[location.href];
        let newTitle = prompt('请输入需要为此网页自定义的标题,可通过 \'Ctrl+Shift+T\' 或直接输入空以取消:', titleInfo ? titleInfo.newTitle : document.title);
        if (newTitle) {
            storage.titleInfoDict[location.href] = {
                oldTitle: titleInfo ? titleInfo.oldTitle : document.title,
                newTitle: newTitle,
                lastUpdateTime: new Date().getTime(),
            };
            document.title = newTitle;
            GM_setValue('storage', storage);
            notify('[Custom Title]: updated title');
        } else if (newTitle !== null && newTitle.constructor === String) {
            removeTitle();
        }
    }

    function removeTitle() {
        let titleInfo = storage.titleInfoDict[location.href];
        if (titleInfo) {
            let res = confirm('是否为此网页移除自定义标题?');
            if (res) {
                document.title = titleInfo.oldTitle;
                delete storage.titleInfoDict[location.href];
                GM_setValue('storage', storage);
                notify('[Custom Title]: removed title');
            }
        } else {
            alert('你还未为此网页设置自定义标题');
        }
    }

    function removeAllTitles() {
        let res = confirm('是否清除所有自定义标题?');
        if (res) {
            let titleInfo = storage.titleInfoDict[location.href];
            if (titleInfo) {
                document.title = titleInfo.oldTitle;
            }
            storage = getDefaultStorage();
            GM_setValue('storage', storage);
            alert('已清除所有自定义标题');
            notify('[Custom Title]: removed all titles');
        }
    }

    function clearExpiredTitle() {
        let currentTime = new Date().getTime();
        if (storage.lastUpdateTime + interval < currentTime) {
            let titleInfoDict = storage.titleInfoDict;
            for (let key of Object.keys(titleInfoDict)) {
                if (titleInfoDict[key].lastUpdateTime + expiredTime < currentTime) {
                    delete titleInfoDict[key];
                }
            }
            storage.titleInfoDict = titleInfoDict;
            storage.lastUpdateTime = currentTime;
            GM_setValue('storage', storage);
            notify('[Custom Title]: cleared expired titles');
        }
    }

    function bindObserver() {
        // key observer
        document.onkeydown = function(e) {
            console.log(e);

            if (e.ctrlKey && e.shiftKey && e.code == focusKey) {
                e.preventDefault();
                removeTitle();
                notify('[Custom Title]: trigger remove title');
            } else if (e.ctrlKey && e.code == focusKey) {
                e.preventDefault();
                updateTitle();
                notify('[Custom Title]: trigger update title');
            } else if (e.ctrlKey && e.code == clearKey) {
                e.preventDefault();
                removeAllTitles();
                notify('[Custom Title]: trigger remove all titles');
            }
        }
        // title observer
        let title = document.querySelector('title');
        let observer = new MutationObserver(function(mutations) {
            console.log(mutations);
            applyTitle();
            notify('[Custom Title]: trigger title change event');
        });
        let config = { subtree: true, characterData: true, childList: true };
        observer.observe(title, config);

        // hash observer
        window.onhashchange = function() {
            applyTitle();
            notify('[Custom Title]: trigger hash change event');
        };

        // call apply again
        applyTitle();
    }

    // main function
    document.onreadystatechange = function() {
        if (document.readyState === 'complete') {
            notify(`[Custom Title]: version ${_VERSION_}`);
            loadStorage();
            applyTitle();
            bindObserver();
        }
    }
})();