Skip to content

Support formula rendering when exporting to WeChat MP #12571

Closed
@Achuan-2

Description

@Achuan-2

发布到微信公众号支持公式渲染

In what scenarios do you need this feature?


行间公式

$$
\chi^2 = \sum \frac{(Observed - Expected)^2}{Expected}
$$

行内公式

$\chi^2 = \sum \frac{(Observed - Expected)^2}{Expected}$

123456$\begin{cases}     \dfrac{你好}{你们好} \\     \dfrac{你好}{你们好} \end{cases}$23456

为什么

$$
\sqrt{\frac{\hat{\sigma}_{1}^{2}+ \hat{\sigma}_{2}^{2}}{n}}= \sqrt{\frac{s_{1}^{2}+
s_{2}^{2}}{n-1}}
$$

1. 首先,我们知道样本方差 $s^2$ 和无偏方差估计 $\hat{\sigma}^2$ 之间的关系:

    $\hat{\sigma}^2 = \frac{n}{n-1}s^2$

    其中,$n$ 是样本量。
2. 现在,让我们看看公式中的分母部分:

    使用方差估计:$\sqrt{\frac{\hat{\sigma}_{1}^{2} + \hat{\sigma}_{2}^{2}}{n}}$

    使用样本方差:$\sqrt{\frac{s_1^2 + s_2^2}{n-1}}$
3. 将 $\hat{\sigma}^2 = \frac{n}{n-1}s^2$ 代入方差估计公式:

    $\sqrt{\frac{\hat{\sigma}_{1}^{2} + \hat{\sigma}_{2}^{2}}{n}}= \sqrt{\frac{\frac{n}{n-1}s_{1}^{2} + \frac{n}{n-1}s_{2}^{2}}{n}}$
4. 化简:

    $= \sqrt{\frac{\frac{n}{n-1}(s_{1}^{2} + s_{2}^{2})}{n}}= \sqrt{\frac{s_{1}^{2} + s_{2}^{2}}{n-1}}$
5. 我们得到:

    $\sqrt{\frac{\hat{\sigma}_1^2 + \hat{\sigma}_2^2}{n}} = \sqrt{\frac{s_1^2 + s_2^2}{n-1}}$

思源笔记预览
PixPin_2024-09-24_12-17-00
发布到微信公众号,可以看到行内公式可以正常显示,行间公式却完全乱了
PixPin_2024-09-24_12-17-24

虽然行内公式看着没问题,而发布后,手机上查看,发现行内公式也是乱的
8901fc08fb3166d7fda1e610a98b488

Describe the optimal solution

发布微信公众号支持公式渲染

Describe the candidate solution

No response

Other information

No response

Activity

changed the title [-]发布微信公众号支持行间公式[/-] [+]发布到微信公众号支持行间公式[/+] on Sep 24, 2024
changed the title [-]发布到微信公众号支持行间公式[/-] [+]发布到微信公众号支持公式渲染[/+] on Sep 24, 2024
Achuan-2

Achuan-2 commented on Sep 24, 2024

@Achuan-2
MemberAuthor
Achuan-2

Achuan-2 commented on Oct 18, 2024

@Achuan-2
MemberAuthor
Achuan-2

Achuan-2 commented on Nov 13, 2024

@Achuan-2
MemberAuthor

问GPT写脚本尝试了下,竟然成功了哈哈哈
https://ld246.com/article/1731513964135

function createRenderer(display) {
  return (mathContent) => {
    window.MathJax.texReset();
    const mjxContainer = window.MathJax.tex2svg(mathContent, { display });
    const svg = mjxContainer.firstChild;
    const width = svg.style[`min-width`] || svg.getAttribute(`width`);
    svg.removeAttribute(`width`);
    svg.style = `max-width: 300vw !important;`;
    svg.style.width = width;
    svg.style.display = `initial`;
    if (display) {
      return `<section style="text-align: center; overflow: auto;">${svg.outerHTML}</section>`;
    }
    return `${svg.outerHTML}`;
  };
}

function renderMathBlocks() {
  const mathBlocks = document.querySelectorAll('.b3-typography div[data-subtype="math"]');
  const renderBlock = createRenderer(true);

  mathBlocks.forEach((block) => {
    const mathContent = block.getAttribute('data-content');
    try {
      block.innerHTML = renderBlock(mathContent);
    } catch (error) {
      console.error('Error rendering MathJax block:', error);
    }
  });
}

