ai写的一个脚注 大纲化插件

脚注

重要提示 如果你不知道我下面发的三段代码怎么用,那就不建议用,ai写的,有没有错会不会造成影响 我也不知道!!!
重要提示 如果你不知道我下面发的三段代码怎么用,那就不建议用,ai写的,有没有错会不会造成影响 我也不知道!!!
重要提示 如果你不知道我下面发的三段代码怎么用,那就不建议用,ai写的,有没有错会不会造成影响 我也不知道!!!

main.js

/* main.js - Footnote Compass (Cursor Follow + Smart Persistence) */
const { Plugin, ItemView, debounce } = require('obsidian');

const VIEW_TYPE_FOOTNOTE = "footnote-compass-view";

class FootnoteListView extends ItemView {
    constructor(leaf) {
        super(leaf);
        // 缓存当前的脚注数据
        this.cachedRefs = []; 
        // 【新增】记忆最后一次活跃的 Markdown 视图
        this.lastActiveMarkdownView = null;
    }

    getViewType() { return VIEW_TYPE_FOOTNOTE; }
    getDisplayText() { return "脚注大纲"; }
    getIcon() { return "message-circle-more"; }

    async onOpen() {
        this.renderStructure();
        // 稍微延迟,等待工作区加载
        setTimeout(() => {
            this.checkAndUpdate();
        }, 100);
    }

    renderStructure() {
        const container = this.containerEl.children[1];
        container.empty();
        container.createEl("h4", { text: "" });
        container.createDiv({ cls: "footnote-list-root" });
    }

    clearView(msg) {
        const container = this.containerEl.children[1];
        const listRoot = container.querySelector(".footnote-list-root");
        if (listRoot) {
            listRoot.empty();
            if(msg) listRoot.createDiv({ cls: "footnote-empty", text: msg });
        }
        this.cachedRefs = [];
    }

    // 【新增】智能寻找最佳 Markdown 视图 (用于快捷键切换回来时恢复显示)
    findBestMarkdownLeaf() {
        // 1. 如果当前激活的就是 Markdown,直接返回
        const active = this.app.workspace.activeLeaf;
        if (active && active.view.getViewType() === 'markdown') {
            return active;
        }

        // 2. 如果之前有记忆,且记忆还未关闭,返回记忆
        if (this.lastActiveMarkdownView && this.lastActiveMarkdownView.leaf && this.lastActiveMarkdownView.leaf.view.getViewType() === 'markdown') {
            return this.lastActiveMarkdownView.leaf;
        }

        // 3. 如果都没有,去工作区找“最近的一个 Markdown 文件”
        const recent = this.app.workspace.getMostRecentLeaf && this.app.workspace.getMostRecentLeaf();
        if (recent && recent.view.getViewType() === 'markdown') {
            return recent;
        }

        // 4. 实在不行,找第一个 Markdown
        const leaves = this.app.workspace.getLeavesOfType('markdown');
        if (leaves.length > 0) return leaves[0];

        return null;
    }

    // --- 主更新入口:逻辑已修改为“非白板即保留” ---
    checkAndUpdate() {
        const activeLeaf = this.app.workspace.activeLeaf;
        if (!activeLeaf) return;

        const viewType = activeLeaf.view.getViewType();

        // 1. 白板 (Canvas) -> 必须清空
        if (viewType === 'canvas') {
            this.clearView("当前是白板 (Canvas)");
            this.lastActiveMarkdownView = null; // 清空记忆
            return;
        }

        // 2. Markdown -> 正常更新并记忆
        if (viewType === 'markdown') {
            this.lastActiveMarkdownView = activeLeaf.view;
            this.updateView(activeLeaf.view);
            return;
        }

        // 3. 其他所有情况 (快捷键切换到本插件、侧边栏、第三方插件)
        // 策略:不要清空!尝试找回最近的 Markdown 内容显示
        const bestLeaf = this.findBestMarkdownLeaf();
        if (bestLeaf) {
            this.updateView(bestLeaf.view);
        } else {
            // 如果真的连一个 Markdown 都找不到,那就保持原样不动
        }
    }

    // 解析文本,构建列表
    updateView(view) {
        const container = this.containerEl.children[1];
        const listRoot = container.querySelector(".footnote-list-root");
        if (!listRoot) return;
        
        // 这里的 view 可能是后台的 view,需要防错
        let text = "";
        try {
            text = view.editor.getValue();
        } catch (e) {
            return;
        }
        
        // 先清空 DOM
        listRoot.empty();

        const lines = text.split("\n");
        const definitionMap = new Map();
        const defRegex = /^\[\^([^\]]+)\]:\s*(.*)$/;

