QuickaddJS实现的悬浮窗指令

有一个使用悬浮窗进行命令控制的想法,所以让gpt写了一个简单的quickaddjs 脚本,目前来说扩展性还可以(修改原始文本就行)。
关于执行命令的部分,可以参考 【技巧】超越快捷键!实现 Obsidian 内的「文本指令」 - 经验分享 - Obsidian 中文论坛 这篇如何通过 Cmd+Shift+i得到指令id的,此处的代码只是一个可以拖动的窗口。仍然通过js 以macro的方式被引入。

module.exports = async function (params) {
    const { app, quickAddApi } = params;

    // 添加在脚本开始处
    const existingToolbar = document.querySelector('.float-toolbar');
    if (existingToolbar) {
        document.body.removeChild(existingToolbar);
        return;
    }

    // 读取命令配置
    let { commands, windowConfig = {} } = await loadCommands();
    const useLastPosition = windowConfig.useLastPosition || false;

    // 创建弹窗容器
    const popupContainer = document.createElement('div');
    popupContainer.classList.add('float-toolbar');
    Object.assign(popupContainer.style, {
        position: 'fixed',
        width: windowConfig.width || '300px',
        height: windowConfig.height || 'auto',
        left: useLastPosition && windowConfig.left ? windowConfig.left : '50%',
        top: useLastPosition && windowConfig.top ? windowConfig.top : '50%',
        transform: useLastPosition ? 'none' : 'translate(-50%, -50%)',
        backgroundColor: 'var(--background-primary)',
        padding: '20px',
        border: '1px solid var(--background-modifier-border)',
        borderRadius: '10px',
        boxShadow: '0 4px 6px rgba(0, 0, 0, 0.3)',
        zIndex: '9999',
        cursor: 'move',
        userSelect: 'none',
        resize: 'none',
        overflow: 'hidden',
    });

    // 创建命令按钮容器
    const commandButtonsContainer = document.createElement('div');
    commandButtonsContainer.classList.add('command-buttons-container');

    // 创建命令按钮
    function createCommandButtons() {
        commandButtonsContainer.innerHTML = ''; // 清空现有按钮
        commands.forEach((cmd) => {
            const button = document.createElement('button');
            button.textContent = cmd.text;
            button.addEventListener('click', async (e) => {
                if (e.button === 0) {
                    // 左键点击
                    try {
                        await app.commands.executeCommandById(cmd.commandId);
                    } catch (error) {
                        console.error(`执行命令 ${cmd.text} 失败:`, error);
                        new Notice(`执行命令 ${cmd.text} 失败,打开控制台查看详情`, 1000);
                    }
                }
            });

            // 右键点击删除按钮
            button.addEventListener('contextmenu', (e) => {
                e.preventDefault();
                // 更新命令列表
                commands = commands.filter((c) => c.text !== cmd.text); // 🚩 现在可以重新赋值
                saveCommands(commands);
                createCommandButtons();
            });

            commandButtonsContainer.appendChild(button);
        });
    }

    createCommandButtons();

    // 创建关闭按钮
    const closeButton = document.createElement('button');
    closeButton.textContent = '×';
    Object.assign(closeButton.style, {
        position: 'absolute',
        top: '5px',
        right: '5px',
        width: '20px',
        height: '20px',
        padding: '0',
        border: 'none',
        background: 'transparent',
        color: 'var(--text-muted)',
        fontSize: '20px',
        lineHeight: '20px',
        cursor: 'pointer',
        display: 'flex',
        alignItems: 'center',
        justifyContent: 'center',
        borderRadius: '50%',
        transition: 'background-color 0.2s, color 0.2s',
    });

    // 添加悬停效果
    closeButton.addEventListener('mouseover', () => {
        closeButton.style.backgroundColor = 'var(--background-modifier-hover)';
        closeButton.style.color = 'var(--text-normal)';
    });

    closeButton.addEventListener('mouseout', () => {
        closeButton.style.backgroundColor = 'transparent';
        closeButton.style.color = 'var(--text-muted)';
    });

    // 定义事件处理函数
    let isDragging = false;
    let offsetX, offsetY;

    const handleMouseMove = (e) => {
        if (isDragging) {
            const x = e.clientX - offsetX;
            const y = e.clientY - offsetY;

            const maxX = window.innerWidth - popupContainer.offsetWidth / 3;
            const maxY = window.innerHeight - popupContainer.offsetHeight / 3;

            popupContainer.style.left = `${Math.max(0, Math.min(x, maxX))}px`;
            popupContainer.style.top = `${Math.max(0, Math.min(y, maxY))}px`;
            popupContainer.style.transform = 'none';
        }
    };

    const handleMouseUp = () => {
        if (isDragging) {
            isDragging = false;
            // 保存新位置
            saveWindowConfig({
                left: popupContainer.style.left,
                top: popupContainer.style.top,
                width: popupContainer.style.width,
                height: popupContainer.style.height,
                useLastPosition: true, // 自动启用记忆位置
            });
        }
    };

    // 修改关闭按钮的点击处理程序
    closeButton.addEventListener('click', () => {
        closeToolbar(handleMouseMove, handleMouseUp, popupContainer);
    });

    // 创建新增命令按钮
    const addCommandButton = document.createElement('button');
    addCommandButton.textContent = '+ 新增命令';
    addCommandButton.style.marginTop = '10px';
    addCommandButton.addEventListener('click', async () => {
        const allCommands = app.commands.listCommands();
        const selectedCommand = await quickAddApi.suggester(
            allCommands.map((cmd) => cmd.name),
            allCommands
        );
        if (!selectedCommand) {
            new Notice('未选择命令');
            return;
        }

        // 检查中文字符数量
        let displayText = selectedCommand.name;
        const chineseCharCount = displayText.length;
        
        if (chineseCharCount > 6) {
            // 弹出输入框获取替代文本
            const userInput = await quickAddApi.inputPrompt(
                '输入替代文本',
                '原命令名过长(超过6个汉字),请输入替代文本:',
                displayText
            );
            
            if (userInput === null) {
                new Notice('已取消添加命令');
                return;
            }
            
            if (userInput.trim() === '') {
                new Notice('替代文本不能为空');
                return;
            }
            
            displayText = userInput;
        }

        const newCommand = {
            text: displayText,
            commandId: selectedCommand.id,
        };
        commands.push(newCommand);
        saveCommands(commands);
        createCommandButtons(); // 刷新按钮
    });

    // 创建调整大小的手柄
    const resizeHandle = document.createElement('div');
    Object.assign(resizeHandle.style, {
        position: 'absolute',
        bottom: '0',
        right: '0',
        width: '10px',
        height: '10px',
        backgroundColor: 'var(--background-modifier-border)',
        cursor: 'se-resize',
    });

    // 拖拽调整大小的逻辑
    let isResizing = false;
    let startX, startY, startWidth, startHeight;

    resizeHandle.addEventListener('mousedown', (e) => {
        isResizing = true;
        startX = e.clientX;
        startY = e.clientY;
        startWidth = parseInt(document.defaultView.getComputedStyle(popupContainer).width, 10);
        startHeight = parseInt(document.defaultView.getComputedStyle(popupContainer).height, 10);
        e.preventDefault(); // 防止文本选中
    });

    document.addEventListener('mousemove', (e) => {
        if (isResizing) {
            const newWidth = startWidth + (e.clientX - startX);
            const newHeight = startHeight + (e.clientY - startY);
            popupContainer.style.width = `${newWidth}px`;
            popupContainer.style.height = `${newHeight}px`;
        }
    });

    document.addEventListener('mouseup', () => {
        if (isResizing) {
            isResizing = false;
            // 保存调整后的尺寸
            saveWindowConfig({
                width: popupContainer.style.width,
                height: popupContainer.style.height,
                left: popupContainer.style.left,
                top: popupContainer.style.top,
            });
        }
    });

    // 将命令按钮、新增按钮、关闭按钮和调整手柄添加到弹窗容器中
    popupContainer.appendChild(commandButtonsContainer);
    popupContainer.appendChild(addCommandButton);
    popupContainer.appendChild(closeButton);
    popupContainer.appendChild(resizeHandle);

    // 将弹窗添加到页面中
    document.body.appendChild(popupContainer);

    // 拖拽功能实现
    popupContainer.addEventListener('mousedown', (e) => {
        if (e.target === resizeHandle) return; // 如果点击的是调整手柄,则不触发拖拽
        isDragging = true;
        offsetX = e.clientX - popupContainer.getBoundingClientRect().left;
        offsetY = e.clientY - popupContainer.getBoundingClientRect().top;
    });

    // 拖拽过程的具体处理
    document.addEventListener('mousemove', handleMouseMove);
    document.addEventListener('mouseup', handleMouseUp);
};