function renderInlineMath() {
  const inlineMaths = document.querySelectorAll('span[data-type="inline-math"]');
  const renderInline = createRenderer(false);

  inlineMaths.forEach((span) => {
    const mathContent = span.getAttribute('data-content');
    try {
      span.innerHTML = renderInline(mathContent);
    } catch (error) {
      console.error('Error rendering MathJax inline:', error);
    }
  });
}

function loadMathJax(callback) {
  window.MathJax = {
    tex: {
      inlineMath: [['$', '$']]
    },
    svg: {
      fontCache: 'none'
    }
  };

  const script = document.createElement('script');
  script.src = 'https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-svg.js';
  script.async = true;
  script.onload = callback;
  document.head.appendChild(script);
}

loadMathJax(() => {
  console.log('MathJax has been loaded!');
  renderMathBlocks();
  renderInlineMath();
});

没处理前,块公式惨不忍睹,行内公式看着正常,但是一旦有下标、上标就会跑到其他地方去
PixPin_2024-11-13_23-27-18
处理后
PixPin_2024-11-13_23-43-55

shuxiaobo

shuxiaobo commented on Mar 9, 2025

@shuxiaobo

请请问下,您给的这段代码,怎么在siyuan使用呢?
@Achuan-2

Achuan-2

Achuan-2 commented on Mar 9, 2025

@Achuan-2
MemberAuthor

请请问下,您给的这段代码,怎么在siyuan使用呢? @Achuan-2

https://ld246.com/article/1731553411638
需要粘贴到js代码片段

ps:微信公众号编辑器最近在内测,不幸被选中内测新版编辑器的人,可能粘贴样式会有问题

Vanessa219

Vanessa219 commented on Apr 23, 2025

@Vanessa219
Member

我用了代码片段复制到服务号的文章里面没有用。关键是里面没有插入公式的按钮,不知道怎么插入公式 -_-||

Image

Achuan-2

Achuan-2 commented on Apr 23, 2025

@Achuan-2
MemberAuthor

我用了代码片段复制到服务号的文章里面没有用。关键是里面没有插入公式的按钮,不知道怎么插入公式 -_-||

Image

这个代码片段的作用是把公式转化为svg图片,微信公众号本身不支持公式
我等会找下代码

Vanessa219

Vanessa219 commented on Apr 23, 2025

@Vanessa219
Member

这个代码已经可以转换为 svg 了,但是粘贴的时候什么都粘不上去

Achuan-2

Achuan-2 commented on Apr 24, 2025

@Achuan-2
MemberAuthor

这个代码已经可以转换为 svg 了,但是粘贴的时候什么都粘不上去

我这边是可以的,
JS代码片段

