IPE添加预览样式和快捷键 (on THBWiki)

为 InPageEdit 的预览添加缺少的样式,以及各种快捷键

// ==UserScript==
// @name         IPE添加预览样式和快捷键 (on THBWiki)
// @namespace    https://greasyfork.org/users/551710
// @version      1.0
// @description  为 InPageEdit 的预览添加缺少的样式,以及各种快捷键
// @author       Gzz
// @match        *://thwiki.cc/*
// @icon         https://static.thbwiki.cc/favicon.ico
// @license      MIT
// @grant        none
// ==/UserScript==

(function() {
    // 去掉 IPE 预览窗口的内边距
    const style = document.createElement('style');
    style.innerHTML = `
    .in-page-edit.previewbox .ssi-modalContent {
        padding: 0;
    }
    .in-page-edit.previewbox .ssi-overflow::before, .in-page-edit.previewbox .ssi-overflow::after {
        height: 0;
    }
    `;
    document.head.appendChild(style);

    function addAccessKeys(editor) {
        // 给编辑区添加快捷键 Alt+,
        const progress = editor.querySelector('.ipe-progress');
        if (!progress.style.display) {
            // 等待编辑器加载完毕
            const observer = new MutationObserver((mutations, observer) => {
                if (progress.style.display !== 'none') return;
                setTimeout(() => {
                    // 判断编辑器类型: CodeMirror 6, Monaco, CodeMirror 5, default
                    let types = ['.cm-content', '.inputarea', '.CodeMirror textarea', '.editArea'];
                    editorType = types.find(type => editor.querySelector(type));

                    const editArea = editor.querySelector(editorType);
                    editArea?.setAttribute('accesskey', ',');
                    editArea?.focus();
                }, 300);
                observer.disconnect();
            });
            observer.observe(progress, { attributes: true });
        } else {
            editor.querySelector(editorType)?.setAttribute('accesskey', ',');
        }

        // 预览: Alt+P, 差异: Alt+V
        const buttons = editor.querySelectorAll('#ssi-leftButtons > button');
        if (buttons.length > 2) {
            buttons[0].title = 'Ctrl+S';
            buttons[1].accessKey = 'p';
            buttons[1].title = 'Alt+P';
            buttons[2].accessKey = 'v';
            buttons[2].title = 'Alt+V';

            // 已存在差异窗口时按下按钮则关闭窗口
            buttons[2].addEventListener('click', () => {
                if (!diff) return;
                setTimeout(() => {
                    document.querySelector('.in-page-edit.quick-diff .ssi-closeIcon')?.click();
                }, 0);
            }, { capture: true });
        }

        // 摘要: Alt+B
        const summary = editor.querySelector('.editSummary');
        if (summary) {
            summary.accessKey = 'b';
            summary.title = 'Alt+B';
        }

        // 小编辑: Alt+I
        const minor = editor.querySelector('.editMinor')?.closest('label');
        if (minor) {
            minor.accessKey = 'i';
            minor.title = 'Alt+I';
        }

        // 监视: Alt+W
        const watch = editor.querySelector('.watchList');
        const label = watch?.closest('label');
        const span = label?.querySelector('span');
        if (!label) return;

        // label 无法被 accesskey 直接激活, 加在 span 上
        span.accessKey = 'w';
        if (label.dataset.processed) return;

        label.title += ' [Alt+W]';
        label.addEventListener('click', () => {
            label.title = 'Alt+W';
        }, { once: true });
        label.dataset.processed = true;
    }

    function removeAccessKeys(editor) {
        editor.querySelectorAll('[accesskey]').forEach(el => {el.accessKey = ''});
    }

    // 等待编辑按钮出现, 添加快捷键 Alt+O
    const observerEdit = new MutationObserver((mutations, observer) => {
        const btn = document.getElementById('edit-btn');
        if (!btn) return;
        btn.accessKey = 'o';
        btn.title = 'Alt+O';

        // 先打开再关闭一次, 解决当前版本 CodeMirror 6 加载不及时的问题
        btn.click();
        document.querySelector('.in-page-edit.ipe-editor .ssi-closeIcon')?.click();
        document.querySelectorAll('.in-page-edit.notify').forEach(notify => notify.remove());
        observer.disconnect();
    });
    observerEdit.observe(document.body, { childList: true });

    // 监听 <body> 直接子元素变化
    let numModals = 0, numEditors = 0;
    let editorType, diff;
    const observer = new MutationObserver(() => {
        const modals = Array.from(document.querySelectorAll('body > .in-page-edit'));
        const lastModal = modals.at(-1);
        if (modals.length === numModals) return;

        const editors = modals.filter(el => el.matches('.ipe-editor'));
        const lastEditor = editors.at(-1);
        numModals = modals.length;

        // 聚焦最上层的窗口
        if (lastModal && lastModal === lastEditor) {
            if (editorType) lastEditor.querySelector(editorType)?.focus();
        } else {
            const el = ['.ssi-overflow', '.ssi-modalContent'].map(type => lastModal?.querySelector(type)).find(Boolean);
            if (el) {
                // 使上下键可以滚动
                el.tabIndex = '0';
                el.style.outline = 'none';
                el.focus();
            }
        }

        if (!lastEditor) {
            if (numEditors) {
                document.querySelectorAll('[data-accesskey]').forEach(el => {el.accessKey = el.dataset.accesskey});
                numEditors = 0;
            }
            return;
        }

        // 快捷键只加在最上层的编辑器里
        if (editors.length > numEditors) {
            if (numEditors) {
                removeAccessKeys(editors.at(-2));
            } else {
                // 去掉页面里已有的快捷键
                document.querySelectorAll('[accesskey]').forEach(el => {
                    el.dataset.accesskey = el.accessKey;
                    el.accessKey = '';
                });
            }
            addAccessKeys(lastEditor);
        } else if (editors.length < numEditors) {
            addAccessKeys(lastEditor);
        }
        numEditors = editors.length;

        // 如果存在两个以上的预览窗口则全部关闭
        const previews = document.querySelectorAll('.in-page-edit.previewbox');
        if (previews.length > 1) {
            previews.forEach(preview => preview.querySelector('.ssi-closeIcon')?.click());
        } else if (previews.length) {
            // 给预览内容添加 class
            const preview = previews[0].querySelector('.InPageEditPreview');
            preview.classList.add('mw-body', 'mw-body-content', 'mw-content-ltr');
            preview.style.marginLeft = 0;
        }

        diff = document.querySelector('.in-page-edit.quick-diff');
    });
    observer.observe(document.body, { childList: true });
})();