function closeToolbar(handleMouseMove, handleMouseUp, popupContainer) {
    document.removeEventListener('mousemove', handleMouseMove);
    document.removeEventListener('mouseup', handleMouseUp);
    // 移除弹窗
    document.body.removeChild(popupContainer);
}

async function loadCommands() {
    const path = '05-木板/JSetc脚本/commands.json';
    try {
        const content = await app.vault.adapter.read(path);
        // 兼容旧格式(纯数组)和新格式(带配置的对象)
        const result = JSON.parse(content);

        return Array.isArray(result)
            ? { commands: result }
            : {
                  commands: result.commands || [],
                  windowConfig: result.windowConfig || {},
              };
    } catch (error) {
        console.error('读取命令文件失败:', error);
        return { commands: [] };
    }
}

async function saveWindowConfig(config) {
    const path = '05-木板/JSetc脚本/commands.json';
    try {
        const existing = await loadCommands();
        const newData = {
            commands: existing.commands,
            windowConfig: {
                ...existing.windowConfig,
                ...config,
            },
        };
        await app.vault.adapter.write(path, JSON.stringify(newData, null, 2));
    } catch (error) {
        console.error('保存窗口配置失败:', error);
    }
}

async function saveCommands(commands) {
    const path = '05-木板/JSetc脚本/commands.json';
    try {
        const existing = await loadCommands();
        const newData = {
            commands,
            windowConfig: existing.windowConfig || {},
        };
        await app.vault.adapter.write(path, JSON.stringify(newData, null, 2));
    } catch (error) {
        console.error('保存命令文件失败:', error);
    }
}

效果大概是:
screenshots

3 个赞

非常巧妙的想法,可以实现很多的拓展性,感谢分享!

1 个赞

尝试做了一点小改动,也分享一下:

  1. 关闭按钮放到右上角
  2. 限制了可放置的范围,避免移动超出窗口
  3. 改成了开关式的,避免重复触发创建多个工具栏,现在第一下打开,第二下会关上
  4. 增加了一个“一次性按钮”的开关(isCloseAfterCommand),如果开启,那么执行一次之后就会自动关闭
  5. 调整了一些样式让它更明显