(() => {
    // Start observing DOM changes
    const targetNode = document.body;
    observeDomChange(targetNode, (mutation) => {
        if (mutation.target.querySelector('.b3-typography')) {
            addCustomButton();
        }
    });

    // Function to add the custom button
    function addCustomButton() {
        const actionContainers = document.querySelectorAll('.protyle-preview .protyle-preview__action');
        actionContainers.forEach(container => {
            // Check if the custom button already exists
            if (!container.querySelector('button[data-type="mp-wechat-enchaced')) {
                const button = document.createElement('button');
                button.type = 'button';
                button.dataset.type = 'mp-wechat-enchaced';
                button.dataset.custom = 'true';
                button.className = 'b3-tooltips b3-tooltips__w';
                button.setAttribute('aria-label', '粘贴到公众号样式适配');
                button.innerHTML = '<svg><use xlink:href="#iconMp"></use></svg>';
                button.style.color = "var(--b3-theme-primary)";
                button.onclick = handleButtonClick;

                // Find the existing desktop button
                const desktopButton = container.querySelector('button[data-type="mp-wechat"]');
                if (desktopButton) {
                    // Insert the custom button before the desktop button
                    container.insertBefore(button, desktopButton);
                } else {
                    // If desktop button doesn't exist, append the custom button at the end
                    container.appendChild(button);
                }
            }
        });
        // 选择所有的 div.b3-typography 元素
        const typographyDivs = document.querySelectorAll('div.b3-typography');

        typographyDivs.forEach(typographyDiv => {
            // 获取最后一个子元素
            const lastChild = typographyDiv.lastElementChild;

            // 检查最后一个子元素是否是 div[data-subtype="math"]
            if (lastChild && lastChild.matches('div[data-subtype="math"]')) {
                // 创建新的 <p> 元素
                const newParagraph = document.createElement('p');
                newParagraph.innerHTML = '&#8203;'; // 插入零宽度空格

                // 将新的 <p> 元素添加到 div.b3-typography 中
                typographyDiv.appendChild(newParagraph);
            }
        });
    }

    // Function to handle button click
    async function handleButtonClick() {

        await fetchSyncPost('/api/notification/pushMsg', {
            "msg": "发布到微信公众号:样式转换ing",
            "timeout": 5000
        });
        convertLinksToWechat();
        convertListToWechat();
        await convertMathToWechat();

        const desktopButton = document.querySelector('.layout__wnd--active .protyle:not(.fn__none) .protyle-preview .protyle-preview__action > button[data-type="desktop"]');
        if (desktopButton) {
            desktopButton.click();
        }
        
        await fetchSyncPost('/api/notification/pushMsg', {
            "msg": "发布到微信公众号:样式转换完成",
            "timeout": 5000
        });


        // Simulate click on the existing button to copy content  
        const wechatCopyButton = document.querySelector('.layout__wnd--active .protyle:not(.fn__none) .protyle-preview .protyle-preview__action > button[data-type="mp-wechat"]');
        if (wechatCopyButton) {
            wechatCopyButton.click();
        }
    }

    // Function to convert SiYuan note links to plain text with link
    function convertLinksToWechat() {
        const links = document.querySelectorAll('.b3-typography a');
        links.forEach(link => {
            const href = link.getAttribute('href');
            const text = link.textContent;

            // Check if the link starts with 'siyuan://'
            if (href.startsWith('siyuan://') || href.startsWith('#')) {
                // Create a new span element with the link text
                const newSpan = document.createElement('span');
                newSpan.textContent = text;
                newSpan.style.color = '#338dd6'; // Set the text color

                // Replace the original link with the new span element
                link.parentNode.replaceChild(newSpan, link);
            } else if (!href.startsWith('https://mp.weixin.qq.com/')) {
                const newTextContent = text === href ? href : `[${text}](${href})`;
                const newSpan = document.createElement('span');
                newSpan.style.color = '#338dd6';
                newSpan.textContent = newTextContent;
                link.parentNode.replaceChild(newSpan, link);
            }
        });
    }

    // Function to adjust list structure for WeChat
    function convertListToWechat() {
        const lists = document.querySelectorAll('.b3-typography ul:not([data-replaced="true"]), .b3-typography ol:not([data-replaced="true"])');
        lists.forEach(list => {
            const items = Array.from(list.children);
            items.forEach(item => {
                const nestedList = item.querySelector('ul, ol');
                if (nestedList) {
                    item.parentNode.insertBefore(nestedList, item.nextSibling);
                }
            });
            list.setAttribute('data-replaced', 'true');
        });
    }

    // Function to convert math blocks and inline math
    async function convertMathToWechat() {
        await loadMathJax();
        await Promise.all([renderMathBlocks(), renderInlineMath()]);
    }

    // Function to render math blocks
    async function renderMathBlocks() {
        const mathBlocks = document.querySelectorAll('.b3-typography div[data-subtype="math"]:not([data-replaced="true"])');
        const renderBlock = createRenderer(true);
        return Promise.all(Array.from(mathBlocks).map(async (block) => {
            const mathContent = block.getAttribute('data-content');
            try {
                block.innerHTML = renderBlock(mathContent);
                block.setAttribute('data-replaced', 'true');
            } catch (error) {
                console.error('Error rendering MathJax block:', error);
            }
        }));
    }

    // Function to render inline math
    async function renderInlineMath() {
        const inlineMaths = document.querySelectorAll('span[data-type="inline-math"]:not([data-replaced="true"])');
        const renderInline = createRenderer(false);
        return Promise.all(Array.from(inlineMaths).map(async (span) => {
            const mathContent = span.getAttribute('data-content');
            try {
                span.innerHTML = renderInline(mathContent);
                span.style.verticalAlign = 'bottom';
                span.setAttribute('data-replaced', 'true');
            } catch (error) {
                console.error('Error rendering MathJax inline:', error);
            }
        }));
    }

    // Function to create MathJax renderer
    function createRenderer(display) {
        return (mathContent) => {
            const cleanedContent = mathContent.replace(/\displaystyle\s*{([^}]*)}/g, '$1');
            window.MathJax.texReset();
            const mjxContainer = window.MathJax.tex2svg(cleanedContent, { display });
            const svg = mjxContainer.firstChild;
            const width = svg.style[`min-width`] || svg.getAttribute(`width`);
            svg.removeAttribute(`width`);
            svg.style = `max-width: 300vw !important;`;
            svg.style.width = width;
            svg.style.display = `initial`;
            if (display) {
            return `<section style="text-align: center; overflow: auto; font-size: 14px;">${svg.outerHTML}</section>`;
            }
            return svg.outerHTML;
        };
    }

    // Function to load MathJax
    function loadMathJax() {
        return new Promise((resolve) => {
            if (window.MathJax) {
                resolve();
                return;
            }
            window.MathJax = {
                svg: {
                    fontCache: 'none'
                }
            };

            const script = document.createElement('script');
            script.src = 'https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-svg.js';
            script.async = true;
            script.onload = resolve;
            document.head.appendChild(script);
        });
    }

    // Function to perform a synchronous POST request
    async function fetchSyncPost(url, data, returnType = 'json') {
        const init = {
            method: "POST",
            headers: {
                'Content-Type': 'application/json'
            },
            body: JSON.stringify(data)
        };
        try {
            const res = await fetch(url, init);
            return returnType === 'json' ? await res.json() : await res.text();
        } catch (e) {
            console.log(e);
            return returnType === 'json' ? { code: e.code || 1, msg: e.message || "", data: null } : "";
        }
    }

    // 定义观察DOM变化的函数
    function observeDomChange(targetNode, callback) {
        const config = { childList: true, subtree: true };
        const observer = new MutationObserver((mutationsList) => {
            for (const mutation of mutationsList) {
                if (mutation.type === 'childList') {
                    callback(mutation);
                }
            }
        });
        observer.observe(targetNode, config);
        return observer;
    }
})();

