linux.do Obsidian Callouts回复

2025-9-28 17:39:58

当前为 2025-09-28 提交的版本,查看 最新版本

// ==UserScript==
// @name        linux.do Obsidian Callouts回复
// @namespace   http://52shell.ltd/
// @match       https://linux.do/*
// @grant       none
// @license     MIT
// @version     1.2.1
// @author      Shell
// @description 2025-9-28 17:39:58
// ==/UserScript==

(function() {
    'use strict';

    // 定义可用的 callout 类型
    // 在 calloutTypes 里新增
const calloutTypes = {
    success: {
        color: '#28a745',
        icon: '<path d="M5 12l5 5 10-10"/>',
        label: 'Success'
    },
    tip: {
        color: '#17a2b8',
        icon: '<path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/>',
        label: 'Tip'
    },
    note: {
        color: '#007bff',
        icon: '<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/><polyline points="10 9 9 9 8 9"/>',
        label: 'Note'
    },
    warning: {
        color: '#ffc107',
        icon: '<path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/>',
        label: 'Warning'
    },
    danger: {
        color: '#dc3545',
        icon: '<circle cx="12" cy="12" r="10"/><line x1="15" y1="9" x2="9" y2="15"/><line x1="9" y1="9" x2="15" y2="15"/>',
        label: 'Danger'
    },
    info: {
        color: '#6f42c1',
        icon: '<circle cx="12" cy="12" r="10"/><line x1="12" y1="16" x2="12" y2="12"/><line x1="12" y1="8" x2="12.01" y2="8"/>',
        label: 'Info'
    },
    question: {
        color: '#f0ad4e',
        icon: '<circle cx="12" cy="12" r="10"/><text x="12" y="16" font-size="12" text-anchor="middle" stroke="white" stroke-width="0.5">?</text>',
        label: 'Question'
    },
    failure: {
        color: '#800000',
        icon: '<circle cx="12" cy="12" r="10"/><line x1="15" y1="9" x2="9" y2="15"/><line x1="9" y1="9" x2="15" y2="15"/>',
        label: 'Failure'
    },
    bug: {
        color: '#e55353',
        icon: '<circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="16"/><line x1="8" y1="12" x2="16" y2="12"/>',
        label: 'Bug'
    }
};


    function getDefaultCalloutType() {
        const saved = localStorage.getItem('obsidianCalloutDefaultType');
        return saved && calloutTypes[saved] ? saved : 'success';
    }

    function setDefaultCalloutType(type) {
        localStorage.setItem('obsidianCalloutDefaultType', type);
    }

    // 创建右键菜单
    function createContextMenu(button, currentType) {
        const existingMenu = document.querySelector('.obsidian-callout-menu');
        if (existingMenu) existingMenu.remove();

        const menu = document.createElement('div');
        menu.className = 'obsidian-callout-menu';
        menu.style.cssText = `
            position: fixed;
            background: white;
            border: 1px solid #ddd;
            border-radius: 8px;
            box-shadow: 0 2px 10px rgba(0,0,0,0.1);
            z-index: 10000;
            min-width: 150px;
            display: none;
        `;

        const rect = button.getBoundingClientRect();
        menu.style.left = rect.left + 'px';
        menu.style.top = (rect.bottom + 5) + 'px';

        Object.entries(calloutTypes).forEach(([type, config]) => {
            const menuItem = document.createElement('div');
            menuItem.style.cssText = `
                padding: 10px 15px;
                cursor: pointer;
                display: flex;
                align-items: center;
                gap: 10px;
                transition: background 0.2s;
            `;
            if (type === currentType) {
                menuItem.style.backgroundColor = '#f0f0f0';
                menuItem.style.fontWeight = 'bold';
            }
            menuItem.innerHTML = `
                <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="${config.color}" stroke-width="2">${config.icon}</svg>
                <span>${config.label}</span>
            `;
            menuItem.addEventListener('mouseenter', () => menuItem.style.backgroundColor = '#f0f0f0');
            menuItem.addEventListener('mouseleave', () => {
                if (type !== currentType) menuItem.style.backgroundColor = 'transparent';
            });
            menuItem.addEventListener('click', (e) => {
                e.stopPropagation();
                setDefaultCalloutType(type);
                updateButtonStyle(button, type);
                menu.remove();
            });
            menu.appendChild(menuItem);
        });

        document.body.appendChild(menu);
        setTimeout(() => menu.style.display = 'block', 10);

        document.addEventListener('click', function closeMenu() {
            menu.remove();
            document.removeEventListener('click', closeMenu);
        });
    }

    function updateButtonStyle(button, type) {
        const config = calloutTypes[type];
        button.style.backgroundColor = config.color;
        button.innerHTML = `
            <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2">${config.icon}</svg>
            <span>${config.label}</span>
        `;
    }

    // 插入按钮逻辑
    function insertCalloutButton() {
        const footerControls = document.querySelector('.timeline-footer-controls');
        if (!footerControls) return;
        if (footerControls.querySelector('.obsidian-callout-button')) return;

        const newButtonContainer = document.createElement('div');
        newButtonContainer.style.marginTop = '10px';
        newButtonContainer.style.width = '100%';

        const calloutBtn = document.createElement('button');
        calloutBtn.className = 'btn no-text btn-icon emoji obsidian-callout-button';

        const defaultType = getDefaultCalloutType();
        const defaultConfig = calloutTypes[defaultType];
        calloutBtn.style.cssText = `
            background-color: ${defaultConfig.color};
            color: white;
            border-radius: 25px;
            padding: 0 20px;
            height: 40px;
            display: flex;
            align-items: center;
            justify-content: center;
            gap: 8px;
            box-shadow: 0 2px 5px rgba(0,0,0,0.2);
            transition: all 0.3s ease;
            font-weight: bold;
            font-size: 14px;
            cursor: pointer;
        `;
        calloutBtn.innerHTML = `
            <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2">${defaultConfig.icon}</svg>
            <span>${defaultConfig.label}</span>
        `;

        calloutBtn.addEventListener('mouseenter', () => {
            calloutBtn.style.transform = 'scale(1.05)';
            calloutBtn.style.boxShadow = '0 4px 8px rgba(0,0,0,0.3)';
        });
        calloutBtn.addEventListener('mouseleave', () => {
            calloutBtn.style.transform = 'scale(1)';
            calloutBtn.style.boxShadow = '0 2px 5px rgba(0,0,0,0.2)';
        });

        // 左键点击插入
        calloutBtn.addEventListener('click', () => {
            const currentType = getDefaultCalloutType();
            const replyTriggerBtn = document.querySelector('button[title="开始撰写此话题的回复"]');
            if (replyTriggerBtn) replyTriggerBtn.click();
            else return console.error('未找到回复触发按钮');

            const checkReplyBox = setInterval(() => {
                const replyBox = document.querySelector('.d-editor-input');
                if (replyBox) {
                    clearInterval(checkReplyBox);
                    const start = replyBox.selectionStart;
                    const end = replyBox.selectionEnd;
                    const contentToInsert = `>[!${currentType}] `;
                    replyBox.value = replyBox.value.slice(0, start) + contentToInsert + replyBox.value.slice(end);
                    const newCursorPos = start + contentToInsert.length;
                    replyBox.setSelectionRange(newCursorPos, newCursorPos);
                    replyBox.dispatchEvent(new Event('input', { bubbles: true }));
                    replyBox.focus();
                }
            }, 100);

            setTimeout(() => {
                clearInterval(checkReplyBox);
            }, 3000);
        });

        // 右键菜单
        calloutBtn.addEventListener('contextmenu', (e) => {
            e.preventDefault();
            const currentType = getDefaultCalloutType();
            createContextMenu(calloutBtn, currentType);
        });

        newButtonContainer.appendChild(calloutBtn);
        footerControls.appendChild(newButtonContainer);
    }

    // 初始执行一次
    insertCalloutButton();

    // 监听 DOM 动态变化
    const observer = new MutationObserver(() => insertCalloutButton());
    observer.observe(document.body, { childList: true, subtree: true });

})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址