module.exports = async function(params) {
    const { app } = params;

    // 一次性执行,点击按钮后关闭整个窗口
    const isCloseAfterClick = true;

    // 添加在脚本开始处
    const existingToolbar = document.querySelector('.float-toolbar');
    if (existingToolbar) {
        document.body.removeChild(existingToolbar);
        return;
    }

    // 创建弹窗容器
    const popupContainer = document.createElement('div');
    popupContainer.classList.add('float-toolbar');
    Object.assign(popupContainer.style, {
        position: 'fixed',
        width: '300px',
        top: '50%',
        left: '50%',
        transform: 'translate(-50%, -50%)',
        backgroundColor: 'var(--background-primary)',
        padding: '20px',
        border: '1px solid var(--background-modifier-border)',
        borderRadius: '10px',
        boxShadow: '0 4px 6px rgba(0, 0, 0, 0.3)',
        zIndex: '9999',
        cursor: 'move',
        userSelect: 'none'
    });

    // 创建命令按钮容器
    const commandButtonsContainer = document.createElement('div');
    commandButtonsContainer.classList.add('command-buttons-container');

    // 命令按钮列表
    const commands = [
        { text: '同步', commandId: 'remotely-save:start-sync' },
        { text: '打开日记', commandId: 'daily-notes' },
        { text: '打开设置', commandId: 'app:open-settings' },
        { text: '高亮', commandId: 'editor:toggle-highlight' },
        { text: '粗体', commandId: 'editor:toggle-bold' },
        { text: '清理图片', commandId: 'oz-clear-unused-images:clear-images-obsidian' },
    ];

    // 创建命令按钮
    commands.forEach(cmd => {
        const button = document.createElement('button');
        button.textContent = cmd.text;
        button.addEventListener('click', async () => {
            try {
                await app.commands.executeCommandById(cmd.commandId);

                if (isCloseAfterClick) {
                    closeToolbar(handleMouseMove, handleMouseUp, popupContainer);
                }  // 点击后关闭
            } catch (error) {
                console.error(`执行命令 ${cmd.text} 失败:`, error);
                // 可以添加用户提示
                new Notice(`执行命令 ${cmd.text} 失败,打开控制台查看详情`, 1000);
            }
        });
        commandButtonsContainer.appendChild(button);
    });

    // 创建关闭按钮
    const closeButton = document.createElement('button');
    closeButton.textContent = '×';
    Object.assign(closeButton.style, {
        position: 'absolute',
        top: '5px',
        right: '5px',
        width: '20px',
        height: '20px',
        padding: '0',
        border: 'none',
        background: 'transparent',
        color: 'var(--text-muted)',
        fontSize: '20px',
        lineHeight: '20px',
        cursor: 'pointer',
        display: 'flex',
        alignItems: 'center',
        justifyContent: 'center',
        borderRadius: '50%',
        transition: 'background-color 0.2s, color 0.2s'
    });

    // 添加悬停效果
    closeButton.addEventListener('mouseover', () => {
        closeButton.style.backgroundColor = 'var(--background-modifier-hover)';
        closeButton.style.color = 'var(--text-normal)';
    });

    closeButton.addEventListener('mouseout', () => {
        closeButton.style.backgroundColor = 'transparent';
        closeButton.style.color = 'var(--text-muted)';
    });

    // 定义事件处理函数
    const handleMouseMove = (e) => {
        if (isDragging) {
            const x = e.clientX - offsetX;
            const y = e.clientY - offsetY;

            const maxX = window.innerWidth - popupContainer.offsetWidth / 3;
            const maxY = window.innerHeight - popupContainer.offsetHeight / 3;

            popupContainer.style.left = `${Math.max(0, Math.min(x, maxX))}px`;
            popupContainer.style.top = `${Math.max(0, Math.min(y, maxY))}px`;
            popupContainer.style.transform = 'none';
        }
    };

    const handleMouseUp = () => {
        isDragging = false;
    };

    // 修改关闭按钮的点击处理程序
    closeButton.addEventListener('click', () => {
        // 移除拖拽相关的事件监听
        closeToolbar(handleMouseMove, handleMouseUp, popupContainer);
    });

    // 将命令按钮和关闭按钮添加到弹窗容器中
    popupContainer.appendChild(commandButtonsContainer);
    popupContainer.appendChild(closeButton);

    // 将弹窗添加到页面中
    popupContainer.style.left = `${Math.max(0, Math.min(window.innerWidth * 2/3 - popupContainer.offsetWidth, window.innerWidth - 320))}px`;
    popupContainer.style.top = `${Math.max(0, Math.min(window.innerHeight / 2 - popupContainer.offsetHeight, window.innerHeight - 240))}px`;
    document.body.appendChild(popupContainer);

    // 拖拽功能实现
    let isDragging = false;
    let offsetX, offsetY;

    popupContainer.addEventListener('mousedown', (e) => {
        isDragging = true;
        offsetX = e.clientX - popupContainer.getBoundingClientRect().left;
        offsetY = e.clientY - popupContainer.getBoundingClientRect().top;
    });

    // 拖拽过程的具体处理
    document.addEventListener('mousemove', handleMouseMove);

    document.addEventListener('mouseup', handleMouseUp);

};

