XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

XFESystem

【Rust】XFE系统,基于Rust和汇编

公开
关注 0 Fork 0 Star 0
UTF-8
(() => {
    const lineNumberPreferenceKey = 'xfe-code-line-numbers';
    const languageExtensions = {
        bash: 'sh', shell: 'sh', sh: 'sh',
        c: 'c', cpp: 'cpp', csharp: 'cs', cs: 'cs',
        css: 'css', cmd: 'cmd', dos: 'cmd',
        html: 'html', java: 'java', javascript: 'js', js: 'js',
        json: 'json', markdown: 'md', md: 'md',
        powershell: 'ps1', ps1: 'ps1', python: 'py', py: 'py',
        razor: 'cshtml', sql: 'sql', text: 'txt', plaintext: 'txt',
        typescript: 'ts', ts: 'ts', xaml: 'xaml', xml: 'xml', yaml: 'yml', yml: 'yml'
    };

    const createButton = (action, icon, label) => {
        const button = document.createElement('button');
        button.className = 'code-tool-button';
        button.type = 'button';
        button.dataset.codeAction = action;
        button.title = label;
        button.setAttribute('aria-label', label);
        button.innerHTML = `<i class="bi ${icon}" aria-hidden="true"></i>`;
        return button;
    };

    const setButtonIcon = (button, icon) => {
        const element = button.querySelector('i');
        if (element) element.className = `bi ${icon}`;
    };

    const getLineNumberPreference = () => {
        try {
            return localStorage.getItem(lineNumberPreferenceKey) !== 'off';
        } catch {
            return true;
        }
    };

    const applyLineNumberPreference = visible => {
        document.querySelectorAll('pre.code-block-enhanced').forEach(pre => {
            pre.classList.toggle('code-lines-visible', visible);
            const button = pre.querySelector('[data-code-action="lines"]');
            if (!button) return;
            button.classList.toggle('is-active', visible);
            button.setAttribute('aria-pressed', String(visible));
            button.title = visible ? '隐藏行号' : '显示行号';
            button.setAttribute('aria-label', button.title);
        });
        try {
            localStorage.setItem(lineNumberPreferenceKey, visible ? 'on' : 'off');
        } catch {
            // Storage can be unavailable in privacy modes; the current page still works.
        }
    };

    const copyText = async text => {
        if (navigator.clipboard?.writeText) {
            await navigator.clipboard.writeText(text);
            return;
        }

        const textarea = document.createElement('textarea');
        textarea.value = text;
        textarea.style.position = 'fixed';
        textarea.style.opacity = '0';
        document.body.appendChild(textarea);
        textarea.select();
        document.execCommand('copy');
        textarea.remove();
    };

    const getLanguage = code => {
        const languageClass = [...code.classList].find(value =>
            value.startsWith('language-') || value.startsWith('lang-'));
        return languageClass?.replace(/^(language-|lang-)/, '').toLowerCase() || 'text';
    };

    const createLineNumbers = source => {
        const normalized = source.replace(/\r\n?/g, '\n');
        const content = normalized.endsWith('\n') ? normalized.slice(0, -1) : normalized;
        const lineCount = Math.max(1, content.split('\n').length);
        const gutter = document.createElement('span');
        gutter.className = 'code-line-numbers';
        gutter.setAttribute('aria-hidden', 'true');
        const fragment = document.createDocumentFragment();
        for (let index = 1; index <= lineCount; index += 1) {
            const line = document.createElement('span');
            line.textContent = String(index);
            fragment.appendChild(line);
        }
        gutter.appendChild(fragment);
        return gutter;
    };

    const downloadCode = (source, language, requestedName) => {
        const extension = languageExtensions[language] || 'txt';
        const blob = new Blob([source], { type: 'text/plain;charset=utf-8' });
        const url = URL.createObjectURL(blob);
        const anchor = document.createElement('a');
        anchor.href = url;
        anchor.download = requestedName || `code.${extension}`;
        document.body.appendChild(anchor);
        anchor.click();
        anchor.remove();
        URL.revokeObjectURL(url);
    };

    const updateExpandButton = (pre, button) => {
        const expanded = document.fullscreenElement === pre || pre.classList.contains('is-expanded');
        setButtonIcon(button, expanded ? 'bi-fullscreen-exit' : 'bi-arrows-fullscreen');
        button.classList.toggle('is-active', expanded);
        button.title = expanded ? '退出全屏' : '全屏查看';
        button.setAttribute('aria-label', button.title);
        button.setAttribute('aria-pressed', String(expanded));
    };

    const toggleExpanded = async (pre, button) => {
        if (document.fullscreenElement === pre) {
            await document.exitFullscreen();
            return;
        }

        if (pre.classList.contains('is-expanded')) {
            pre.classList.remove('is-expanded');
            document.body.classList.remove('code-block-expanded');
            updateExpandButton(pre, button);
            return;
        }

        if (pre.requestFullscreen) {
            try {
                await pre.requestFullscreen();
                return;
            } catch {
                // Fall back to an in-page overlay when fullscreen is unavailable.
            }
        }

        document.querySelectorAll('pre.code-block-enhanced.is-expanded').forEach(item =>
            item.classList.remove('is-expanded'));
        pre.classList.add('is-expanded');
        document.body.classList.add('code-block-expanded');
        updateExpandButton(pre, button);
    };

    const enhanceBlock = pre => {
        if (pre.dataset.codeToolsApplied === 'true' ||
            pre.classList.contains('mermaid') ||
            pre.closest('.mermaid-wrapper')) return;

        const code = pre.querySelector('code');
        if (!code || code.classList.contains('language-mermaid') || code.classList.contains('lang-mermaid')) return;

        const source = code.textContent || '';
        const language = getLanguage(code);
        pre.dataset.codeToolsApplied = 'true';
        pre.dataset.featuresApplied = 'true';
        pre.classList.add('code-block-enhanced');

        pre.querySelectorAll(':scope > .lang-label, :scope > .copy-btn').forEach(item => item.remove());

        const toolbar = document.createElement('span');
        toolbar.className = 'code-block-toolbar';

        const languageLabel = document.createElement('span');
        languageLabel.className = 'code-language-label';
        languageLabel.textContent = language;

        const actions = document.createElement('span');
        actions.className = 'code-block-actions';
        const linesButton = createButton('lines', 'bi-list-ol', '隐藏行号');
        const downloadButton = createButton('download', 'bi-download', '下载代码');
        const expandButton = createButton('expand', 'bi-arrows-fullscreen', '全屏查看');
        const copyButton = createButton('copy', 'bi-copy', '复制代码');
        actions.append(linesButton, downloadButton, expandButton, copyButton);
        toolbar.append(languageLabel, actions);

        const viewport = document.createElement('span');
        viewport.className = 'code-block-viewport';
        viewport.append(createLineNumbers(source), code);
        pre.replaceChildren(toolbar, viewport);

        const showLines = getLineNumberPreference();
        pre.classList.toggle('code-lines-visible', showLines);
        linesButton.classList.toggle('is-active', showLines);
        linesButton.setAttribute('aria-pressed', String(showLines));
        linesButton.title = showLines ? '隐藏行号' : '显示行号';
        linesButton.setAttribute('aria-label', linesButton.title);

        linesButton.addEventListener('click', () =>
            applyLineNumberPreference(!pre.classList.contains('code-lines-visible')));
        downloadButton.addEventListener('click', () =>
            downloadCode(source, language, pre.dataset.codeDownloadName));
        expandButton.addEventListener('click', () => toggleExpanded(pre, expandButton));
        copyButton.addEventListener('click', async () => {
            try {
                await copyText(source);
                setButtonIcon(copyButton, 'bi-check-lg');
                copyButton.classList.add('is-success');
                copyButton.title = '已复制';
                copyButton.setAttribute('aria-label', '代码已复制');
                window.setTimeout(() => {
                    setButtonIcon(copyButton, 'bi-copy');
                    copyButton.classList.remove('is-success');
                    copyButton.title = '复制代码';
                    copyButton.setAttribute('aria-label', '复制代码');
                }, 1500);
            } catch {
                copyButton.title = '复制失败';
                copyButton.setAttribute('aria-label', '复制失败');
            }
        });

        document.addEventListener('fullscreenchange', () => updateExpandButton(pre, expandButton));
    };

    const enhance = (container = document) => {
        const blockSelector = '.markdown-body pre, pre[data-code-block-tools]';
        const blocks = [...container.querySelectorAll(blockSelector)];
        if (container.matches?.(blockSelector)) blocks.unshift(container);
        blocks.forEach(enhanceBlock);
    };

    document.addEventListener('keydown', event => {
        if (event.key !== 'Escape') return;
        const expanded = document.querySelector('pre.code-block-enhanced.is-expanded');
        if (!expanded) return;
        expanded.classList.remove('is-expanded');
        document.body.classList.remove('code-block-expanded');
        const button = expanded.querySelector('[data-code-action="expand"]');
        if (button) updateExpandButton(expanded, button);
    });

    window.XFECodeBlocks = { enhance };
    document.addEventListener('DOMContentLoaded', () => enhance(document));
})();