让思源渲染 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("完成动态加载"); }); });
  • 思源笔记

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

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

    26114 引用 • 108412 回帖

相关帖子

欢迎来到这里!

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

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