function closeToolbar(handleMouseMove, handleMouseUp, popupContainer) {
    document.removeEventListener('mousemove', handleMouseMove);
    document.removeEventListener('mouseup', handleMouseUp);
    // 移除弹窗
    document.body.removeChild(popupContainer);
}

1 个赞

感谢修改,我又问gpt做了一点其他的修改,现在用起来算是很不错了:
screenshots

  • 加一个.json 文件,现在支持加入和删除
  • .json 文件的位置"/"表示仓库的根目录,可以自己改文件的存放地区,在代码的最末端
  • 使用左键执行按键,点击“新建”进行新建,点击右键进行删除

感谢大佬和deepseek coder 的大力支持

以下是代码

module.exports = async function (params) {
    const { app, quickAddApi } = params;

    // 添加在脚本开始处
    const existingToolbar = document.querySelector('.float-toolbar');
    if (existingToolbar) {
        document.body.removeChild(existingToolbar);
        return;
    }

    // 读取命令配置
    let { commands, windowConfig = {} } = await loadCommands();
    const useLastPosition = windowConfig.useLastPosition || false;

    // 创建弹窗容器
    const popupContainer = document.createElement('div');
    popupContainer.classList.add('float-toolbar');
    Object.assign(popupContainer.style, {
        position: 'fixed',
        width: windowConfig.width || '300px',
        height: windowConfig.height || 'auto',
        left: useLastPosition && windowConfig.left ? windowConfig.left : '50%',
        top: useLastPosition && windowConfig.top ? windowConfig.top : '50%',
        transform: useLastPosition ? 'none' : 'translate(-50%, -50%)',
        backgroundColor: 'var(--background-primary)',
        padding: '20px',
        border: '1px solid var(--background-modifier-border)',
        borderRadius: '10px',
        boxShadow: '0 4px 6px rgba(0, 0, 0, 0.3)',
        zIndex: '9999',
        cursor: 'move',
        userSelect: 'none',
        resize: 'none',
        overflow: 'hidden',
    });

    // 创建命令按钮容器
    const commandButtonsContainer = document.createElement('div');
    commandButtonsContainer.classList.add('command-buttons-container');

    // 创建命令按钮
    function createCommandButtons() {
        commandButtonsContainer.innerHTML = ''; // 清空现有按钮
        commands.forEach((cmd) => {
            const button = document.createElement('button');
            button.textContent = cmd.text;
            button.addEventListener('click', async (e) => {
                if (e.button === 0) {
                    // 左键点击
                    try {
                        await app.commands.executeCommandById(cmd.commandId);
                    } catch (error) {
                        console.error(`执行命令 ${cmd.text} 失败:`, error);
                        new Notice(`执行命令 ${cmd.text} 失败,打开控制台查看详情`, 1000);
                    }
                }
            });

            // 右键点击删除按钮
            button.addEventListener('contextmenu', (e) => {
                e.preventDefault();
                // 更新命令列表
                commands = commands.filter((c) => c.text !== cmd.text); // 🚩 现在可以重新赋值
                saveCommands(commands);
                createCommandButtons();
            });

            commandButtonsContainer.appendChild(button);
        });
    }

    createCommandButtons();

    // 创建关闭按钮
    const closeButton = document.createElement('button');
    closeButton.textContent = '×';
    Object.assign(closeButton.style, {
        position: 'absolute',
        top: '5px',
        right: '5px',
        width: '20px',
        height: '20px',
        padding: '0',
        border: 'none',
        background: 'transparent',
        color: 'var(--text-muted)',
        fontSize: '20px',
        lineHeight: '20px',
        cursor: 'pointer',
        display: 'flex',
        alignItems: 'center',
        justifyContent: 'center',
        borderRadius: '50%',
        transition: 'background-color 0.2s, color 0.2s',
    });

    // 添加悬停效果
    closeButton.addEventListener('mouseover', () => {
        closeButton.style.backgroundColor = 'var(--background-modifier-hover)';
        closeButton.style.color = 'var(--text-normal)';
    });

    closeButton.addEventListener('mouseout', () => {
        closeButton.style.backgroundColor = 'transparent';
        closeButton.style.color = 'var(--text-muted)';
    });

    // 定义事件处理函数
    let isDragging = false;
    let offsetX, offsetY;

    const handleMouseMove = (e) => {
        if (isDragging) {
            const x = e.clientX - offsetX;
            const y = e.clientY - offsetY;

            const maxX = window.innerWidth - popupContainer.offsetWidth / 3;
            const maxY = window.innerHeight - popupContainer.offsetHeight / 3;

            popupContainer.style.left = `${Math.max(0, Math.min(x, maxX))}px`;
            popupContainer.style.top = `${Math.max(0, Math.min(y, maxY))}px`;
            popupContainer.style.transform = 'none';
        }
    };

    const handleMouseUp = () => {
        if (isDragging) {
            isDragging = false;
            // 保存新位置
            saveWindowConfig({
                left: popupContainer.style.left,
                top: popupContainer.style.top,
                width: popupContainer.style.width,
                height: popupContainer.style.height,
                useLastPosition: true, // 自动启用记忆位置
            });
        }
    };

    // 修改关闭按钮的点击处理程序
    closeButton.addEventListener('click', () => {
        closeToolbar(handleMouseMove, handleMouseUp, popupContainer);
    });

    // 创建新增命令按钮
    const addCommandButton = document.createElement('button');
    addCommandButton.textContent = '+ 新增命令';
    addCommandButton.style.marginTop = '10px';
    addCommandButton.addEventListener('click', async () => {
        const allCommands = app.commands.listCommands();
        const selectedCommand = await quickAddApi.suggester(
            allCommands.map((cmd) => cmd.name),
            allCommands
        );
        if (!selectedCommand) {
            new Notice('未选择命令');
            return;
        }
        const newCommand = {
            text: selectedCommand.name,
            commandId: selectedCommand.id,
        };
        commands.push(newCommand);
        saveCommands(commands);
        createCommandButtons(); // 刷新按钮
    });

    // 创建调整大小的手柄
    const resizeHandle = document.createElement('div');
    Object.assign(resizeHandle.style, {
        position: 'absolute',
        bottom: '0',
        right: '0',
        width: '10px',
        height: '10px',
        backgroundColor: 'var(--background-modifier-border)',
        cursor: 'se-resize',
    });

    // 拖拽调整大小的逻辑
    let isResizing = false;
    let startX, startY, startWidth, startHeight;

    resizeHandle.addEventListener('mousedown', (e) => {
        isResizing = true;
        startX = e.clientX;
        startY = e.clientY;
        startWidth = parseInt(document.defaultView.getComputedStyle(popupContainer).width, 10);
        startHeight = parseInt(document.defaultView.getComputedStyle(popupContainer).height, 10);
        e.preventDefault(); // 防止文本选中
    });

    document.addEventListener('mousemove', (e) => {
        if (isResizing) {
            const newWidth = startWidth + (e.clientX - startX);
            const newHeight = startHeight + (e.clientY - startY);
            popupContainer.style.width = `${newWidth}px`;
            popupContainer.style.height = `${newHeight}px`;
        }
    });

    document.addEventListener('mouseup', () => {
        if (isResizing) {
            isResizing = false;
            // 保存调整后的尺寸
            saveWindowConfig({
                width: popupContainer.style.width,
                height: popupContainer.style.height,
                left: popupContainer.style.left,
                top: popupContainer.style.top,
            });
        }
    });

    // 将命令按钮、新增按钮、关闭按钮和调整手柄添加到弹窗容器中
    popupContainer.appendChild(commandButtonsContainer);
    popupContainer.appendChild(addCommandButton);
    popupContainer.appendChild(closeButton);
    popupContainer.appendChild(resizeHandle);

    // 将弹窗添加到页面中
    document.body.appendChild(popupContainer);

    // 拖拽功能实现
    popupContainer.addEventListener('mousedown', (e) => {
        if (e.target === resizeHandle) return; // 如果点击的是调整手柄,则不触发拖拽
        isDragging = true;
        offsetX = e.clientX - popupContainer.getBoundingClientRect().left;
        offsetY = e.clientY - popupContainer.getBoundingClientRect().top;
    });

    // 拖拽过程的具体处理
    document.addEventListener('mousemove', handleMouseMove);
    document.addEventListener('mouseup', handleMouseUp);
};

