让思源渲染 Halo Dream plus 主题的样式

概述

使用的 Halo 主题 Dream plus 支持使用 htlm 源码显示消息框,面板等功能,如下

image

image

我想通过思源笔记也实现上述显示效果,同时能做到与博客页面保持同步。

研究过程

首先想到,思源笔记有 html 的渲染显示功能,添加一个 html 做实验效果如下

image

文字内容可以显示,但是没有样式效果。

通过开发者工具查看节点会发现 mew-message 标签被过滤了,需要在编辑功能中将允许 html 代码运行的功能打开。这时可以发现 mew-message 标签可以显示了。

现在需要为 mew-message 标签增加样式,想到思源笔记的代码片段功能可以通过代码片段为标签增加样式,样式内容如下。但是此样式不能成功应用到 html 的代码快中

/* 标准CSS写法 */
mew-message {
    font-size: 20px;
    padding: 10px;
    margin: 5px;
    border-radius: 5px;
    display: block; /* 确保每个元素单独占一行 */
}

mew-message[type="info"] {
    background-color: #e3f2fd;
    color: #1976d2;
}

mew-message[type="success"] {
    background-color: #e8f5e9;
    color: #2e7d32;
}

mew-message[type="warning"] {
    background-color: #fff3e0;
    color: #f4511e;
}

mew-message[type="error"] {
    background-color: #ffebee;
    color: #c62828;
}

通过开发者工具可以发现 ,html 代码块中的内容是通过 shadowRoot 节点进行渲染的,其不会被外部的样式渗透,同时其内部的 css 样式也不会污染外部,需要通过 js 脚本才能获取到 shadowRoot 节点的内容,参考如下。

document.getElementsByTagName("protyle-html")[0].shadowRoot

适配过程

基于思源的 html 块直接套用 halo 的所有主题。

套用原理,使用思源的代码块功能,将需要的 js 脚本和部分 ccs 动态加入头中,然后再将特定的 ccs 加载到 html 的 div 中从而实现主题的特定组件显示。特定组件使用了 costumElements 实现,具体代码如下:

