魔改tag summary插件:标记文本淡化与双链图标简化

效果如下:
PixPin_2025-07-06_13-38-31

  1. 将双链显示为:fountain_pen:图标并置于标签后,点击:fountain_pen:跳转至原文件指定位置
  2. 支持段落、多级列表项、任务列表项等多种元素类型
  3. 在tag summary显示的内容中,点击任意段落、列表项或任务项即可“标记”,已标记的条目透明度降低到40%,鼠标悬停时透明度提升到60%。
  4. 可在插件设置中移除标记,点击"Clear All Marks"按钮,可清除所有已标记的条目

tag summary插件原项目地址: GitHub - macrojd/tag-summary

使用方法:用下面的js替换原来的main.js即可

点击查看 main.js 文件

1 个赞

很棒的创意,可是点击获取你这个main.js文件打不开网页,能给个网盘链接吗?

代码放这里了

main.js
/*
THIS IS A GENERATED/BUNDLED FILE BY ESBUILD
if you want to view the source, please visit the github repository of this plugin
*/

var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __markAsModule = (target) => __defProp(target, "__esModule", { value: true });
var __export = (target, all) => {
  __markAsModule(target);
  for (var name in all)
    __defProp(target, name, { get: all[name], enumerable: true });
};
var __reExport = (target, module2, desc) => {
  if (module2 && typeof module2 === "object" || typeof module2 === "function") {
    for (let key of __getOwnPropNames(module2))
      if (!__hasOwnProp.call(target, key) && key !== "default")
        __defProp(target, key, { get: () => module2[key], enumerable: !(desc = __getOwnPropDesc(module2, key)) || desc.enumerable });
  }
  return target;
};
var __toModule = (module2) => {
  return __reExport(__markAsModule(__defProp(module2 != null ? __create(__getProtoOf(module2)) : {}, "default", module2 && module2.__esModule && "default" in module2 ? { get: () => module2.default, enumerable: true } : { value: module2, enumerable: true })), module2);
};
var __async = (__this, __arguments, generator) => {
  return new Promise((resolve, reject) => {
    var fulfilled = (value) => {
      try {
        step(generator.next(value));
      } catch (e) {
        reject(e);
      }
    };
    var rejected = (value) => {
      try {
        step(generator.throw(value));
      } catch (e) {
        reject(e);
      }
    };
    var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
    step((generator = generator.apply(__this, __arguments)).next());
  });
};

// main.ts
__export(exports, {
  default: () => SummaryPlugin
});
var import_obsidian3 = __toModule(require("obsidian"));

// settings.ts
var import_obsidian = __toModule(require("obsidian"));
var SummarySettingTab = class extends import_obsidian.PluginSettingTab {
  constructor(app2, plugin) {
    super(app2, plugin);
    this.plugin = plugin;
  }
  display() {
    let { containerEl } = this;
    containerEl.empty();
    new import_obsidian.Setting(containerEl).setName("Show Callouts").setDesc("Show the text inside callout blocks").addToggle((toggle) => toggle.setValue(this.plugin.settings.includecallout).onChange((value) => __async(this, null, function* () {
      this.plugin.settings.includecallout = value;
      yield this.plugin.saveSettings();
    })));
    new import_obsidian.Setting(containerEl).setName("Show Link").setDesc("Show link to original note").addToggle((toggle) => toggle.setValue(this.plugin.settings.includelink).onChange((value) => __async(this, null, function* () {
      this.plugin.settings.includelink = value;
      yield this.plugin.saveSettings();
    })));
    new import_obsidian.Setting(containerEl).setName("Remove Tags").setDesc("Remove tags from text").addToggle((toggle) => toggle.setValue(this.plugin.settings.removetags).onChange((value) => __async(this, null, function* () {
      this.plugin.settings.removetags = value;
      yield this.plugin.saveSettings();
    })));
    new import_obsidian.Setting(containerEl).setName("List Items").setDesc("Include only the items of a list that contain the tag, not the entire list.").addToggle((toggle) => toggle.setValue(this.plugin.settings.listparagraph).onChange((value) => __async(this, null, function* () {
      this.plugin.settings.listparagraph = value;
      yield this.plugin.saveSettings();
    })));
    new import_obsidian.Setting(containerEl).setName("Include Child Items").setDesc("Include the child items of a list.").addToggle((toggle) => toggle.setValue(this.plugin.settings.includechildren).onChange((value) => __async(this, null, function* () {
      this.plugin.settings.includechildren = value;
      yield this.plugin.saveSettings();
    })));
    new import_obsidian.Setting(containerEl).setName("Clear All Marks").setDesc("Clear all marked items in tag summaries.").addButton((button) => button.setButtonText("Clear All").setWarning().onClick(() => __async(this, null, function* () {
      this.plugin.settings.markedItems = [];
      this.plugin.markedItems = new Set();
      yield this.plugin.saveSettings();
      // 清除当前页面上所有已标记的元素
      this.plugin.clearAllMarksFromDOM();
      new import_obsidian.Notice("All marks cleared!");
    })));
  }
};