        // 1. 找定义
        lines.forEach((line) => {
            const match = line.match(defRegex);
            if (match) definitionMap.set(match[1], match[2]);
        });

        // 2. 找引用
        this.cachedRefs = [];
        const refRegex = /\[\^([^\]]+)\](?!:)/g;

        lines.forEach((line, lineIndex) => {
            // 排除文末定义行
            if (defRegex.test(line)) return;

            let match;
            while ((match = refRegex.exec(line)) !== null) {
                const key = match[1];
                const content = definitionMap.get(key) || "(未找到定义内容)";
                
                this.cachedRefs.push({
                    key: key,
                    content: content,
                    line: lineIndex,
                    col: match.index,
                    el: null
                });
            }
        });

        if (this.cachedRefs.length === 0) {
            listRoot.createDiv({ cls: "footnote-empty", text: "当前文档没有脚注引用" });
            return;
        }

        const listContainer = listRoot.createDiv({ cls: "footnote-compass-container" });

        // 3. 渲染 DOM
        this.cachedRefs.forEach((ref, index) => {
            const itemEl = listContainer.createDiv({ cls: "footnote-item" });
            ref.el = itemEl; 

            itemEl.createDiv({ cls: "footnote-key", text: `[^${ref.key}]` });
            itemEl.createDiv({ cls: "footnote-content", text: ref.content });

            // 点击跳转
            itemEl.addEventListener("click", () => {
                this.app.workspace.setActiveLeaf(view.leaf, true, true);
                view.editor.setCursor({ line: ref.line, ch: ref.col + 2 });
                view.editor.scrollIntoView({ from: { line: ref.line, ch: ref.col }, to: { line: ref.line, ch: ref.col } }, true);
                view.editor.focus();
                
                this.highlightByRefIndex(index);
            });
        });

        // 渲染完立刻做一次高亮检查
        this.syncHighlightWithCursor(view);
    }

    // --- 核心功能:根据光标位置高亮 (保持你要求的逻辑) ---
    syncHighlightWithCursor(view) {
        if (!this.cachedRefs.length || !view || view.getViewType() !== 'markdown') return;

        // 注意:如果是通过快捷键切换过来,view 可能不在前台,但 editor 还是可读的
        const cursor = view.editor.getCursor();
        const currentLine = cursor.line;

        let activeIndex = -1;

        for (let i = 0; i < this.cachedRefs.length; i++) {
            const ref = this.cachedRefs[i];
            if (ref.line <= currentLine) {
                activeIndex = i;
            } else {
                break;
            }
        }

        this.highlightByRefIndex(activeIndex);
    }

    highlightByRefIndex(index) {
        this.cachedRefs.forEach((ref, i) => {
            if (!ref.el) return;
            if (i === index) {
                if (!ref.el.classList.contains("is-active")) {
                    ref.el.addClass("is-active");
                    ref.el.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
                }
            } else {
                ref.el.removeClass("is-active");
            }
        });
    }
}

module.exports = class FootnoteCompassPlugin extends Plugin {
    async onload() {
        this.registerView(VIEW_TYPE_FOOTNOTE, (leaf) => new FootnoteListView(leaf));

        this.addRibbonIcon('message-circle-more', '打开脚注大纲', () => {
            this.activateView();
        });

        // 注册命令,方便快捷键调用
        this.addCommand({
            id: 'open-sidebar',
            name: '打开/显示侧边栏',
            callback: () => { this.activateView(); }
        });

        // 1. 内容改变/视图切换/布局改变 -> 更新大纲内容
        const debouncedUpdate = debounce(() => {
            const leaves = this.app.workspace.getLeavesOfType(VIEW_TYPE_FOOTNOTE);
            leaves.forEach(leaf => {
                if (leaf.view instanceof FootnoteListView) leaf.view.checkAndUpdate();
            });
        }, 200, true);

        // 2. 光标移动 -> 只更新高亮 (保持你的逻辑)
        const handleCursorActivity = debounce(() => {
            // 这里我们不仅要找 activeLeaf,还要考虑如果是侧边栏激活的情况
            // 简单处理:让所有存在的脚注插件实例去同步它目前持有 view 的光标
            const leaves = this.app.workspace.getLeavesOfType(VIEW_TYPE_FOOTNOTE);
            leaves.forEach(leaf => {
                if (leaf.view instanceof FootnoteListView) {
                    // 如果插件当前显示的是某个 Markdown view 的内容,就去同步那个 view 的光标
                    const targetView = leaf.view.lastActiveMarkdownView || this.app.workspace.activeLeaf?.view;
                    if(targetView && targetView.getViewType() === 'markdown') {
                         leaf.view.syncHighlightWithCursor(targetView);
                    }
                }
            });
        }, 50, true);

        this.registerEvent(this.app.workspace.on('active-leaf-change', debouncedUpdate));
        this.registerEvent(this.app.workspace.on('editor-change', debouncedUpdate));
        this.registerEvent(this.app.workspace.on('layout-change', debouncedUpdate));

        // --- 保持你要求的监听方式 ---
        this.registerDomEvent(document, 'click', handleCursorActivity);
        this.registerDomEvent(document, 'keyup', handleCursorActivity);
    }

    async activateView() {
        const { workspace } = this.app;
        let leaf = null;
        const leaves = workspace.getLeavesOfType(VIEW_TYPE_FOOTNOTE);
        if (leaves.length > 0) {
            leaf = leaves[0];
            workspace.revealLeaf(leaf);
        } else {
            leaf = workspace.getRightLeaf(false);
            await leaf.setViewState({ type: VIEW_TYPE_FOOTNOTE, active: true });
            workspace.revealLeaf(leaf);
        }
        
        // 激活时强制刷新一次,确保快捷键切过来有内容
        if (leaf.view instanceof FootnoteListView) leaf.view.checkAndUpdate();
    }
};

