feat(dnfs): 新增主题/样式祖先继承——当前节点缺失时沿树向上搜索并注入

提取 injectSpCss 复用 CSS 注入逻辑,renderSpCss 的 applyCss 去重。
inheritThemeStyle 在 load_notes 中检查当前层 theme/style,缺失时
沿 stack 向根逐级搜索最近祖先,找到后自动注入,支持跨导航取消。
This commit is contained in:
qing committed 2026-08-12 15:32:47 +08:00
1 parent 6db0cf017e
commit d7eb46b3a1
1 file changed
+65 -10
+65 -10
View File
@@ -614,6 +614,25 @@ function themeVarsToText(vars) {
return lines.join('\n');
}
/* 将 theme/style 信封注入为 <style> 元素到 <head>(供卡片渲染和祖先继承复用)。
* 返回创建的 styleEl,cssText 为空时返回 null。 */
function injectSpCss(sp, noteId) {
var is_theme = (sp._sp === 'theme');
var cssText = is_theme ? themeVarsToCss(sp.vars || {}) : String(sp.css || '');
if (!cssText) return null;
/* 同一 noteId 的旧注入先移除 */
if (noteId) {
document.querySelectorAll('.sp-injected-style[data-note-id="' + noteId + '"]')
.forEach(function(s) { s.remove(); });
}
var styleEl = document.createElement('style');
styleEl.className = 'sp-injected-style';
if (noteId) styleEl.setAttribute('data-note-id', noteId);
styleEl.textContent = cssText;
document.head.appendChild(styleEl);
return styleEl;
}
function renderSpCss(container, sp, appendNote, item) {
var is_theme = (sp._sp === 'theme');
var kindLabel = is_theme ? '主题' : '样式';
@@ -646,16 +665,8 @@ function renderSpCss(container, sp, appendNote, item) {
});
function applyCss() {
if (styleEl) return;
/* 同一节点的旧注入先移除(编辑更新 / 重新渲染不产生重复 <style>) */
if (noteId) {
document.querySelectorAll('.sp-injected-style[data-note-id="' + noteId + '"]')
.forEach(function(s) { s.remove(); });
}
styleEl = document.createElement('style');
styleEl.className = 'sp-injected-style';
if (noteId) styleEl.setAttribute('data-note-id', noteId);
styleEl.textContent = cssText;
document.head.appendChild(styleEl);
styleEl = injectSpCss(sp, noteId);
if (!styleEl) return;
toggle_btn.textContent = '停用' + kindLabel;
out.style.display = 'block';
out.textContent = kindLabel + '已生效(本层)';
@@ -686,6 +697,49 @@ function formatBytes(n) {
return n.toFixed(1) + units[i];
}
/* ===== 祖先主题/样式继承 =====
* 当前节点不存在 theme/style 子节点时,沿 stack 向根搜索最近祖先的对应节点,
* 自动注入其 CSS。已存在的不重复搜索(hasTheme/hasStyle 分别控制)。
* 异步 getNode 可能跨导航——用 _inherit_gen 做取消,新导航的 clearAllEffects
* 会移除旧注入。 */
var _inherit_gen = 0;
function inheritThemeStyle() {
var hasTheme = false, hasStyle = false;
for (var i = 0; i < current_children.length; i++) {
var sp = parseSp(current_children[i].content);
if (!sp) continue;
if (sp._sp === 'theme') hasTheme = true;
if (sp._sp === 'style') hasStyle = true;
}
if (hasTheme && hasStyle) return;
var gen = ++_inherit_gen;
(async function() {
for (var i = stack.length - 1; i >= 0; i--) {
if (hasTheme && hasStyle) break;
if (gen !== _inherit_gen) return;
try {
var children = await getNode(stack[i]);
if (!Array.isArray(children)) continue;
for (var j = 0; j < children.length; j++) {
var sp = parseSp(children[j].content);
if (!sp) continue;
if (!hasTheme && sp._sp === 'theme') {
injectSpCss(sp, 'inh-' + children[j].id);
hasTheme = true;
}
if (!hasStyle && sp._sp === 'style') {
injectSpCss(sp, 'inh-' + children[j].id);
hasStyle = true;
}
}
} catch (e) {
console.error('继承主题/样式失败:', e);
}
}
})();
}
/* by qing — 互动统计图标:inline SVG,stroke=currentColor 自动跟随主题色 */
const ICON_EYE = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg>';
const ICON_HEART = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"/></svg>';
@@ -969,6 +1023,7 @@ function load_notes(main_node) {
if (searchtext) searchtext.value = "";
clearAllEffects();
renderCards(current_children);
inheritThemeStyle();
};
/* 把卡片列表渲染到 recommend_list(从 load_notes/搜索过滤复用) */