function closeToolbar(handleMouseMove, handleMouseUp, popupContainer) {
    document.removeEventListener('mousemove', handleMouseMove);
    document.removeEventListener('mouseup', handleMouseUp);
    // 移除弹窗
    document.body.removeChild(popupContainer);
}

async function loadCommands() {
    const path = '05-木板/JSetc脚本/commands.json';
    try {
        const content = await app.vault.adapter.read(path);
        // 兼容旧格式(纯数组)和新格式(带配置的对象)
        const result = JSON.parse(content);

        return Array.isArray(result)
            ? { commands: result }
            : {
                  commands: result.commands || [],
                  windowConfig: result.windowConfig || {},
              };
    } catch (error) {
        console.error('读取命令文件失败:', error);
        return { commands: [] };
    }
}

async function saveWindowConfig(config) {
    const path = '05-木板/JSetc脚本/commands.json';
    try {
        const existing = await loadCommands();
        const newData = {
            commands: existing.commands,
            windowConfig: {
                ...existing.windowConfig,
                ...config,
            },
        };
        await app.vault.adapter.write(path, JSON.stringify(newData, null, 2));
    } catch (error) {
        console.error('保存窗口配置失败:', error);
    }
}

async function saveCommands(commands) {
    const path = '05-木板/JSetc脚本/commands.json';
    try {
        const existing = await loadCommands();
        const newData = {
            commands,
            windowConfig: existing.windowConfig || {},
        };
        await app.vault.adapter.write(path, JSON.stringify(newData, null, 2));
    } catch (error) {
        console.error('保存命令文件失败:', error);
    }
}

增加了位置保存和大小保存吗,可以通过修改json文件的位置存储部分进行修改:

  "windowConfig": {
    "left": "428px",
    "top": "846px",
    "width": "582px",
    "height": "143px",
    "useLastPosition": true
  }
1 个赞

WOW 现在更方便了!很 Nice!
既然 json 都有了,感觉打磨打磨可以直接做一个新插件了哈哈~
让 AI 老师再加把劲!

1 个赞

谢谢谢谢,不过我觉得js quickadd就挺好了,也不占启动时间

1 个赞

好的好的,那我可以用这个点子:bulb:去尝试做个插件练手吗?

1 个赞

当然 :smile: , 期待;不过我后来又改了一点东西,你要用的话把baseline 换成修改后的吧

1 个赞

厉害了,没想到可以这样使用。