// summarytags.ts
var import_obsidian2 = __toModule(require("obsidian"));
var SummaryModal = class extends import_obsidian2.Modal {
  constructor(app2, onSubmit) {
    super(app2);
    this.onSubmit = onSubmit;
  }
  onOpen() {
    let listTags = [];
    let listFiles = this.app.vault.getMarkdownFiles();
    listFiles.forEach((file) => {
      const cache = app.metadataCache.getFileCache(file);
      listTags = listTags.concat((0, import_obsidian2.getAllTags)(cache));
    });
    const tagsSet = new Set(listTags);
    listTags = Array.from(tagsSet).sort();
    const tagsToExclude = ["None"].concat(listTags);
    let { contentEl } = this;
    contentEl.createEl("h1", { text: "Add Summary" });
    if (listTags.length <= 0) {
      contentEl.setText("There are no tags in your notes");
    } else {
      this.include = listTags[0];
      new import_obsidian2.Setting(contentEl).setName("Select tag to include in the summary").addDropdown((menu) => {
        for (let i = 0; i < listTags.length; i++) {
          menu.addOption(listTags[i], listTags[i]);
        }
        menu.setValue(listTags[0]);
        menu.onChange((value) => {
          this.include = value;
        });
      });
      this.exclude = tagsToExclude[0];
      new import_obsidian2.Setting(contentEl).setName("Select tag to exclude from the summary").addDropdown((menu) => {
        for (let i = 0; i < tagsToExclude.length; i++) {
          menu.addOption(tagsToExclude[i], tagsToExclude[i]);
        }
        menu.setValue(tagsToExclude[0]);
        menu.onChange((value) => {
          this.exclude = value;
        });
      });
      new import_obsidian2.Setting(contentEl).addButton((button) => {
        button.setButtonText("Add Summary").setCta().onClick(() => {
          this.close();
          this.onSubmit(this.include, this.exclude);
        });
      });
    }
  }
  onClose() {
    let { contentEl } = this;
    contentEl.empty();
  }
};

