Skip to content

Java知识体系总览

dataviewjs
// === 配置区 ===
const coverField = "banner_img"; // 封面图属性名
const cardWidth = "220px";       // 卡片宽度

// 1. 获取当前页面的出链
const outlinks = dv.current().file.outlinks;
const currentFile = app.vault.getAbstractFileByPath(dv.current().file.path);

if (currentFile && outlinks.length > 0) {
  const fileContent = await app.vault.read(currentFile);
  
  // 2. 排序:按链接在正文中的出现位置
  // 不能拿 link.display 去找:[别名]() 的 display 是"别名",正文里根本没有「[[别名」,
  // indexOf 会返回 -1,这张卡就被过滤掉了。这里改成用"链接目标"匹配,
  // 并要求后面紧跟 | # ^ ] 之一,避免 [笔记]() 误命中 [笔记2]()。
  const escapeRe = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
  const linkPosition = (content, link) => {
    const base = link.path.split("/").pop().replace(/\.md$/, "");
    const targets = [link.path, link.path.replace(/\.md$/, ""), base].filter(Boolean);
    let pos = -1;
    for (const t of new Set(targets)) {
      const re = new RegExp(`\\[\\[\\s*${escapeRe(t)}\\s*(?=[|#^\\]])`);
      const m = re.exec(content);
      if (m && (pos === -1 || m.index < pos)) pos = m.index;
    }
    return pos;
  };

  const selfPath = dv.current().file.path;
  const seenPaths = new Set();
  const sortedLinks = [];
  for (const link of outlinks) {
    // 先解析成真实文件路径:[名称]()、[路径/名称]()、[别名]()、[名称#标题]() 都会落到同一篇
    const dest = app.metadataCache.getFirstLinkpathDest(link.path, selfPath);
    const path = dest ? dest.path : link.path;
    if (!path || path === selfPath) continue; // 跳过 [#本页小标题]() 这类自链接
    if (seenPaths.has(path)) continue;        // 同一篇被链接多次,只出一张卡
    seenPaths.add(path);
    sortedLinks.push({ link, path, index: linkPosition(fileContent, link) });
  }
  // 找不到出现位置的排到最后:宁可多显示一张,也别让它莫名消失
  sortedLinks.sort((a, b) => {
    const x = a.index === -1 ? Number.MAX_SAFE_INTEGER : a.index;
    const y = b.index === -1 ? Number.MAX_SAFE_INTEGER : b.index;
    return x - y;
  });

  // 3. 创建网格容器
  // 【关键】不能写 dv.el("div", ""):空字符串会走 Dataview 的 renderValue,
  // 在容器里塞一个没有内容的 <span>。它也是网格项,会独占第一行第一列,
  // 于是整排卡片右移一格、最后一张掉到第二行,左上角留出一块空白。
  const container = dv.el("div");
  container.empty(); // 双保险:清掉任何非卡片子节点
  container.style.display = "grid";
  container.style.gridTemplateColumns = `repeat(auto-fill, minmax(${cardWidth}, 1fr))`;
  container.style.gap = "16px";
  container.style.marginLeft = "0";
  container.style.paddingLeft = "0";
  container.style.width = "100%";

  // 4. 遍历生成卡片
  for (const { link, path } of sortedLinks) {
    const page = dv.page(path);
    if (!page) continue;

    // 处理封面图
    const cover = page[coverField];
    let imgSrc = "";
    if (cover) {
      if (cover.startsWith("[[")) {
        const imgName = cover.replace(/\[\[|\]\]/g, '');
        const imgFile = app.metadataCache.getFirstLinkpathDest(imgName, page.file.path);
        if (imgFile) imgSrc = app.vault.getResourcePath(imgFile);
      } else {
        imgSrc = cover;
      }
    }

    // 创建卡片外壳
    const card = container.createEl("a", { href: page.file.path, cls: "internal-link" });
    card.style.textDecoration = "none";
    card.style.color = "inherit";
    
    const cardInner = card.createEl("div");
    cardInner.style.background = "var(--background-secondary)";
    cardInner.style.borderRadius = "8px";
    cardInner.style.overflow = "hidden";
    cardInner.style.boxShadow = "0 2px 4px rgba(0,0,0,0.1)";
    cardInner.style.transition = "transform 0.2s";
    
    // 封面图区域
    if (imgSrc) {
      const img = cardInner.createEl("img");
      img.src = imgSrc;
      img.style.width = "100%";
      img.style.height = "140px";
      img.style.objectFit = "cover";
      img.style.display = "block";
    } else {
      const noCover = cardInner.createEl("div");
      noCover.style.height = "140px";
      noCover.style.display = "flex";
      noCover.style.alignItems = "center";
      noCover.style.justifyContent = "center";
      noCover.style.background = "var(--background-modifier-border)";
      noCover.style.color = "var(--text-muted)";
      noCover.textContent = "无封面";
    }
    
    // 文字信息区域
    const textDiv = cardInner.createEl("div");
    textDiv.style.padding = "12px";
    
    // 标题:优先用链接里写的别名([别名]()),没写别名就用文件名。
    // link.display 只有 [别名]() 这种写法才有值,[文件]() 时是 undefined。
    const linkAlias = (link.display || "").trim();
    const titleDiv = textDiv.createEl("div");
    titleDiv.textContent = linkAlias || page.file.name;
    titleDiv.title = page.file.name; // 悬停显示真实文件名,别名太抽象也能对回去
    titleDiv.style.fontWeight = "bold";
    titleDiv.style.marginBottom = "4px";
    titleDiv.style.whiteSpace = "nowrap";
    titleDiv.style.overflow = "hidden";
    titleDiv.style.textOverflow = "ellipsis";
    
    const folderDiv = textDiv.createEl("div");
    // 优化显示:只显示最后一级文件夹,避免太长
    const folderName = page.file.folder;
    folderDiv.textContent = folderName ? folderName.split('/').pop() : "根目录";
    folderDiv.style.fontSize = "12px";
    folderDiv.style.color = "var(--text-muted)";
  }
} else if (outlinks.length === 0) {
  dv.paragraph("当前笔记没有出链");
}

后面可能是大幅度改动相关内容,暂时先不动了,之前的问题是维护难度很大,层级关系写的挺好的,但是自己自己后面难以梳理,而且很多东西,的确是有概念就行,还是需要实操和理解,编程能力和细节等。


什么阶段做什么事情。