markdown示例

为什么

$$
\sqrt{\frac{\hat{\sigma}_{1}^{2}+ \hat{\sigma}_{2}^{2}}{n}}= \sqrt{\frac{s_{1}^{2}+
s_{2}^{2}}{n-1}}
$$

1. 首先,我们知道样本方差 $s^2$ 和无偏方差估计 $\hat{\sigma}^2$ 之间的关系:

    $\hat{\sigma}^2 = \frac{n}{n-1}s^2$

    其中,$n$ 是样本量。
2. 现在,让我们看看公式中的分母部分:

    使用方差估计:$\sqrt{\frac{\hat{\sigma}_{1}^{2} + \hat{\sigma}_{2}^{2}}{n}}$

    使用样本方差:$\sqrt{\frac{s_1^2 + s_2^2}{n-1}}$
3. 将 $\hat{\sigma}^2 = \frac{n}{n-1}s^2$ 代入方差估计公式:

    $\sqrt{\frac{\hat{\sigma}_{1}^{2} + \hat{\sigma}_{2}^{2}}{n}}= \sqrt{\frac{\frac{n}{n-1}s_{1}^{2} + \frac{n}{n-1}s_{2}^{2}}{n}}$
4. 化简:

    $= \sqrt{\frac{\frac{n}{n-1}(s_{1}^{2} + s_{2}^{2})}{n}}= \sqrt{\frac{s_{1}^{2} + s_{2}^{2}}{n-1}}$
5. 我们得到:

    $\sqrt{\frac{\hat{\sigma}_1^2 + \hat{\sigma}_2^2}{n}} = \sqrt{\frac{s_1^2 + s_2^2}{n-1}}$

Image

粘贴到公众号

Image

Vanessa219

Vanessa219 commented on Apr 29, 2025

@Vanessa219
Member

我的运不运行代码片段结果都一样,第一个数学公式不完整。(刚才登录服务号的时候提示编辑器有改进,不知道是不是官方更新导致)

Image

运行完代码片段这里有点异常。

Image

Achuan-2

Achuan-2 commented on Apr 29, 2025

@Achuan-2
MemberAuthor

我的运不运行代码片段结果都一样,第一个数学公式不完整。(刚才登录服务号的时候提示编辑器有改进,不知道是不是官方更新导致)

Image

运行完代码片段这里有点异常。

Image

不运行代码片段结果是这样的,无法直接粘贴,

新版微信公众号可以直接粘贴svg的

微信公众号那个蓝色是我添加的,需要点下才进行转换的,我怀疑你没有点这个按钮,只有直接粘贴才会不完整,svg是不会数学公式不完整的

34 remaining items

Loading
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Relationships

None yet

    Development

    No branches or pull requests

      Participants

      @Vanessa219@shuxiaobo@Achuan-2@TCOTC

      Issue actions

        Support formula rendering when exporting to WeChat MP · Issue #12571 · siyuan-note/siyuan