// main.ts
var DEFAULT_SETTINGS = {
  includecallout: true,
  includelink: true,
  removetags: false,
  listparagraph: true,
  includechildren: true,
  markedItems: []
};
var SummaryPlugin = class extends import_obsidian3.Plugin {
  onload() {
    return __async(this, null, function* () {
      yield this.loadSettings();
      this.addSettingTab(new SummarySettingTab(this.app, this));
      
      // 初始化标记状态
      this.markedItems = new Set(this.settings.markedItems || []);
      
      // 添加样式
      this.addStyles();
      
      this.addCommand({
        id: "summary-modal",
        name: "Add Summary",
        editorCallback: (editor) => {
          new SummaryModal(this.app, (include, exclude) => {
            let summary = "```add-summary\n";
            summary += "tags: " + include + "\n";
            if (exclude != "None") {
              summary += "exclude: " + exclude + "\n";
            }
            summary += "```\n";
            editor.replaceRange(summary, editor.getCursor());
          }).open();
        }
      });
      this.registerMarkdownCodeBlockProcessor("add-summary", (source, el, ctx) => __async(this, null, function* () {
        let tags = Array();
        let include = Array();
        let exclude = Array();
        const rows = source.split("\n").filter((row) => row.length > 0);
        rows.forEach((line) => {
          if (line.match(/^\s*tags:[\p{L}0-9_\-/# ]+$/gu)) {
            const content = line.replace(/^\s*tags:/, "").trim();
            let list = content.split(/\s+/).map((tag) => tag.trim());
            list = list.filter((tag) => {
              if (tag.match(/^#[\p{L}]+[^#]*$/u)) {
                return true;
              } else {
                return false;
              }
            });
            tags = list;
          }
          if (line.match(/^\s*include:[\p{L}0-9_\-/# ]+$/gu)) {
            const content = line.replace(/^\s*include:/, "").trim();
            let list = content.split(/\s+/).map((tag) => tag.trim());
            list = list.filter((tag) => {
              if (tag.match(/^#[\p{L}]+[^#]*$/u)) {
                return true;
              } else {
                return false;
              }
            });
            include = list;
          }
          if (line.match(/^\s*exclude:[\p{L}0-9_\-/# ]+$/gu)) {
            const content = line.replace(/^\s*exclude:/, "").trim();
            let list = content.split(/\s+/).map((tag) => tag.trim());
            list = list.filter((tag) => {
              if (tag.match(/^#[\p{L}]+[^#]*$/u)) {
                return true;
              } else {
                return false;
              }
            });
            exclude = list;
          }
        });
        if (tags.length > 0 || include.length > 0) {
          yield this.createSummary(el, tags, include, exclude, ctx.sourcePath);
        } else {
          this.createEmptySummary(el);
        }
      }));
    });
  }
  createEmptySummary(element) {
    const container = createEl("div");
    container.createEl("span", {
      attr: { style: "color: var(--text-error) !important;" },
      text: "There are no blocks that match the specified tags."
    });
    element.replaceWith(container);
  }
  createSummary(element, tags, include, exclude, filePath) {
    return __async(this, null, function* () {
      var _a;
      const validTags = tags.concat(include);
      let listFiles = this.app.vault.getMarkdownFiles();
      listFiles = listFiles.filter((file) => {
        const cache = app.metadataCache.getFileCache(file);
        const tagsInFile = (0, import_obsidian3.getAllTags)(cache);
        if (validTags.some((value) => tagsInFile.includes(value))) {
          return true;
        }
        return false;
      });
      listFiles = listFiles.sort((file1, file2) => {
        if (file1.path < file2.path) {
          return -1;
        } else if (file1.path > file2.path) {
          return 1;
        } else {
          return 0;
        }
      });
      let listContents = yield this.readFiles(listFiles);
      let summary = "";
      listContents.forEach((item) => {
        const fileName = item[0].name.replace(/.md$/g, "");
        const filePath2 = item[0].path;
        let listParagraphs = Array();
        const blocks = item[1].split(/\n\s*\n/).filter((row) => row.trim().length > 0);
        blocks.forEach((paragraph) => {
          let valid = false;
          let listTags = paragraph.match(/#[\p{L}0-9_\-/#]+/gu);
          if (listTags != null && listTags.length > 0) {
            if (!paragraph.contains("```")) {
              valid = this.isValidText(listTags, tags, include, exclude);
            }
          }
          if (valid) {
            // 简化处理:如果段落包含标签,直接添加整个段落
            listParagraphs.push(paragraph);
          }
        });
        listParagraphs.forEach((paragraph) => {
          paragraph += "\n";
          if (this.settings.includelink) {
            paragraph = paragraph.replace(/(#[^\s#]+)/g, function(tag) {
              return tag + " [[" + filePath2 + "|🖋️]]";
            });
          }
          // 再移除标签本身,保留link
          if (this.settings.removetags) {
            paragraph = paragraph.replace(/#[^\s#]+ ?/g, "");
          }
          if (this.settings.includecallout) {
            let callout = "> [!" + fileName + "]\n";
            const rows = paragraph.split("\n");
            rows.forEach((row) => {
              callout += "> " + row + "\n";
            });
            paragraph = callout + "\n\n";
          } else {
            paragraph += "\n\n";
          }
          
          summary += paragraph;
        });
      });
      if (summary != "") {
        let summaryContainer = createEl("div");
        yield import_obsidian3.MarkdownRenderer.renderMarkdown(summary, summaryContainer, (_a = this.app.workspace.getActiveFile()) == null ? void 0 : _a.path, null);
        
        // 为容器添加点击标记功能
        this.addClickMarking(summaryContainer);
        
        element.replaceWith(summaryContainer);
      } else {
        this.createEmptySummary(element);
      }
    });
  }
  readFiles(listFiles) {
    return __async(this, null, function* () {
      let list = [];
      for (let t = 0; t < listFiles.length; t += 1) {
        const file = listFiles[t];
        let content = yield this.app.vault.cachedRead(file);
        list.push([file, content]);
      }
      return list;
    });
  }
  isValidText(listTags, tags, include, exclude) {
    let valid = true;
    if (tags.length > 0) {
      valid = valid && tags.some((value) => listTags.includes(value));
    }
    if (include.length > 0) {
      valid = valid && include.every((value) => listTags.includes(value));
    }
    if (valid && exclude.length > 0) {
      valid = !exclude.some((value) => listTags.includes(value));
    }
    return valid;
  }
  loadSettings() {
    return __async(this, null, function* () {
      this.settings = Object.assign({}, DEFAULT_SETTINGS, yield this.loadData());
    });
  }
  saveSettings() {
    return __async(this, null, function* () {
      yield this.saveData(this.settings);
    });
  }
  
  // 为容器添加点击标记功能
  addClickMarking(container) {
    // 为段落、列表项、任务项等添加点击事件和样式
    const elements = container.querySelectorAll('p, li, .task-list-item');
    elements.forEach((element) => {
      if (element.textContent.trim()) {
        element.classList.add('tag-summary-item');
        element.title = '点击标记为已使用';
        
        // 添加点击事件
        element.addEventListener('click', (event) => {
          // 如果点击的是链接,不处理标记
          if (event.target.tagName === 'A' || event.target.closest('a')) {
            return;
          }
          event.stopPropagation();
          this.toggleMark(element);
        });
      }
    });
    
    // 恢复之前的标记状态
    this.restoreMarkingState(container);
  }
  
  // 切换标记状态
  toggleMark(element) {
    if (element.classList.contains('tag-summary-used')) {
      // 取消标记
      element.classList.remove('tag-summary-used');
      element.title = '点击标记为已使用';
    } else {
      // 标记为已使用
      element.classList.add('tag-summary-used');
      element.title = '点击取消标记';
    }
    
    // 保存标记状态
    this.saveMarkingState(element);
  }
  
  // 保存标记状态到本地存储
  saveMarkingState(element) {
    const text = element.textContent.trim();
    const isMarked = element.classList.contains('tag-summary-used');
    
    if (!this.markedItems) {
      this.markedItems = new Set();
    }
    
    if (isMarked) {
      this.markedItems.add(text);
    } else {
      this.markedItems.delete(text);
    }
    
    // 将Set转换为数组保存到settings
    this.settings.markedItems = Array.from(this.markedItems);
    this.saveSettings();
  }
  
  // 从本地存储恢复标记状态
  restoreMarkingState(container) {
    if (!this.settings.markedItems) {
      return;
    }
    
    this.markedItems = new Set(this.settings.markedItems);
    
    const elements = container.querySelectorAll('.tag-summary-item');
    elements.forEach((element) => {
      const text = element.textContent.trim();
      if (this.markedItems.has(text)) {
        element.classList.add('tag-summary-used');
        element.title = '点击取消标记';
      }
    });
  }
  
  // 清除DOM中所有已标记的元素
  clearAllMarksFromDOM() {
    const markedElements = document.querySelectorAll('.tag-summary-used');
    markedElements.forEach((element) => {
      element.classList.remove('tag-summary-used');
      element.title = '点击标记为已使用';
    });
  }
  
  // 添加CSS样式
  addStyles() {
    const style = document.createElement('style');
    style.textContent = `
      .tag-summary-item {
        cursor: pointer;
        transition: all 0.3s ease;
        border-radius: 4px;
        padding: 2px 4px;
        margin: 1px 0;
      }
      
      .tag-summary-item:hover {
        background-color: var(--background-modifier-hover);
      }
      
      .tag-summary-used {
        opacity: 0.4 !important;
      }
      
      .tag-summary-used:hover {
        opacity: 0.6 !important;
      }
      
      /* 确保列表项也能正确显示 */
      li.tag-summary-item {
        list-style-position: outside;
        margin-left: 20px;
      }
      
      .task-list-item.tag-summary-item {
        display: list-item;
      }
    `;
    document.head.appendChild(style);
  }
};
1 个赞

Clear Unused Images插件扫描整个仓库的 Markdown 文件,识别所有未被引用的图片文件,您觉得这个插件能魔改成,识别某一个笔记中所有链接(包括笔记、图片等)然后将整个库中除了这个笔记和其中链接的文件都删除的功能吗?这样的话就可以实现把这个笔记快速分享给其他人。

如果你的最终目标是分享markdown文件给别人的话,可以找markdown export插件(记得类似的插件有2个,差不多都是这个名字)

1 个赞

好的谢谢 :+1: :+1: :+1:

之前发现原版的tag summary插件的查询结果中没有办法显示图片,只显示图片的路径,不知道您这个修改版能不能实现图片的渲染

使用魔改后的代码,在设置中,“list items”无效了,请楼主看一下。