我是通过修改Note toolbar的样式:Obsidian插件样式:定制Note Toolbar布局以模拟Cmenu显示 - 经验分享 - Obsidian 中文论坛,使它像Cmenu那样将常用按钮悬浮在笔记中间,也可以悬浮按钮,效果如下:


修改按钮命令也比较方便,就是不能进行拖拽移动。

1 个赞

可以对过长的命令文本进行一次文本替换

我针对楼主的代码进行了一点修改,增加了输入框可以设置命令名,以及可以手动设置样式
图片
其中新增命令这个按钮的样式我为了省事,直接写死的,各位想改的可以改一下
注:我遇到问题,这个窗口拖动到边边角角之后,以后打开也在边边角角,以为我代码出问题了

var path = '00-MO/4.脚本/commands.json';//保存的配置json路径
var flag = true;//是否点击命令按钮便关闭弹窗


module.exports = async function (params) {
    const { app, quickAddApi } = params;

    // 添加在脚本开始处
    const existingToolbar = document.querySelector('.float-toolbar');
    if (existingToolbar) {
        document.body.removeChild(existingToolbar);
        return;
    }

    // 读取命令配置
    let { commands, windowConfig,inputBoxConfig = {} } = await loadCommands();
    const useLastPosition = windowConfig.useLastPosition || false;

    // 创建弹窗容器
    const popupContainer = document.createElement('div');
    popupContainer.setAttribute('id', 'quickAddJsCommandsDiv');
    popupContainer.classList.add('float-toolbar');
    Object.assign(popupContainer.style, {
        position: 'fixed',
        width: windowConfig.width || '300px',
        height: windowConfig.height || 'auto',
        left: useLastPosition && windowConfig.left ? windowConfig.left : '50%',
        top: useLastPosition && windowConfig.top ? windowConfig.top : '50%',
        transform: useLastPosition ? 'none' : 'translate(-50%, -50%)',
        backgroundColor: 'var(--background-primary)',
        padding: '20px',
        border: '1px solid var(--background-modifier-border)',
        borderRadius: '10px',
        boxShadow: '0 4px 6px rgba(0, 0, 0, 0.3)',
        zIndex: '9999',
        cursor: 'move',
        userSelect: 'none',
        resize: 'none',
        overflow: 'hidden',
    });

    // 创建命令按钮容器
    const commandButtonsContainer = document.createElement('div');
    commandButtonsContainer.classList.add('command-buttons-container');

    // 创建命令按钮
    function createCommandButtons() {
        commandButtonsContainer.innerHTML = ''; // 清空现有按钮
        commands.forEach((cmd) => {
            const button = document.createElement('button');
            button.textContent = cmd.text;
            button.className = cmd.class;
            button.addEventListener('click', async (e) => {
                if (e.button === 0) {
                    // 左键点击
                    try {
                        await app.commands.executeCommandById(cmd.commandId);
                        if(flag){
                            document.body.removeChild(document.getElementById('quickAddJsCommandsDiv'));
                        }
                    } catch (error) {
                        console.error(`执行命令 ${cmd.text} 失败:`, error);
                        new Notice(`执行命令 ${cmd.text} 失败,打开控制台查看详情`, 1000);
                    }
                }
            });

            // 右键点击删除按钮
            button.addEventListener('contextmenu', (e) => {
                e.preventDefault();
                // 更新命令列表
                commands = commands.filter((c) => c.text !== cmd.text); // 🚩 现在可以重新赋值
                saveCommands(commands);
                createCommandButtons();
            });

            commandButtonsContainer.appendChild(button);
        });
    }

    createCommandButtons();

    // 创建关闭按钮
    const closeButton = document.createElement('button');
    closeButton.textContent = '×';
    Object.assign(closeButton.style, {
        position: 'absolute',
        top: '5px',
        right: '5px',
        width: '20px',
        height: '20px',
        padding: '0',
        border: 'none',
        background: 'transparent',
        color: 'var(--text-muted)',
        fontSize: '20px',
        lineHeight: '20px',
        cursor: 'pointer',
        display: 'flex',
        alignItems: 'center',
        justifyContent: 'center',
        borderRadius: '50%',
        transition: 'background-color 0.2s, color 0.2s',
    });

    // 添加悬停效果
    closeButton.addEventListener('mouseover', () => {
        closeButton.style.backgroundColor = 'var(--background-modifier-hover)';
        closeButton.style.color = 'var(--text-normal)';
    });

    closeButton.addEventListener('mouseout', () => {
        closeButton.style.backgroundColor = 'transparent';
        closeButton.style.color = 'var(--text-muted)';
    });

    // 定义事件处理函数
    let isDragging = false;
    let offsetX, offsetY;

    const handleMouseMove = (e) => {
        if (isDragging) {
            const x = e.clientX - offsetX;
            const y = e.clientY - offsetY;

            const maxX = window.innerWidth - popupContainer.offsetWidth / 3;
            const maxY = window.innerHeight - popupContainer.offsetHeight / 3;

            popupContainer.style.left = `${Math.max(0, Math.min(x, maxX))}px`;
            popupContainer.style.top = `${Math.max(0, Math.min(y, maxY))}px`;
            popupContainer.style.transform = 'none';
        }
    };

    const handleMouseUp = () => {
        if (isDragging) {
            isDragging = false;
            // 保存新位置
            saveWindowConfig({
                left: popupContainer.style.left,
                top: popupContainer.style.top,
                width: popupContainer.style.width,
                height: popupContainer.style.height,
                useLastPosition: true, // 自动启用记忆位置
            });
        }
    };

    // 修改关闭按钮的点击处理程序
    closeButton.addEventListener('click', () => {
        closeToolbar(handleMouseMove, handleMouseUp, popupContainer);
    });

    // 创建新增命令按钮
    const addCommandButton = document.createElement('button');
    addCommandButton.textContent = '新增命令';
    //设置新增命令样式
    // addCommandButton.classList.add('circle_btn','g_lightgreen');
    addCommandButton.style='text-align: center; text-transform: uppercase; cursor: pointer; font-size: 20px; letter-spacing: 4px; position: relative; background-color:rgb(37, 152, 234); border: none; color: #fff; padding: 20px; width: 100%; transition-duration: 0.4s; overflow: hidden; box-shadow: 0 5px 15px #193047; border-radius: 4px;';
    // addCommandButton.style.marginTop = '10px';
    addCommandButton.addEventListener('click', async () => {
        const allCommands = app.commands.listCommands();
        const selectedCommand = await quickAddApi.suggester(
            allCommands.map((cmd) => cmd.name),
            allCommands
        );
        if (!selectedCommand) {
            new Notice('未选择命令');
            return;
        }
        const newCommand = {
            text: selectedCommand.name,
            commandId: selectedCommand.id,
        };
    
        var element = document.getElementById('quickAddJsCommandsDiv');
        //隐藏命令框
        element.style.display = 'none';
        const popupInput = document.createElement('input');
        popupInput.id='inputbox';

        let useLastPosition = inputBoxConfig.useLastPosition || false;
        let inputName
        
        Object.assign(popupInput.style, {
            position: 'fixed',
            width: inputBoxConfig.width || '300px',
            height: inputBoxConfig.height || 'auto',
            left: useLastPosition && inputBoxConfig.left ? inputBoxConfig.left : '50%',
            top: useLastPosition && inputBoxConfig.top ? inputBoxConfig.top : '50%',
            transform: useLastPosition ? 'none' : 'translate(-50%, -50%)',
            backgroundColor: 'var(--background-primary)',
            padding: '20px',
            border: '1px solid var(--background-modifier-border)',
            borderRadius: '10px',
            boxShadow: '0 4px 6px rgba(0, 0, 0, 0.3)',
            zIndex: '9999',
            cursor: 'move',
            userSelect: 'none',
            resize: 'none',
            overflow: 'hidden',
        });
        popupInput.addEventListener('keydown', function(event) {
            // 检查是否按下了回车键
            if (event.keyCode === 13) {
            // 获取输入框的值并赋给变量
            inputName = popupInput.value;
        
            //恢复命令面板
            element.style.display = 'block';

            // 关闭输入框,例如从文档中移除
            document.body.removeChild(popupInput);

            if (/^\s*$/.test(inputName)) {
                console.log("字符串为空或只包含空白字符");
            }else{
                newCommand.text=inputName
            }
            
            commands.push(newCommand);
            saveCommands(commands);
            createCommandButtons(); // 刷新按钮
            }
        });

        // 将弹窗添加到页面中
        document.body.appendChild(popupInput);

        // 定义事件处理函数
        let isDragging = false;
        let offsetX, offsetY;
        // 拖拽功能实现
        popupInput.addEventListener('mousedown', (e) => {
            // if (e.target === resizeHandle) return; // 如果点击的是调整手柄,则不触发拖拽
            isDragging = true;
            offsetX = e.clientX - popupInput.getBoundingClientRect().left;
            offsetY = e.clientY - popupInput.getBoundingClientRect().top;
        });

        const handleMouseMove = (e) => {
            if (isDragging) {
                const x = e.clientX - offsetX;
                const y = e.clientY - offsetY;

                const maxX = window.innerWidth - popupInput.offsetWidth / 3;
                const maxY = window.innerHeight - popupInput.offsetHeight / 3;

                popupInput.style.left = `${Math.max(0, Math.min(x, maxX))}px`;
                popupInput.style.top = `${Math.max(0, Math.min(y, maxY))}px`;
                popupInput.style.transform = 'none';
            }
        };

        const handleMouseUp = () => {
            if (isDragging) {
                isDragging = false;
                // 保存新位置
                saveInputBoxConfig({
                    left: popupInput.style.left,
                    top: popupInput.style.top,
                    width: popupInput.style.width,
                    height: popupInput.style.height,
                    useLastPosition: true, // 自动启用记忆位置
                });
            }
        };
        document.addEventListener('mousemove', handleMouseMove);
        document.addEventListener('mouseup', handleMouseUp);
    });

    // 创建调整大小的手柄
    const resizeHandle = document.createElement('div');
    Object.assign(resizeHandle.style, {
        position: 'absolute',
        bottom: '0',
        right: '0',
        width: '10px',
        height: '10px',
        backgroundColor: 'var(--background-modifier-border)',
        cursor: 'se-resize',
    });

    // 拖拽调整大小的逻辑
    let isResizing = false;
    let startX, startY, startWidth, startHeight;

    resizeHandle.addEventListener('mousedown', (e) => {
        isResizing = true;
        startX = e.clientX;
        startY = e.clientY;
        startWidth = parseInt(document.defaultView.getComputedStyle(popupContainer).width, 10);
        startHeight = parseInt(document.defaultView.getComputedStyle(popupContainer).height, 10);
        e.preventDefault(); // 防止文本选中
    });

    document.addEventListener('mousemove', (e) => {
        if (isResizing) {
            const newWidth = startWidth + (e.clientX - startX);
            const newHeight = startHeight + (e.clientY - startY);
            popupContainer.style.width = `${newWidth}px`;
            popupContainer.style.height = `${newHeight}px`;
        }
    });

    document.addEventListener('mouseup', () => {
        if (isResizing) {
            isResizing = false;
            // 保存调整后的尺寸
            saveWindowConfig({
                width: popupContainer.style.width,
                height: popupContainer.style.height,
                left: popupContainer.style.left,
                top: popupContainer.style.top,
            });
        }
    });

    // 将命令按钮、新增按钮、关闭按钮和调整手柄添加到弹窗容器中
    popupContainer.appendChild(commandButtonsContainer);
    popupContainer.appendChild(addCommandButton);
    popupContainer.appendChild(closeButton);
    popupContainer.appendChild(resizeHandle);

    // 将弹窗添加到页面中
    document.body.appendChild(popupContainer);

    // 拖拽功能实现
    popupContainer.addEventListener('mousedown', (e) => {
        if (e.target === resizeHandle) return; // 如果点击的是调整手柄,则不触发拖拽
        isDragging = true;
        offsetX = e.clientX - popupContainer.getBoundingClientRect().left;
        offsetY = e.clientY - popupContainer.getBoundingClientRect().top;
    });

    // 拖拽过程的具体处理
    document.addEventListener('mousemove', handleMouseMove);
    document.addEventListener('mouseup', handleMouseUp);
};