styles.css

/* 整个容器的内边距 */
.footnote-compass-container {
    padding: 12px;
}

/* 单个脚注卡片 */
.footnote-item {
    /* 布局 */
    display: flex;
    flex-direction: column;
    
    /* 外观 */
    padding: 10px 12px;
    margin-bottom: 8px;
    border-radius: 6px;
    
    /* 左侧线条(默认灰色) */
    
    /* 交互 */
    cursor: pointer;
    transition: all 0.2s ease;
}

/* 鼠标悬停时的效果 */
.footnote-item:hover {
    background-color: var(--background-modifier-hover);
    transform: translateX(2px);
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}

/* --- 关键修改:隐藏 [^1] --- */
.footnote-key {
    display: none;
}

/* 脚注内容样式 */
.footnote-content {
    font-size: 18px; /* 稍微调小一点,18px在大纲里太大了,你可以自己改回18 */
    color: var(--text-muted);
    line-height: 1.5;
    word-wrap: break-word;
}

/* 悬停时文字颜色变深 */
.footnote-item:hover .footnote-content {
    color: var(--text-normal);
}

/* 空状态提示 */
.footnote-empty {
    color: var(--text-faint);
    padding: 20px;
    text-align: center;
    font-size: 13px;
}

/* =========================================
   新增:被选中的状态 (Active State)
   ========================================= */

/* 选中时,左侧线条变红 */
.footnote-item.is-active {
    background-color: var(--background-primary); /* 稍微突出的背景 */
    box-shadow: 0 2px 8px rgba(255, 0, 0, 0.1); /* 微微的红色光晕 */
}

/* 选中时,文字内容强制变红 */
.footnote-item.is-active .footnote-content {
    color: rgb(0, 157, 255) !important;
    font-weight: bold; /* 可选:加粗 */
}

manifest.json

{
	"id": "footnote-compass",
	"name": "Footnote Compass",
	"version": "1.0.0",
	"minAppVersion": "0.15.0",
	"description": "在侧边栏显示所有脚注,展示脚注内容,点击跳转到正文引用位置。",
	"author": "AI Assistant",
	"authorUrl": "",
	"isDesktopOnly": false
}

美化css

/* 隐藏 Obsidian 核心 Page Preview (页面预览/脚注预览) 弹窗的标题栏 */

/* 1. 针对核心弹窗的标题栏,使用您提供的类名 */
div.popover-titlebar {
    display: none !important;
}

/* 2. 确保内容区域向上移动,填补被隐藏标题栏的位置 */
/* 核心弹窗通常使用 .popover.hover-popover-note 作为父容器 */
.popover.hover-popover-note .popover-content {
    /* 移除内容区域顶部的默认内边距,确保内容贴边 */
    padding-top: 0 !important;
}

/* 
 * CSS 片段名称: increase-footnote-popup-text-size.css
 * 描述: 增大页面预览弹窗(包括脚注弹窗)中的文字大小
 */

.popover.hover-popover .markdown-preview-view {
    /* 
     * 调整为你希望的字体大小。 
     * 1.1em 比默认字体(1.0em)增大 10%。
     */
    font-size: 1.4em; 
    
    /* 可选:如果你想同时增大行高,使阅读更舒适 */
    line-height: 1.6;
}


