求助能否将“优化排版”按钮从折叠菜单中放到外面来

image.png

  • 思源笔记

    思源笔记是一款隐私优先的个人知识管理系统,支持完全离线使用,同时也支持端到端加密同步。

    融合块、大纲和双向链接,重构你的思维。

    25517 引用 • 105527 回帖 • 1 关注
  • Q&A

    提问之前请先看《提问的智慧》,好的问题比好的答案更有价值。

    9762 引用 • 44400 回帖 • 88 关注

相关帖子

被采纳的回答
  • wilsons 2 2 赞同

    最完美的方式,当如是也

    image.png

    // 把优化排版菜单移动到文档导航条
    {
        const main = (protyle)=>{
            // 发布服务下不显示
            if(window.siyuan.config.readonly) return;
            
            if(protyle?.querySelector('.protyle-breadcrumb [data-type="optimizeTypography"]')) return;
            const exitFocusBtn = protyle.querySelector('.protyle-breadcrumb [data-type="exit-focus"]');
            if(!exitFocusBtn) return;
            const optimizeHtml = `<button class="block__icon fn__flex-center ariaLabel" aria-label="优化排版" data-type="optimizeTypography"><svg><use xlink:href="#iconFormat"></use></svg></button>`;
            exitFocusBtn.insertAdjacentHTML('afterend', optimizeHtml);
            const optimizeBtn = protyle.querySelector('.protyle-breadcrumb [data-type="optimizeTypography"]');
            if(!optimizeBtn) return;
            optimizeBtn.addEventListener('click', () => {
                // 锁定状态下不可修改
                const icon = protyle?.querySelector('button[data-type="readonly"] use')?.getAttributeNS('http://www.w3.org/1999/xlink', 'href');
                if(icon === '#iconLock') {
                    showMessage('锁定状态不可用');
                    return
                }
                // 优化排版
                // see https://github.com/siyuan-note/siyuan/blob/a01523dc98799590396ffcabdc90b2dc6efe8474/app/src/protyle/breadcrumb/index.ts#L415
                requestApi("/api/format/autoSpace", {
                    id: protyle?.querySelector('.protyle-title')?.dataset?.nodeId
                });
            });
        };
    
        // 监听protyle加载
        whenElementExist('.protyle:not(.fn__none)').then(main);
        observeProtyleLoad(main);
    
        function showMessage(message, isError = false, delay = 7000) {
            return fetch('/api/notification/' + (isError ? 'pushErrMsg' : 'pushMsg'), {
                "method": "POST",
                "body": JSON.stringify({"msg": message, "timeout": delay})
            });
        }
        async function requestApi(url, data, method = 'POST') {
            return await (await fetch(url, {method: method, body: JSON.stringify(data||{})})).json();
        }
        function whenElementExist(selector, node) {
            return new Promise(resolve => {
                const check = () => {
                    const el = typeof selector==='function'?selector():(node||document).querySelector(selector);
                    if (el) resolve(el); else requestAnimationFrame(check);
                };
                check();
            });
        }
        function observeProtyleLoad(callback, parentElement) {
            // 如果 parentElement 是字符串,则将其转换为 DOM 元素
            if (typeof parentElement === 'string') {
                parentElement = document.querySelector(parentElement);
            }
            // 创建一个 MutationObserver 实例
            const observer = new MutationObserver((mutationsList) => {
                mutationsList.forEach((mutation) => {
                    // 检查是否是属性变化并且变化的属性是 class
                    if (mutation.type === 'attributes' && mutation.attributeName === 'class') {
                        const targetElement = mutation.target; // 发生变化的目标元素
        
                        // 判断目标元素是否匹配指定选择器 .protyle:not(.fn__none)
                        if (targetElement.matches('.protyle:not(.fn__none)')) {
                            // 触发回调
                            callback(targetElement);
                        }
                    }
                });
            });
            // 配置观察选项
            const config = {
                attributes: true, // 监听属性变化
                attributeFilter: ['class'], // 仅监听 class 属性
                subtree: true, // 监听父容器及其所有后代元素
            };
            // 启动观察,默认监听 document.body 或指定的父容器
            observer.observe(parentElement || document.body, config);
        }
    }
    

    类似文章推荐 https://pipe.b3log.org/blogs/wilsons/articles/2025/05/13/1744703423359

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...
  • EmptyLight 1

    我记得优化排版可以通过快捷键设置分配快捷键

  • 其他回帖
  • wilsons 2 2 赞同 3 评论

    最完美的方式,当如是也

    image.png

    // 把优化排版菜单移动到文档导航条
    {
        const main = (protyle)=>{
            // 发布服务下不显示
            if(window.siyuan.config.readonly) return;
            
            if(protyle?.querySelector('.protyle-breadcrumb [data-type="optimizeTypography"]')) return;
            const exitFocusBtn = protyle.querySelector('.protyle-breadcrumb [data-type="exit-focus"]');
            if(!exitFocusBtn) return;
            const optimizeHtml = `<button class="block__icon fn__flex-center ariaLabel" aria-label="优化排版" data-type="optimizeTypography"><svg><use xlink:href="#iconFormat"></use></svg></button>`;
            exitFocusBtn.insertAdjacentHTML('afterend', optimizeHtml);
            const optimizeBtn = protyle.querySelector('.protyle-breadcrumb [data-type="optimizeTypography"]');
            if(!optimizeBtn) return;
            optimizeBtn.addEventListener('click', () => {
                // 锁定状态下不可修改
                const icon = protyle?.querySelector('button[data-type="readonly"] use')?.getAttributeNS('http://www.w3.org/1999/xlink', 'href');
                if(icon === '#iconLock') {
                    showMessage('锁定状态不可用');
                    return
                }
                // 优化排版
                // see https://github.com/siyuan-note/siyuan/blob/a01523dc98799590396ffcabdc90b2dc6efe8474/app/src/protyle/breadcrumb/index.ts#L415
                requestApi("/api/format/autoSpace", {
                    id: protyle?.querySelector('.protyle-title')?.dataset?.nodeId
                });
            });
        };
    
        // 监听protyle加载
        whenElementExist('.protyle:not(.fn__none)').then(main);
        observeProtyleLoad(main);
    
        function showMessage(message, isError = false, delay = 7000) {
            return fetch('/api/notification/' + (isError ? 'pushErrMsg' : 'pushMsg'), {
                "method": "POST",
                "body": JSON.stringify({"msg": message, "timeout": delay})
            });
        }
        async function requestApi(url, data, method = 'POST') {
            return await (await fetch(url, {method: method, body: JSON.stringify(data||{})})).json();
        }
        function whenElementExist(selector, node) {
            return new Promise(resolve => {
                const check = () => {
                    const el = typeof selector==='function'?selector():(node||document).querySelector(selector);
                    if (el) resolve(el); else requestAnimationFrame(check);
                };
                check();
            });
        }
        function observeProtyleLoad(callback, parentElement) {
            // 如果 parentElement 是字符串,则将其转换为 DOM 元素
            if (typeof parentElement === 'string') {
                parentElement = document.querySelector(parentElement);
            }
            // 创建一个 MutationObserver 实例
            const observer = new MutationObserver((mutationsList) => {
                mutationsList.forEach((mutation) => {
                    // 检查是否是属性变化并且变化的属性是 class
                    if (mutation.type === 'attributes' && mutation.attributeName === 'class') {
                        const targetElement = mutation.target; // 发生变化的目标元素
        
                        // 判断目标元素是否匹配指定选择器 .protyle:not(.fn__none)
                        if (targetElement.matches('.protyle:not(.fn__none)')) {
                            // 触发回调
                            callback(targetElement);
                        }
                    }
                });
            });
            // 配置观察选项
            const config = {
                attributes: true, // 监听属性变化
                attributeFilter: ['class'], // 仅监听 class 属性
                subtree: true, // 监听父容器及其所有后代元素
            };
            // 启动观察,默认监听 document.body 或指定的父容器
            observer.observe(parentElement || document.body, config);
        }
    }
    

    类似文章推荐 https://pipe.b3log.org/blogs/wilsons/articles/2025/05/13/1744703423359

    1 回复
    5 操作
    wilsons 在 2025-05-14 05:31:44 更新了该回帖
    wilsons 在 2025-04-16 09:29:39 更新了该回帖
    wilsons 在 2025-04-15 22:09:54 更新了该回帖
    wilsons 在 2025-04-15 15:53:51 更新了该回帖 wilsons 在 2025-04-15 13:28:24 更新了该回帖
    真不错,还顺便学会了用 pipe
    ACai 1
    @ACai 感谢打赏!我最近写了个思源文档直接发布到 pipe 的,目前仅单文档发布,后续可能增加批量和自动发布,如果有兴趣,好了 @ 你下。
    wilsons
    @wilsons 可以可以
    ACai
  • 倒是不难,但 JS 容易出 BUG,我推荐你给优化排版设置一个快捷键

  • lichlaughing

    厉害厉害,还有个问题就是,文档锁定的状态下该按钮也能发挥作用。能否让该排版按钮在锁定状态下隐藏(解锁文章显示)或者锁定状下失效(不可以点击)解锁状态下可以点击 😄

    1 回复
  • 查看全部回帖