function closeToolbar(handleMouseMove, handleMouseUp, popupContainer) {
    document.removeEventListener('mousemove', handleMouseMove);
    document.removeEventListener('mouseup', handleMouseUp);
    // 移除弹窗
    document.body.removeChild(popupContainer);
}

async function loadCommands() {
    try {
        const content = await app.vault.adapter.read(path);
        // 兼容旧格式(纯数组)和新格式(带配置的对象)
        const result = JSON.parse(content);
        
        return Array.isArray(result)
            ? { commands: result }
            : {
                  commands: result.commands || [],
                  windowConfig: result.windowConfig || {},
                  inputBoxConfig: result.inputBoxConfig || {},
              };
    } catch (error) {
        console.error('读取命令文件失败:', error);
        return { commands: [] };
    }
}

async function saveInputBoxConfig(config) {
    try {
        const existing = await loadCommands();
        const newData = {
            commands: existing.commands,
            windowConfig: existing.windowConfig || {},
            inputBoxConfig: {
                ...existing.inputBoxConfig,
                ...config,
            },
        };
        await app.vault.adapter.write(path, JSON.stringify(newData, null, 2));
    } catch (error) {
        console.error('保存输入框配置失败:', error);
    }
}

async function saveWindowConfig(config) {
    try {
        const existing = await loadCommands();
        const newData = {
            commands: existing.commands,
            inputBoxConfig: existing.inputBoxConfig || {},
            windowConfig: {
                ...existing.windowConfig,
                ...config,
            },
        };
        await app.vault.adapter.write(path, JSON.stringify(newData, null, 2));
    } catch (error) {
        console.error('保存窗口配置失败:', error);
    }
}