/* 
 * 针对 编辑模式 (包括源文件模式和实时预览模式) 增大文字大小 
 * 对应的主体容器类是 .cm-editor 或 .markdown-source-view
 */


/* 
 * CSS 片段名称: increase-footnote-popup-text-size.css
 * 描述: 仅增大悬停弹窗(包括脚注弹窗)内的文字大小。
 */

/* 
 * .popover.hover-popover 目标是 Obsidian 的所有悬停预览弹窗。
 * .markdown-preview-view 目标是弹窗内的预览渲染内容。
 */
.popover.hover-popover .markdown-preview-view {
    /* 
     * 调整为你希望的字体大小。 
     * 1.1em 比默认字体(1.0em)增大 10%。
     */
    font-size: 1.5em; 
    
    /* 可选:如果你想同时增大行高 */
    line-height: 1.6;
}


/* 
 * 针对所有同时是“悬停弹窗”且“包含编辑器”的元素
 */
.popover.hover-popover .cm-editor {
    /* 
     * 增大弹窗内的编辑文本大小
     */
    font-size: 1.7em !important; 
}
/* 隐藏处于编辑状态的预览弹窗的边框和外轮廓 */
.popover.hover-popover.is-editing {
    /* 隐藏外轮廓线 (outline) */
    outline: none !important; 
    
    /* 隐藏边框线 (border) */
    border: none !important; 
    
    /* 也可以单独设置边框颜色为透明,确保它不显示 */
    border-color: transparent !important;
}


.cm-s-obsidian span.cm-footref.cm-hmd-barelink, .cm-s-obsidian span.cm-blockid {
    color: var(--text-muted);
    
}

/* 
 * 最终修复方案:隔离源码模式 + 解决符号重复问题。
 * 我们使用 :not(.is-source-mode) + 假设 CodeMirror 编辑器中脚注引用的所有部分都是兄弟元素。
 */

/* ================= 隔离源码模式:使用更强的排除规则 ================= */

/* 选择所有【非源码模式】的 CodeMirror 编辑器内的脚注引用 span */
.markdown-source-view:not(.is-source-mode) span.cm-footref.cm-hmd-barelink {
    /* 1. 隐藏原文本 ([^2] 的每个部分) */
    font-size: 8px !important; 
    line-height: 3 !important; 
    color: #1E1E1E;
    /* 2. 确保不占用空间,并为伪元素定位做准备 */
    margin: 0 !important; 
    padding: 0 !important;
    position: relative;
    display: inline-block; /* 确保它是一个可操作的块级元素 */
}


/* ================= 解决符号重复问题:只在最后一个子元素后插入 ================= */

/* 1. 只在【非源码模式】下,且为【兄弟元素中的最后一个】后面插入符号 */
.markdown-source-view:not(.is-source-mode) span.cm-footref.cm-hmd-barelink:last-child::after {
    /* 插入符号 */
    content: "💬"; 
    
    /* 恢复符号的样式 */
    font-size: 26px; 
    line-height: 1.1; 
    margin-left: -15px; 
    color: var(--text-normal);
    
    /* 确保符号能覆盖任何剩余的边框或背景 */
    background-color: transparent; 
}


/* 2. 确保在第一个元素前没有符号(以防万一) */
.markdown-source-view:not(.is-source-mode) span.cm-footref.cm-hmd-barelink::before {
    content: none !important;
}

/* 3. 移除其他元素的::after,以防不是 last-child 的元素也生成符号 */
.markdown-source-view:not(.is-source-mode) span.cm-footref.cm-hmd-barelink:not(:last-child)::after {
    content: none !important;
}

/* 针对原生页面预览弹窗 */
.popover.hover-popover {
    /* 
     * 这是一个魔法数值。
     * 原生弹窗通常会为了不遮挡文字而留出一段距离。
     * 我们用负边距把它拉回来,让它离鼠标更近。
     */
    margin-top: -95px !important; 
    margin-left: -80px !important;
    margin-bottom: 100px !important;
    
    /* 加上弹窗出现的动画,看起来更丝滑 */
    transition: opacity 0.2s ease-in-out, transform 0.2s ease-in-out;
}

授人以鱼不如授人以渔
我不懂编程啊!!!
其实我更想请教楼主,用哪个AI写?提示词怎么写?
我想参考着写个离线能用的、插入当前农历信息的小插件。

完全不懂编程我不知道啊,我懂啊
学过java c# 还有python
ai是 gemini
没啥提示词 要干啥就告诉他干啥

离线如何算农历,这个怎么滴都得联网调第三方api,本地算农历小白可能不太好搞

网上有农历的js库,应该能离线了吧?