try{
    // 创建第一个 link 元素
    const link1 = document.createElement('link');
    link1.rel = 'preload stylesheet';
    link1.as = 'style';
    link1.href = 'https://your.halo.site/themes/theme-dream2-plus/assets/css/theme.min.css?mew=1.4.7';

    // 将 link 元素添加到文档的 head 中
    document.head.appendChild(link1);

}catch (error) {
    console.error('执行过程中出现错误:', error);
}
try {
   console.log("******jquery****")


    class i extends HTMLElement {
        constructor() {
            super(),
                this.hasAttribute("draw") || this.init()
        }
        drawComplete() {
            this.setAttribute("draw", !0)
        }
    }

    customElements.define("mew-hide", class extends i {
        init() {
            var t = $(this)
                , i = t.closest(".main-content");
            this.options = {
                target: i.attr("data-target"),
                id: i.attr("data-id")
            },
                this.options.target && this.options.id && ((i = (i = localStorage.getItem(window.encrypt("mew-hide-" +
                    this.options.target))) ? JSON.parse(window.decrypt(i)) : []).includes(this.options.id) ? (t.before(this.innerHTML),
                        t.remove()) : (i = 0 !== t.find("h1,h2,h3,h4,h5").length,
                            this.setAttribute("hide", window.encrypt(this.innerHTML)),
                            this.innerHTML = "",
                            i && (this.setAttribute("toc", !0),
                                commonContext.initTocAndNotice()),
                            this.onclick = function () {
                                var t = $(`halo-comment[id='${this.options.id}'][type='${this.options.target.substring(0, this.options.target.length -
                                    1)}']`);
                                0 === t.length || t.is(":hidden") || Utils.animateScroll(t[0], 20, (window.innerHeight ||
                                    document.documentElement.clientHeight) / 4)
                            }
                )),
                this.drawComplete()
        }
    }
    );
    customElements.define("mew-subtitle", class extends i {
        init() {
            this.innerHTML = `<span>${this.innerText || "默认标题"}</span>`,
                this.drawComplete()
        }
    }
    )

    customElements.define("mew-cloud", class extends i {
        init() {
            this.options = {
                type: this.getAttribute("type") || "default",
                title: this.innerText || "资源文件分享",
                url: this.getAttribute("url"),
                password: this.getAttribute("password")
            };
            var t = {
                default: "网络来源",
                360: "360云盘",
                115: "115网盘",
                123: "123云盘",
                kk: "夸克网盘",
                xl: "迅雷云盘",
                onedrive: "OneDrive",
                yd: "中国移动云盘",
                ty: "天翼云盘",
                lt: "联通云盘",
                uc: "UC网盘",
                pan: "网络云盘",
                oss: "对象存储",
                bd: "百度网盘",
                wy: "微云",
                ali: "阿里云盘",
                github: "Github仓库",
                gitee: "Gitee仓库",
                gitlab: "Gitlab仓库",
                gitea: "Gitea仓库",
                git: "Git仓库",
                lz: "蓝奏云网盘"
            };
            this.innerHTML = `
					<div class="mew-cloud-logo type-${t[this.options.type] ? this.options.type : "default"}"></div>
					<div class="mew-cloud-desc">
						<div class="mew-cloud-desc-title">${this.options.title}</div>
						<div class="mew-cloud-desc-type">来源:${t[this.options.type] || "网络来源"}${this.options.password ? " | 提取码:" + this.options.password : ""}</div>
					</div>
					<a class="mew-cloud-link" href="${this.options.url}" target="_blank" rel="noopener noreferrer nofollow">
						<i class="ri-download-line"></i>
					</a>
				`,
                this.drawComplete()
        }
    }
    )

    customElements.define("mew-progress", class extends i {
        init() {
            this.options = {
                value: /^\d{1,3}%$/.test(this.getAttribute("value")) ? this.getAttribute("value") : "50%",
                color: this.getAttribute("color") || "var(--theme)"
            },
                this.innerHTML = `<div class="mew-progress-bar">
									<div class="mew-progress-bar-inner" style="width: ${this.options.value}; background: ${this.options.color};"></div>
								</div>
								<div class="mew-progress-value">${this.options.value}</div>`,
                this.drawComplete()
        }
    }
    )

    customElements.define("mew-panel", class extends i {
        init() {
            this.options = {
                title: this.getAttribute("title") || "",
                color: this.getAttribute("color") || "var(--theme)"
            },
                this.innerHTML = `
                <div class="mew-panel-title">${this.options.title}</div>
                <div class="mew-panel-body">${this.innerHTML}</div>`,
                this.style.background = this.options.color,
                this.style.color = this.options.color,
                this.drawComplete()
        }
    }
    )


    customElements.define("mew-message", class extends i {
        init() {
            this.options = {
                type: /^(success|info|warning|error)$/.test(this.getAttribute("type")) ? this.getAttribute("type") : "info",
                content: this.innerHTML || "消息内容"
            },
                this.innerHTML = this.options.content,
                this.setAttribute("type", this.options.type),
                this.drawComplete()
        }
    }
    )
    customElements.define("mew-hr", class extends i {
        init() {
            this.startColor = this.getAttribute("startColor") || "#01d0ff",
                this.endColor = this.getAttribute("endColor") || "#fc3e85",
                this.style.backgroundImage = `repeating-linear-gradient(-45deg, ${this.startColor} 0,${this.startColor} 20%, transparent 0,transparent 35%, ${this.endColor} 0,${this.endColor} 65%, transparent 0,transparent 80%, ${this.startColor} 0,${this.startColor} 100%)`,
                this.drawComplete()
        }
    }
    )
    customElements.define("mew-timeline", class extends i {
        init() {
            let t = ""
                , i = this.firstChild;
            for (; i;) {
                var e;
                "MEW-TIMELINE-TITLE" === i.tagName ? t += `<div class="mew-timeline-title ${i.getAttribute("type") || ""}"><span class="mew-timeline-title-elem">${i.innerHTML}</span></div>` : "MEW-TIMELINE-ITEM" === i.tagName && (i.getAttribute("type"),
                    e = i.getAttribute("title") ? `<span class="mew-timeline-item-title">${i.getAttribute("title")}</span>` : "",
                    t += `<div class="mew-timeline-item ${i.getAttribute("type") || ""}">${e}<div class="mew-timeline-item-content">${i.innerHTML}</div></div>`),
                    i = i.nextElementSibling
            }
            this.innerHTML = t,
                this.drawComplete()
        }
    }
    )

    customElements.define("mew-music", class i extends HTMLElement {
        constructor() {
            super(),
                this.innerHTML = this.getAttribute("prompt") || "音乐播放器加载中...",
                this.options = {
                    container: this,
                    fixed: this.hasAttribute("fixed") || !1,
                    mutex: this.hasAttribute("mutex") || !0,
                    theme: this.getAttribute("theme") || "var(--theme)",
                    loop: this.getAttribute("loop") || "all",
                    autoplay: this.hasAttribute("autoplay") && "false" !== this.getAttribute("autoplay"),
                    lrcType: this.getAttribute("lrcType") || 3,
                    listFolded: this.getAttribute("listFolded") || !1,
                    volume: this.getAttribute("volume") || .7,
                    listMaxHeight: this.getAttribute("listMaxHeight") || "450px",
                    mini: this.getAttribute("mini") || !1,
                    order: this.getAttribute("order") || "list",
                    storageName: this.getAttribute("storageName") || "aplayer-setting"
                },
                "APlayer" in window ? this.render() : i.prototype.load ? i.prototype.await.push(() => this.render()) : (i.prototype.load = !0,
                    i.prototype.await = [],
                    new Promise(t => {
                        $("head").append('<link rel="stylesheet" href="https://unpkg.com/aplayer@1.10.1/dist/APlayer.min.css">'),
                            Utils.cachedScript("https://unpkg.com/aplayer@1.10.1/dist/APlayer.min.js").done(() => t()).fail(() => t())
                    }
                    ).then(() => {
                        this.render(),
                            i.prototype.await && i.prototype.await.forEach(t => t())
                    }
                    ))
        }
        render() {
            "APlayer" in window ? new Promise(async t => {
                if (this.hasAttribute("meetingUrl"))
                    this.options.audio = await fetch(this.getAttribute("meetingUrl")).then(t => t.json());
                else if (this.hasAttribute("song"))
                    this.options.audio = await fetch("https://api.i-meto.com/meting/api?server=netease&type=song&id=" + this.getAttribute("song")).then(t => t.json());
                else if (this.hasAttribute("playlist"))
                    this.options.audio = await fetch("https://api.i-meto.com/meting/api?server=netease&type=playlist&id=" + this.getAttribute("playlist")).then(t => t.json());
                else if (this.hasAttribute("url"))
                    this.options.audio = [{
                        name: this.getAttribute("name") || "音乐",
                        url: this.getAttribute("url"),
                        artist: this.getAttribute("artist") || "未知歌手",
                        cover: this.getAttribute("cover"),
                        lrc: this.getAttribute("lrc") || (this.options.lrcType = void 0)
                    }];
                else {
                    if (!this.hasAttribute("music-list"))
                        return this.innerHTML = "未指定播放的音乐!",
                            t();
                    var i = JSON.parse(this.getAttribute("music-list"));
                    i.forEach(t => {
                        t.lrc = t.lrc || "",
                            t.name = t.name || "音乐",
                            t.artist = t.artist || "未知歌手",
                            t.cover = t.cover || "/themes/theme-dream2-plus/assets/img/music.webp"
                    }
                    ),
                        this.options.audio = i
                }
                this.aplayer = new APlayer(this.options),
                    t()
            }
            ).catch(t => { }
            ) : this.innerHTML = "未开启音乐播放器!"
        }
        getAPlayer() {
            return this.aplayer
        }
        disconnectedCallback() {
            this.aplayer && this.aplayer.destroy()
        }
    }
    )
    customElements.define("mew-bilibili", class extends i {
        init() {
            this.options = {
                bvid: this.getAttribute("bvid"),
                width: /^\d{1,3}%$/.test(this.getAttribute("width")) ? this.getAttribute("width") : "100%"
            },
                this.options.bvid ? (this.style.padding = `calc(${this.options.width} * 0.3) 0`,
                    this.innerHTML = `<iframe allowfullscreen="true" src="//player.bilibili.com/player.html?bvid=${this.options.bvid}&page=1" style="width: ${this.options.width};"></iframe>`) : this.innerHTML = "bvid未填写!",
                this.drawComplete()
        }
    }
    )
    customElements.define("mew-tabs", class extends i {
        init() {
            var t = $(this).children("mew-tab-page");
            if (0 === t.length)
                this.innerHTML = "没有标签页!";
            else {
                let s = ""
                    , o = ""
                    , n = !1;
                t.each((t, i) => {
                    var e = i.getAttribute("title") || "默认标签"
                        , t = t + "-" + (new Date).getTime();
                    !n && i.hasAttribute("active") ? (n = !0,
                        s += `<div class="active" data-id="#${t}">${e}</div>`,
                        o += `<div class="active" id="${t}">${i.innerHTML}</div>`) : (s += `<div data-id="#${t}">${e}</div>`,
                            o += `<div id="${t}">${i.innerHTML}</div>`)
                }
                ),
                    this.innerHTML = `<div class="tabs-head">${s}</div><div class="tabs-body">${o}</div>`,
                    n || $(this).find("div>div:first-child").addClass("active")
            }
            this.drawComplete()
        }
        connectedCallback() {
            $(this).find(".tabs-head").on("click", "div:not(.active)", function () {
                var t = $(this).parent().parent();
                t.find(".active").removeClass("active"),
                    $(this).addClass("active"),
                    t.find($(this).attr("data-id")).addClass("active")
            })
        }
    }
    )

customElements.define("mew-quote", class extends i {
            init() {
                this.options = {
                    avatar: this.getAttribute("avatar"),
                    href: this.getAttribute("href"),
                    name: this.getAttribute("name")
                };
                var t = this.options.avatar ? `<a class="mew-quote-href" target="_blank" ${this.options.href ? `href="${this.options.href}"` : ""}><img class="quote-avatar-hexagon not-gallery" src="${this.options.avatar}"/></a>` : ""
                  , i = this.options.name ? `<a class="mew-quote-name" target="_blank" ${this.options.href ? `href="${this.options.href}"` : ""}>${this.options.name}</a>` : "";
                this.innerHTML = `<div class="mew-quote"><div class="quote-container">${t}<div class="mew-quote-info"><p class="mew-quote-content">${this.innerHTML}</p>${i}</div></div></div>`,
                this.drawComplete()
            }
        }
        )


} catch (error) {
    console.error('执行过程中出现错误:', error);
}
@font-face {
    font-family: remixicon;
    src: url(remixicon.eot?t=1734404658139);
    src: url(
https://your.halo.site/themes/theme-dream2-plus/assets/lib/remixicon@4.6.0/remixicon.woff2?t=1734404658139) format('woff2');
    font-display: swap
}

下面的代码即时运行一下就能出现渲染效果了

// 获取所有的 protyle-html 元素
const protyleHtmlElements = document.querySelectorAll('protyle-html');

// 遍历每个 protyle-html 元素
protyleHtmlElements.forEach((element) => {
    // 检查元素是否有 shadowRoot
    if (element.shadowRoot) {

        // 检查是否存在 id 为 css 的元素,不存在则添加
        let cssElement = element.shadowRoot.getElementById('css');
        if (!cssElement) {
            cssElement = document.createElement('link');
            cssElement.id = 'css';
            cssElement.rel = 'stylesheet';
            cssElement.href = 'https://your.halo.site:32593/upload/in/siyuan-style.css';
            element.shadowRoot.appendChild(cssElement);
        }

        // 检查是否存在 id 为 js-jquery 的元素,不存在则添加
        let jsJqueryElement = element.shadowRoot.getElementById('js-jquery');
        if (!jsJqueryElement) {
            jsJqueryElement = document.createElement('script');
            jsJqueryElement.id = 'js-jquery';
            jsJqueryElement.src = 'https://your.halo.site:32593/themes/theme-dream2-plus/assets/lib/jquery@3.5.1/jquery.min.js?mew=1.4.7';
            element.shadowRoot.appendChild(jsJqueryElement);
        }
    } else {
        console.debug('No shadowRoot found for this element.');
    }
});

使用下面的方法让 思源自动调用美化函数,美化函数我命名为 loadHaloStyle。这里需要使用 runJs 的插件实现

const waitForRunJs = async (maxAttempts) => {
  let attempts = 0;

  while (attempts < maxAttempts) {
    if (globalThis?.runJs !== undefined) {
      console.debug("Detect runJS!");
      return true;
    }
    await new Promise((resolve) => {
      setTimeout(resolve, 5000);
    });

    attempts++;
  }
  return false;
};

waitForRunJs(5).then((flag) => {
  if (flag === false) return;
  //Your code here...
  runJs.siyuan.showMessage("Load RunJs");
  runJs.plugin.onEvent("loaded-protyle-static", (event) => {
    setTimeout(() => {
      runJs.plugin.call("loadHaloStyle");
      // runJs.siyuan.showMessage('完成渲染');
    }, 500);
    // runJs.siyuan.showMessage("完成静态加载");
  });
  runJs.plugin.onEvent("loaded-protyle-dynamic", (event) => {

    setTimeout(() => {
      runJs.plugin.call("loadHaloStyle");
      // runJs.siyuan.showMessage('完成渲染');
    }, 500);

    // runJs.siyuan.showMessage("完成动态加载");
  });
});
  • 思源笔记

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

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

    26047 引用 • 108127 回帖

相关帖子

欢迎来到这里!

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

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