推荐标签 标签

  • Linux

    Linux 是一套免费使用和自由传播的类 Unix 操作系统,是一个基于 POSIX 和 Unix 的多用户、多任务、支持多线程和多 CPU 的操作系统。它能运行主要的 Unix 工具软件、应用程序和网络协议,并支持 32 位和 64 位硬件。Linux 继承了 Unix 以网络为核心的设计思想,是一个性能稳定的多用户网络操作系统。

    952 引用 • 944 回帖 • 1 关注
  • JSON

    JSON (JavaScript Object Notation)是一种轻量级的数据交换格式。易于人类阅读和编写。同时也易于机器解析和生成。

    52 引用 • 190 回帖
  • RabbitMQ

    RabbitMQ 是一个开源的 AMQP 实现,服务器端用 Erlang 语言编写,支持多种语言客户端,如:Python、Ruby、.NET、Java、C、PHP、ActionScript 等。用于在分布式系统中存储转发消息,在易用性、扩展性、高可用性等方面表现不俗。

    49 引用 • 60 回帖 • 344 关注
  • AngularJS

    AngularJS 诞生于 2009 年,由 Misko Hevery 等人创建,后为 Google 所收购。是一款优秀的前端 JS 框架,已经被用于 Google 的多款产品当中。AngularJS 有着诸多特性,最为核心的是:MVC、模块化、自动化双向数据绑定、语义化标签、依赖注入等。2.0 版本后已经改名为 Angular。

    12 引用 • 50 回帖 • 511 关注
  • Solo

    Solo 是一款小而美的开源博客系统,专为程序员设计。Solo 有着非常活跃的社区,可将文章作为帖子推送到社区,来自社区的回帖将作为博客评论进行联动(具体细节请浏览 B3log 构思 - 分布式社区网络)。

    这是一种全新的网络社区体验,让热爱记录和分享的你不再感到孤单!

    1441 引用 • 10069 回帖 • 496 关注
  • OneDrive
    2 引用
  • 国际化

    i18n(其来源是英文单词 internationalization 的首末字符 i 和 n,18 为中间的字符数)是“国际化”的简称。对程序来说,国际化是指在不修改代码的情况下,能根据不同语言及地区显示相应的界面。

    8 引用 • 26 回帖 • 4 关注
  • Sublime

    Sublime Text 是一款可以用来写代码、写文章的文本编辑器。支持代码高亮、自动完成,还支持通过插件进行扩展。

    10 引用 • 5 回帖 • 2 关注
  • QQ

    1999 年 2 月腾讯正式推出“腾讯 QQ”,在线用户由 1999 年的 2 人(马化腾和张志东)到现在已经发展到上亿用户了,在线人数超过一亿,是目前使用最广泛的聊天软件之一。

    45 引用 • 557 回帖 • 3 关注
  • Flutter

    Flutter 是谷歌的移动 UI 框架,可以快速在 iOS 和 Android 上构建高质量的原生用户界面。 Flutter 可以与现有的代码一起工作,它正在被越来越多的开发者和组织使用,并且 Flutter 是完全免费、开源的。

    39 引用 • 92 回帖 • 4 关注
  • CongSec

    本标签主要用于分享网络空间安全专业的学习笔记

    1 引用 • 1 回帖 • 31 关注
  • 职场

    找到自己的位置,萌新烦恼少。

    127 引用 • 1708 回帖
  • JWT

    JWT(JSON Web Token)是一种用于双方之间传递信息的简洁的、安全的表述性声明规范。JWT 作为一个开放的标准(RFC 7519),定义了一种简洁的,自包含的方法用于通信双方之间以 JSON 的形式安全的传递信息。

    20 引用 • 15 回帖 • 22 关注
  • Jenkins

    Jenkins 是一套开源的持续集成工具。它提供了非常丰富的插件,让构建、部署、自动化集成项目变得简单易用。

    54 引用 • 37 回帖
  • JetBrains

    JetBrains 是一家捷克的软件开发公司,该公司位于捷克的布拉格,并在俄国的圣彼得堡及美国麻州波士顿都设有办公室,该公司最为人所熟知的产品是 Java 编程语言开发撰写时所用的集成开发环境:IntelliJ IDEA

    18 引用 • 54 回帖 • 2 关注
  • 人工智能

    人工智能(Artificial Intelligence)是研究、开发用于模拟、延伸和扩展人的智能的理论、方法、技术及应用系统的一门技术科学。

    170 引用 • 315 回帖
  • 知乎

    知乎是网络问答社区,连接各行各业的用户。用户分享着彼此的知识、经验和见解,为中文互联网源源不断地提供多种多样的信息。

    10 引用 • 66 回帖
  • Sandbox

    如果帖子标签含有 Sandbox ,则该帖子会被视为“测试帖”,主要用于测试社区功能,排查 bug 等,该标签下内容不定期进行清理。

    434 引用 • 1250 回帖 • 593 关注
  • 单点登录

    单点登录(Single Sign On)是目前比较流行的企业业务整合的解决方案之一。SSO 的定义是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。

    9 引用 • 25 回帖
  • Webswing

    Webswing 是一个能将任何 Swing 应用通过纯 HTML5 运行在浏览器中的 Web 服务器,详细介绍请看 将 Java Swing 应用变成 Web 应用

    1 引用 • 15 回帖 • 637 关注
  • V2Ray
    1 引用 • 15 回帖
  • 自由行
  • GitLab

    GitLab 是利用 Ruby 一个开源的版本管理系统,实现一个自托管的 Git 项目仓库,可通过 Web 界面操作公开或私有项目。

    46 引用 • 72 回帖
  • 小薇

    小薇是一个用 Java 写的 QQ 聊天机器人 Web 服务,可以用于社群互动。

    由于 Smart QQ 从 2019 年 1 月 1 日起停止服务,所以该项目也已经停止维护了!

    35 引用 • 468 回帖 • 763 关注
  • WebClipper

    Web Clipper 是一款浏览器剪藏扩展,它可以帮助你把网页内容剪藏到本地。

    3 引用 • 9 回帖 • 1 关注
  • 智能合约

    智能合约(Smart contract)是一种旨在以信息化方式传播、验证或执行合同的计算机协议。智能合约允许在没有第三方的情况下进行可信交易,这些交易可追踪且不可逆转。智能合约概念于 1994 年由 Nick Szabo 首次提出。

    1 引用 • 11 回帖 • 2 关注
  • Bootstrap

    Bootstrap 是 Twitter 推出的一个用于前端开发的开源工具包。它由 Twitter 的设计师 Mark Otto 和 Jacob Thornton 合作开发,是一个 CSS / HTML 框架。

    18 引用 • 33 回帖 • 647 关注