首先是引入依赖,这里我们需要一个颜色选择器来修改主题样式,就用跟思源本身一样的 pickr 好了:
import Pickr from "@simonwep/pickr/dist/pickr.es5.min";
let pickClassic = `
.pickr{position:relative;overflow:visible;transform:translateY(0)}.pickr *{box-sizing:border-box;outline:none;border:none;-webkit-appearance:none}.pickr .pcr-button{position:relative;height:2em;width:2em;padding:0.5em;cursor:pointer;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI","Roboto","Helvetica Neue",Arial,sans-serif;border-radius:.15em;background:url('data:image/svg+xml;utf8, <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50 50" stroke="%2342445A" stroke-width="5px" stroke-linecap="round"><path d="M45,45L5,5"></path><path d="M45,5L5,45"></path></svg>') no-repeat center;background-size:0;transition:all 0.3s}.pickr .pcr-button::before{position:absolute;content:'';top:0;left:0;width:100%;height:100%;background:url('data:image/svg+xml;utf8, <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2 2"><path fill="white" d="M1,0H2V1H1V0ZM0,1H1V2H0V1Z"/><path fill="gray" d="M0,0H1V1H0V0ZM1,1H2V2H1V1Z"/></svg>');background-size:.5em;border-radius:.15em;z-index:-1}.pickr .pcr-button::before{z-index:initial}.pickr .pcr-button::after{position:absolute;content:'';top:0;left:0;height:100%;width:100%;transition:background 0.3s;background:var(--pcr-color);border-radius:.15em}.pickr .pcr-button.clear{background-size:70%}.pickr .pcr-button.clear::before{opacity:0}.pickr .pcr-button.clear:focus{box-shadow:0 0 0 1px rgba(255,255,255,0.85),0 0 0 3px var(--pcr-color)}.pickr .pcr-button.disabled{cursor:not-allowed}.pickr *,.pcr-app *{box-sizing:border-box;outline:none;border:none;-webkit-appearance:none}.pickr input:focus,.pickr input.pcr-active,.pickr button:focus,.pickr button.pcr-active,.pcr-app input:focus,.pcr-app input.pcr-active,.pcr-app button:focus,.pcr-app button.pcr-active{box-shadow:0 0 0 1px rgba(255,255,255,0.85),0 0 0 3px var(--pcr-color)}.pickr .pcr-palette,.pickr .pcr-slider,.pcr-app .pcr-palette,.pcr-app .pcr-slider{transition:box-shadow 0.3s}.pickr .pcr-palette:focus,.pickr .pcr-slider:focus,.pcr-app .pcr-palette:focus,.pcr-app .pcr-slider:focus{box-shadow:0 0 0 1px rgba(255,255,255,0.85),0 0 0 3px rgba(0,0,0,0.25)}.pcr-app{position:fixed;display:flex;flex-direction:column;z-index:10000;border-radius:0.1em;background:#fff;opacity:0;visibility:hidden;transition:opacity 0.3s, visibility 0s 0.3s;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI","Roboto","Helvetica Neue",Arial,sans-serif;box-shadow:0 0.15em 1.5em 0 rgba(0,0,0,0.1),0 0 1em 0 rgba(0,0,0,0.03);left:0;top:0}.pcr-app.visible{transition:opacity 0.3s;visibility:visible;opacity:1}.pcr-app .pcr-swatches{display:flex;flex-wrap:wrap;margin-top:0.75em}.pcr-app .pcr-swatches.pcr-last{margin:0}@supports (display: grid){.pcr-app .pcr-swatches{display:grid;align-items:center;grid-template-columns:repeat(auto-fit, 1.75em)}}.pcr-app .pcr-swatches>button{font-size:1em;position:relative;width:calc(1.75em - 5px);height:calc(1.75em - 5px);border-radius:0.15em;cursor:pointer;margin:2.5px;flex-shrink:0;justify-self:center;transition:all 0.15s;overflow:hidden;background:transparent;z-index:1}.pcr-app .pcr-swatches>button::before{position:absolute;content:'';top:0;left:0;width:100%;height:100%;background:url('data:image/svg+xml;utf8, <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2 2"><path fill="white" d="M1,0H2V1H1V0ZM0,1H1V2H0V1Z"/><path fill="gray" d="M0,0H1V1H0V0ZM1,1H2V2H1V1Z"/></svg>');background-size:6px;border-radius:.15em;z-index:-1}.pcr-app .pcr-swatches>button::after{content:'';position:absolute;top:0;left:0;width:100%;height:100%;background:var(--pcr-color);border:1px solid rgba(0,0,0,0.05);border-radius:0.15em;box-sizing:border-box}.pcr-app .pcr-swatches>button:hover{filter:brightness(1.05)}.pcr-app .pcr-swatches>button:not(.pcr-active){box-shadow:none}.pcr-app .pcr-interaction{display:flex;flex-wrap:wrap;align-items:center;margin:0 -0.2em 0 -0.2em}.pcr-app .pcr-interaction>*{margin:0 0.2em}.pcr-app .pcr-interaction input{letter-spacing:0.07em;font-size:0.75em;text-align:center;cursor:pointer;color:#75797e;background:#f1f3f4;border-radius:.15em;transition:all 0.15s;padding:0.45em 0.5em;margin-top:0.75em}.pcr-app .pcr-interaction input:hover{filter:brightness(0.975)}.pcr-app .pcr-interaction input:focus{box-shadow:0 0 0 1px rgba(255,255,255,0.85),0 0 0 3px rgba(66,133,244,0.75)}.pcr-app .pcr-interaction .pcr-result{color:#75797e;text-align:left;flex:1 1 8em;min-width:8em;transition:all 0.2s;border-radius:.15em;background:#f1f3f4;cursor:text}.pcr-app .pcr-interaction .pcr-result::-moz-selection{background:#4285f4;color:#fff}.pcr-app .pcr-interaction .pcr-result::selection{background:#4285f4;color:#fff}.pcr-app .pcr-interaction .pcr-type.active{color:#fff;background:#4285f4}.pcr-app .pcr-interaction .pcr-save,.pcr-app .pcr-interaction .pcr-cancel,.pcr-app .pcr-interaction .pcr-clear{color:#fff;width:auto}.pcr-app .pcr-interaction .pcr-save,.pcr-app .pcr-interaction .pcr-cancel,.pcr-app .pcr-interaction .pcr-clear{color:#fff}.pcr-app .pcr-interaction .pcr-save:hover,.pcr-app .pcr-interaction .pcr-cancel:hover,.pcr-app .pcr-interaction .pcr-clear:hover{filter:brightness(0.925)}.pcr-app .pcr-interaction .pcr-save{background:#4285f4}.pcr-app .pcr-interaction .pcr-clear,.pcr-app .pcr-interaction .pcr-cancel{background:#f44250}.pcr-app .pcr-interaction .pcr-clear:focus,.pcr-app .pcr-interaction .pcr-cancel:focus{box-shadow:0 0 0 1px rgba(255,255,255,0.85),0 0 0 3px rgba(244,66,80,0.75)}.pcr-app .pcr-selection .pcr-picker{position:absolute;height:18px;width:18px;border:2px solid #fff;border-radius:100%;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.pcr-app .pcr-selection .pcr-color-palette,.pcr-app .pcr-selection .pcr-color-chooser,.pcr-app .pcr-selection .pcr-color-opacity{position:relative;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;display:flex;flex-direction:column;cursor:grab;cursor:-webkit-grab}.pcr-app .pcr-selection .pcr-color-palette:active,.pcr-app .pcr-selection .pcr-color-chooser:active,.pcr-app .pcr-selection .pcr-color-opacity:active{cursor:grabbing;cursor:-webkit-grabbing}.pcr-app[data-theme='classic']{width:28.5em;max-width:95vw;padding:0.8em}.pcr-app[data-theme='classic'] .pcr-selection{display:flex;justify-content:space-between;flex-grow:1}.pcr-app[data-theme='classic'] .pcr-selection .pcr-color-preview{position:relative;z-index:1;width:2em;display:flex;flex-direction:column;justify-content:space-between;margin-right:0.75em}.pcr-app[data-theme='classic'] .pcr-selection .pcr-color-preview::before{position:absolute;content:'';top:0;left:0;width:100%;height:100%;background:url('data:image/svg+xml;utf8, <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2 2"><path fill="white" d="M1,0H2V1H1V0ZM0,1H1V2H0V1Z"/><path fill="gray" d="M0,0H1V1H0V0ZM1,1H2V2H1V1Z"/></svg>');background-size:.5em;border-radius:.15em;z-index:-1}.pcr-app[data-theme='classic'] .pcr-selection .pcr-color-preview .pcr-last-color{cursor:pointer;border-radius:0.15em 0.15em 0 0;z-index:2}.pcr-app[data-theme='classic'] .pcr-selection .pcr-color-preview .pcr-current-color{border-radius:0 0 0.15em 0.15em}.pcr-app[data-theme='classic'] .pcr-selection .pcr-color-preview .pcr-last-color,.pcr-app[data-theme='classic'] .pcr-selection .pcr-color-preview .pcr-current-color{background:var(--pcr-color);width:100%;height:50%}.pcr-app[data-theme='classic'] .pcr-selection .pcr-color-palette{width:100%;height:8em;z-index:1}.pcr-app[data-theme='classic'] .pcr-selection .pcr-color-palette .pcr-palette{flex-grow:1;border-radius:.15em}.pcr-app[data-theme='classic'] .pcr-selection .pcr-color-palette .pcr-palette::before{position:absolute;content:'';top:0;left:0;width:100%;height:100%;background:url('data:image/svg+xml;utf8, <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2 2"><path fill="white" d="M1,0H2V1H1V0ZM0,1H1V2H0V1Z"/><path fill="gray" d="M0,0H1V1H0V0ZM1,1H2V2H1V1Z"/></svg>');background-size:.5em;border-radius:.15em;z-index:-1}.pcr-app[data-theme='classic'] .pcr-selection .pcr-color-chooser,.pcr-app[data-theme='classic'] .pcr-selection .pcr-color-opacity{margin-left:0.75em}.pcr-app[data-theme='classic'] .pcr-selection .pcr-color-chooser .pcr-picker,.pcr-app[data-theme='classic'] .pcr-selection .pcr-color-opacity .pcr-picker{left:50%;transform:translateX(-50%)}.pcr-app[data-theme='classic'] .pcr-selection .pcr-color-chooser .pcr-slider,.pcr-app[data-theme='classic'] .pcr-selection .pcr-color-opacity .pcr-slider{width:8px;flex-grow:1;border-radius:50em}.pcr-app[data-theme='classic'] .pcr-selection .pcr-color-chooser .pcr-slider{background:linear-gradient(to bottom, red, #ff0, lime, cyan, blue, #f0f, red)}.pcr-app[data-theme='classic'] .pcr-selection .pcr-color-opacity .pcr-slider{background:linear-gradient(to bottom, transparent, black),url('data:image/svg+xml;utf8, <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2 2"><path fill="white" d="M1,0H2V1H1V0ZM0,1H1V2H0V1Z"/><path fill="gray" d="M0,0H1V1H0V0ZM1,1H2V2H1V1Z"/></svg>');background-size:100%, 50%}
`;
然后再弄一个注册表,保证各个地方的颜色选择器获取到的颜色一致
import { 注册表 } from "siyuan-noob/commonStruct";
一开始的时候,我们就把主题目录下的 custom.css 重新插入回来(因为它被干掉了嘛)
let currentThemeElement =
document.querySelector("#themeCustomStyle") ||
document.querySelector("#themeStyle") ||
document.querySelector("#themeDefaultStyle");
let currentThemeURL = currentThemeElement.getAttribute("href").split("?")[0];
if (!document.querySelector("#themeCustomStyle")) {
let customThemeStyle = document.createElement("link");
customThemeStyle.setAttribute("id", "themeCustomStyle");
customThemeStyle.setAttribute("type", "text/css");
customThemeStyle.setAttribute("rel", "stylesheet");
customThemeStyle.setAttribute(
"href",
currentThemeURL.replace("theme.css", "custom.css")
);
document.head.appendChild(customThemeStyle);
}
然后还需要一个函数,用来从样式表元素(link 或者 style)来获取样式,就像这样
function getColor(element, name) {
console.log(element.sheet)
let rootRule
for (let i = 0; i < element.sheet.cssRules.length; i++) {
if (element.sheet.cssRules[i].selectorText === ":root") {
rootRule = element.sheet.cssRules[i];
break;
}
}
let namedColor;
namedColor = rootRule.style.getPropertyValue(name);
namedColor = namedColor ? namedColor.trim() : null;
return namedColor
}
再搞一个函数,用来临时设置样式
async function setstyle(items, save) {
let tempstring = "";
for (let item of items) {
item.value !== undefined
? (tempstring = tempstring + item.id + ":" + item.value + ";\n")
: null;
}
tempstring = "{" + tempstring + "}";
let css = ":root" + tempstring;
let styleSetterTemp = document.getElementById("styleSetterTemp");
if (!styleSetterTemp) {
styleSetterTemp = document.createElement("style");
styleSetterTemp.innerText = css;
styleSetterTemp.setAttribute("id", "styleSetterTemp");
document.head.appendChild(styleSetterTemp);
} else {
styleSetterTemp.innerText = css;
document.head.appendChild(styleSetterTemp);
}
save ? saveStyle(css) : null;
}
当确定下来之后,把它保存到 custom.css
async function saveStyle(css) {
let blob = new Blob([css], {
type: "text/css",
});
let file = new File([blob], "custom.css", {
lastModified: Date.now(),
});
let data = new FormData();
data.append(
"path",
"/conf/" + currentThemeURL.replace("theme.css", "custom.css")
);
data.append("file", file);
data.append("isDir", false);
data.append("modTime", Date.now());
await fetch("/api/file/putFile", {
method: "POST",
body: data,
});
if (!document.querySelector("#themeCustomStyle")) {
let customThemeStyle = document.createElement("link");
customThemeStyle.setAttribute("id", "themeCustomStyle");
customThemeStyle.setAttribute("type", "text/css");
customThemeStyle.setAttribute("rel", "stylesheet");
customThemeStyle.setAttribute(
"href",
currentThemeURL.replace("theme.css", "custom.css")
);
document.head.appendChild(customThemeStyle);
} else {
let customThemeStyle = document.querySelector("#themeCustomStyle");
customThemeStyle.remove();
document.head.appendChild(customThemeStyle);
}
document.querySelectorAll("#styleSetterTemp").forEach((el) => {
el.remove();
});
}
啊,终于可以开始写我们的 ThemeColorPicker 了
class ThemeColorPicker extends HTMLElement {
constructor() {
super();
this.css变量注册表 = new 注册表("css变量");
}
getDefault() {
try {
let currentThemeElement =
document.querySelector("#themeCustomStyle") ||
document.querySelector("#themeStyle") ||
document.querySelector("#themeDefaultStyle");
this.defaultColor = getColor(currentThemeElement, this.name);
} catch (e) {}
if (!this.defaultColor) {
this.defaultColor = getColor(
document.querySelector("#themeStyle") ||
document.querySelector("#themeDefaultStyle"),
this.name
);
}
if (!this.defaultColor) {
this.defaultColor = getColor(
document.querySelector("#themeDefaultStyle"),
this.name
);
}
return this.defaultColor;
}
connectedCallback() {
document.getElementById("#pickrcss")
? null
: document.head.insertAdjacentHTML(
"beforeend",
` <style id='pickrcss'>
${pickClassic}
</style>`
);
this.attachShadow({ mode: "open" });
this.shadowRoot.innerHTML = `
<style>
${pickClassic}
</style>
<link href="/stage/build/desktop/base.56dfeeea3d0c5f4a8fef.css" rel="stylesheet">
<label class="b3-label fn__flex config__item">
<div class="fn__flex-1 fn__flex-center">
${this.name}
<div class="b3-label__text">${this.getAttribute("memo")}</div>
</div>
<span class="fn__space"></span>
<span class="pickr"></span>
</label>
`;
if (this.getAttribute("simple")) {
this.shadowRoot.innerHTML = `
<style>
${pickClassic}
</style>
<link href="/stage/build/desktop/base.56dfeeea3d0c5f4a8fef.css" rel="stylesheet">
<span class="pickr"></span>
`;
}
this.pickr = Pickr.create({
container: "body",
el: this.shadowRoot.querySelector(".pickr"),
default: this.getDefault(),
theme: "classic", // or 'monolith', or 'nano'
comparison: false,
components: {
preview: true,
opacity: true,
hue: true,
interaction: {
hex: true,
rgba: true,
hsla: true,
hsva: true,
cmyk: true,
input: true,
save: true,
clear: true,
},
},
});
this.pickr.on("change", (color) => {
let colorValue = color ? color.toRGBA() : "";
this.css变量注册表.注册({
id: this.name,
value: colorValue,
});
setstyle(this.css变量注册表.list(), false);
});
this.pickr.on("save", (color) => {
let colorValue = color ? color.toRGBA() : "";
this.css变量注册表.注册({
id: this.name,
value: colorValue,
});
setstyle(this.css变量注册表.list(), true);
});
this.pickr.on("clear", (color) => {
this.pickr.setColor(this.getDefault());
this.css变量注册表.注销({
id: this.name,
value: undefined,
});
setstyle(this.css变量注册表.list(), true);
});
}
static get observedAttributes() {
return ["name"];
}
attributeChangedCallback(name, oldValue, newValue) {
if (name === "name") {
this.name = newValue;
}
}
}
customElements.define("theme-color-picker", ThemeColorPicker);
使用方法:
在主题中使用的时候,需要先运行这个代码片段,然后把运行产物(在 /snippets/jsInNote
里面)引入.
直接使用这个代码片段的话,在 html 块里面插入就行了
<theme-color-picker name="--b3-theme-background" memo="背景色" ></theme-color-picker>
theme-color-picker
元素的 name
属性就是要调节的 css 变量,memo
是显示出来的提示.
当然你也可以整点别的,simple 属性为 true
,这样可以自己写其他的部分,最后的效果反正就是这样:
用思源笔记折腾记录 - 运行你的笔记 - 链滴 (ld246.com)里面的方法来弄,运行到代码片段之后就可以用了
搞完收工,自己写主题配色器去吧~~
如果你想要了解更多原理的话,就自己去搜一下 stylesheet 对象吧,这个可以获取各种样式元素的规则,我就不罗嗦了.
水完收工,如果这玩意对你有用可以去爱发电给我买杯咖啡哒
leolee9086 正在创作一些简单的技术教程和小工具,以及设计方面内容 | 爱发电 (afdian.net)
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于