async function saveCommands(commands) {
    try {
        const existing = await loadCommands();
        const newData = {
            commands,
            inputBoxConfig: existing.inputBoxConfig || {},
            windowConfig: existing.windowConfig || {},
        };
        await app.vault.adapter.write(path, JSON.stringify(newData, null, 2));
    } catch (error) {
        console.error('保存命令文件失败:', error);
    }
}

其中由于按钮样式有很多种,需要在json文件中自己手动设置
就是class这个属性是修改样式的,默认不会生成,需要手动在json文件中添加
(主要是个人感觉再弄个二级输入框有些没意义了)

{
  "commands": [
    {
      "text": "闪记记录",
      "class": "grad_button g_yellow",
      "commandId": "quickadd:choice:bf0c6a27-4cc0-4f5c-a8d6-081232b703a0"
    },
    {
      "text": "卡片笔记",
      "class": "grad_button g_yellow",
      "commandId": "quickadd:choice:52069ba0-f7b5-4dd0-8aca-5cbdb01e11e1"
    },
    {
      "text": "他人想法",
      "class": "grad_button g_yellow",
      "commandId": "quickadd:choice:9275f56a-f387-4434-b049-a546747089fb"
    },
    {
      "text": "灵感记录",
      "class": "grad_button g_yellow",
      "commandId": "quickadd:choice:42a9e6c3-fa19-4e09-8f7d-2a2b5fa48120"
    },
    {
      "text": "教程整理",
      "class": "grad_button g_yellow",
      "commandId": "quickadd:choice:8cd67672-77f9-4f61-ab00-3aaa99643b35"
    },
    {
      "text": "工作任务",
      "class": "grad_button g_purple",
      "commandId": "quickadd:choice:0802a7ff-35fc-419c-a9e7-ee2e6b8bdeee"
    }
  ],
  "inputBoxConfig": {
    "left": "50%",
    "top": "50%",
    "width": "300px",
    "height": "auto",
    "useLastPosition": true
  },
  "windowConfig": {
    "left": "1205px",
    "top": "645.5px",
    "width": "266px",
    "height": "238px",
    "useLastPosition": true
  }
}
1 个赞

因为我设置了一个保存位置的变量在json文件里面

你手动在js脚本里面切换为 false就始终会在json配置坐标打开了。
image