Skip to content

支持行级样式格式刷 #12732

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
TCOTC opened this issue Oct 7, 2024 · 4 comments
Closed

支持行级样式格式刷 #12732

TCOTC opened this issue Oct 7, 2024 · 4 comments

Comments

@TCOTC
Copy link
Contributor

TCOTC commented Oct 7, 2024

背景

大量的用户需求:

方案

格式刷跟 #8554 的跨块设置文本样式不太一样,交互应该是鼠标选中到哪里哪里的样式就会改变。

选中带样式的文本之后应该在工具栏显示一个格式刷按钮:

image

  • 单击格式刷按钮之后选中文本应用相同的行级样式;
  • 双击格式刷按钮之后工具栏移动到编辑器左上角,然后可以多次选中文本应用相同的行级样式,再次点击格式刷按钮即可暂停格式刷
  • 点击非编辑器区域后即暂停格式刷
@Zenchao
Copy link

Zenchao commented Oct 8, 2024

支持

@Achuan-2
Copy link
Member

Achuan-2 commented Oct 10, 2024

简单写了一个插件框架代码
目前有两个问题不知道怎么解决

  1. document.addEventListener('mouseup') 如何获取思源的protyle事件
  2. 行内数学公式的window.getSelection()..getRangeAt(0)..startContainer; 获取的不是行内数学公式dom,而是前面的span标签
import {
    Plugin,
    getFrontend,
    getBackend,
    IModel,
    Protyle,
    IProtyle,
    Toolbar
} from "siyuan";
import "./index.scss";

const STORAGE_NAME = "menu-config";
const TAB_TYPE = "custom_tab";
const DOCK_TYPE = "dock_tab";

export default class PluginSample extends Plugin {
    private customTab: () => IModel;
    private isMobile: boolean;
    private formatPainterEnable: boolean = false;
    private formatData: { dataSubtype: string, style: string } | null = null;
    private protyle: IProtyle;
    onload() {
        this.data[STORAGE_NAME] = { readonlyText: "Readonly" };
        this.protyleOptions = {
            toolbar: ["block-ref",
                "a",
                "|",
                "text",
                "strong",
                "em",
                "u",
                "s",
                "mark",
                "sup",
                "sub",
                "clear",
                "|",
                "code",
                "kbd",
                "tag",
                "inline-math",
                "inline-memo",
                "|",
                {
                    name: "format-painter",
                    icon: "iconFormat",
                    tipPosition: "n",
                    tip: this.i18n.tips,
                    click: (protyle: Protyle) => {
                        this.protyle = protyle.protyle;
                        if (!this.formatPainterEnable) {
                            const selectedInfo = this.getSelectedParentHtml();
                            if (selectedInfo) {
                                this.formatData = {
                                    dataSubtype: selectedInfo.dataSubtype,
                                    style: selectedInfo.style
                                };
                                console.log(this.formatData);
                                this.formatPainterEnable = true;
                                console.log("Format Painter Enabled");
                            }
                        }
                    }
                }
            ],
        };

        document.addEventListener('mouseup', (event) => {
            if (this.formatPainterEnable && this.formatData) {
                const selection = window.getSelection();
                if (selection && selection.rangeCount > 0) {
                    const range = selection.getRangeAt(0);
                    const selectedText = range.toString();
                    if (selectedText) {
                        // Apply the stored format to the selected text
                        if (this.formatData.dataSubtype) {
                            this.protyle.toolbar.setInlineMark(this.protyle, this.formatData.dataSubtype, "range");
                        }
                        if (this.formatData.style) {
                            this.protyle.toolbar.setInlineMark(this.protyle, "text", "range", { "type": "style1", "color": this.formatData.style });
                        }
                        console.log("Format applied to selected text");
                    }
                }
            }
        });

        document.addEventListener('keydown', (event) => {
            if (event.key === 'Escape') {
                this.formatPainterEnable = false;
                this.formatData = null;
                console.log("Format Painter Disabled");
            }
        });

        console.log(this.i18n.helloPlugin);
    }

    getSelectedParentHtml() {
        const selection = window.getSelection();
        if (selection.rangeCount > 0) {
            const range = selection.getRangeAt(0);
            const selectedNode = range.startContainer;
            const startNode = selectedNode.nodeType === Node.TEXT_NODE ? selectedNode.parentNode : selectedNode;
            let parentElement = selectedNode.parentElement;
            while (parentElement && !parentElement.hasAttribute("data-type")) {
                parentElement = parentElement.parentElement;
            }
            if (parentElement) {
                const result = {
                    html: parentElement.outerHTML,
                    dataSubtype: parentElement.getAttribute("data-type"),
                    style: parentElement.getAttribute("style")
                };
                return result;
            }
        }
        return null;
    }

    onLayoutReady() {
        this.loadData(STORAGE_NAME);
        console.log(`frontend: ${getFrontend()}; backend: ${getBackend()}`);
    }

    onunload() {
        console.log(this.i18n.byePlugin);
    }

    uninstall() {
        console.log("uninstall");
    }
}

@Achuan-2
Copy link
Member

Achuan-2 commented Oct 10, 2024

解决了第一点,已经可以正常使用了
https://github.com/Achuan-2/siyuan-plugin-formatPainter

  1. 工具栏点击格式刷,即可选中文本快速刷格式
  2. 按esc键退出格式刷
import {
    Plugin,
    getFrontend,
    getBackend,
    fetchPost,
    IModel,
    Protyle,
    IProtyle,
    Toolbar
} from "siyuan";
import "./index.scss";

const STORAGE_NAME = "menu-config";
const TAB_TYPE = "custom_tab";
const DOCK_TYPE = "dock_tab";

