概述
使用的 Halo 主题 Dream plus 支持使用 htlm 源码显示消息框,面板等功能,如下
我想通过思源笔记也实现上述显示效果,同时能做到与博客页面保持同步。
研究过程
首先想到,思源笔记有 html 的渲染显示功能,添加一个 html 做实验效果如下
文字内容可以显示,但是没有样式效果。
通过开发者工具查看节点会发现 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("完成动态加载");
});
});
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于