export default class PluginSample extends Plugin {
    private customTab: () => IModel;
    private isMobile: boolean;
    private formatPainterEnable = false;
    private formatData: { datatype: string, style: string } | null = null;
    private protyle: IProtyle;
    onload() {
        this.data[STORAGE_NAME] = { readonlyText: "Readonly" };
        this.protyleOptions = {
            toolbar: ["block-ref",
                "a",
                "|",
                "text",
                "strong",
                "em",
                "u",
                "s",
                "mark",
                "sup",
                "sub",
                "clear",
                "|",
                "code",
                "kbd",
                "tag",
                "inline-math",
                "inline-memo",
                "|",
                {
                    name: "format-painter",
                    icon: "iconFormat",
                    tipPosition: "n",
                    tip: this.i18n.tips,
                    click: (protyle: Protyle) => {
                        this.protyle = protyle.protyle;
                        
                        if (!this.formatPainterEnable) {
                            const selectedInfo = this.getSelectedParentHtml();
                            if (selectedInfo) {
                                this.formatData = {
                                    datatype: selectedInfo.datatype,
                                    style: selectedInfo.style
                                };

                            }
                            else {
                                this.formatData = null;
                                // console.log("选中无样式文字");
                            }
                            this.formatPainterEnable = true;
                            // console.log(this.formatData);
                            fetchPost("/api/notification/pushErrMsg", { "msg": this.i18n.enable, "timeout": 7000 });
                        }
                    }
                }
            ],
        };
        // this.eventBus.on("click-editorcontent", ({ detail }) => {
        //     if (this.formatPainterEnable && this.formatData) {
        //         this.protyle = detail.protyle;
        //     }

        // });
        document.addEventListener('mouseup', (event) => {
            if (this.formatPainterEnable) {
                const selection = window.getSelection();
                if (selection && selection.rangeCount > 0) {
                    const range = selection.getRangeAt(0);
                    const selectedText = range.toString();
                    if (selectedText) {

                        this.protyle.toolbar.range = range;  // 更改选区
                        // console.log(this.protyle.toolbar.range.toString());
                        // Apply the stored format to the selected text
                        // 如果都为空
                        if (!this.formatData) {
                            this.protyle.toolbar.setInlineMark(this.protyle, "clear", "range");
                            selection.removeAllRanges();
                            return;
                        }
                        if (this.formatData.datatype) {
                            this.protyle.toolbar.setInlineMark(this.protyle, this.formatData.datatype, "range");
                        }
                        if (this.formatData.style) {
                            // console.log(this.formatData.style);
                            // this.protyle.toolbar.setInlineMark(this.protyle, "text", "range", { "type": "style1", "color": this.formatData.style });
                            const { backgroundColor, color, fontSize } = parseStyle(this.formatData.style);
                            // console.log(backgroundColor, color, fontSize);

                            if (backgroundColor) {
                                this.protyle.toolbar.setInlineMark(this.protyle, "text", "range", {
                                    "type": "backgroundColor",
                                    "color": backgroundColor
                                });
                            }

                            if (color) {
                                this.protyle.toolbar.setInlineMark(this.protyle, "text", "range", {
                                    "type": "color",
                                    "color": color
                                });
                            }

                            if (fontSize) {
                                this.protyle.toolbar.setInlineMark(this.protyle, "text", "range", {
                                    "type": "fontSize",
                                    "color": fontSize
                                });
                            }
                        }

                        // console.log("Format applied to selected text");
                        // 清空选区
                        selection.removeAllRanges();
                    }
                }
            }
        });
        function parseStyle(styleString) {
            const styles = styleString.split(';').filter(s => s.trim() !== '');
            const styleObject = {};

            styles.forEach(style => {
                const [property, value] = style.split(':').map(s => s.trim());
                styleObject[property] = value;
            });

            return {
                backgroundColor: styleObject['background-color'],
                color: styleObject['color'],
                fontSize: styleObject['font-size']
            };
        }
        document.addEventListener('keydown', (event) => {
            if (event.key === "Escape") {
                if (this.formatPainterEnable) {
                    this.formatPainterEnable = false;
                    this.formatData = null;
                    fetchPost("/api/notification/pushMsg", { "msg": this.i18n.disable, "timeout": 7000 });
                }
            }
        });

        console.log(this.i18n.helloPlugin);
    }

    getSelectedParentHtml() {
        const selection = window.getSelection();
        if (selection.rangeCount > 0) {
            const range = selection.getRangeAt(0);
            const selectedNode = range.startContainer;
            const startNode = selectedNode.nodeType === Node.TEXT_NODE ? selectedNode.parentNode : selectedNode;
            let parentElement = selectedNode.parentElement;
            while (parentElement && !parentElement.hasAttribute("data-type")) {
                parentElement = parentElement.parentElement;
            }
            if (parentElement.tagName.toLowerCase() === "span") {
                const result = {
                    html: parentElement.outerHTML,
                    datatype: parentElement.getAttribute("data-type"),
                    style: parentElement.getAttribute("style")
                };
                // 清空选区
                selection.removeAllRanges();
                return result;
            }

        }
        // 清空选区
        selection.removeAllRanges();
        return null;
    }

    onLayoutReady() {
    }

    onunload() {
        console.log(this.i18n.byePlugin);
    }

    uninstall() {
        console.log("uninstall");
    }
}

@88250 88250 closed this as not planned Won't fix, can't repro, duplicate, stale Oct 13, 2024
@88250
Copy link
Member

88250 commented Oct 13, 2024

感谢 @Achuan-2 贡献插件,原生功能暂时不考虑了。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants