---
cssclasses:
  - hide-properties
tags:
  - aiassist
modified: 20260405 17:00
created: 20260405 17:00
---

## Обслуживание хранилища

> [!abstract] Назначение
> Инструмент для «гигиены» хранилища: несуществующие ссылки, неиспользуемые вложения, пустые заметки, инвентаризация метаданных.

---

### Несуществующие ссылки

> [!warning] Ссылки на заметки, которые ещё не созданы

```dataviewjs
const unresolvedLinksMap = app.metadataCache.unresolvedLinks;
const res = {};

for (let page in unresolvedLinksMap) {
  if (page.startsWith(".obsidian/") || page.startsWith("Базы/")) continue;
  const unresolved = Object.keys(unresolvedLinksMap[page]);
  if (unresolved.length === 0) continue;
  for (let link of unresolved) {
    if (!res[link]) res[link] = { link, usages: [] };
    res[link].usages.push(dv.fileLink(page));
  }
}

const rows = Object.values(res)
  .map(l => [dv.fileLink(l.link), l.usages.length, l.usages.join(" · ")])
  .sort((a, b) => b[1] - a[1]);

if (rows.length === 0) {
  dv.paragraph("> [!done] Все ссылки ведут на существующие заметки");
} else {
  dv.paragraph(`Найдено **${rows.length}** несуществующих ссылок.`);
  dv.table(["Несуществующая ссылка", "Упоминаний", "Где встречается"], rows);
}
```

---

### Неиспользуемые вложения

> [!warning] Файлы (изображения, PDF и др.), на которые нет ссылок ни из одной заметки

```dataviewjs
const allNonMdFiles = app.vault.getFiles()
  .filter(file => file.extension !== 'md' && file.extension !== 'base')
  .filter(file => !file.path.startsWith(".obsidian/"))
  .filter(file => !file.path.startsWith("."));

const allOutlinks = new Set();
dv.pages().file.outlinks.forEach(link => {
  if (link.path) allOutlinks.add(link.path);
});

const notReferenced = allNonMdFiles.filter(file => !allOutlinks.has(file.path));

if (notReferenced.length === 0) {
  dv.paragraph("> [!done] Все вложения используются");
} else {
  dv.paragraph(`Найдено **${notReferenced.length}** неиспользуемых вложений.`);
  dv.table(
    ["Файл", "Папка", "Размер"],
    notReferenced
      .sort((a, b) => b.stat.size - a.stat.size)
      .map(f => [
        dv.fileLink(f.path),
        f.parent.path,
        f.stat.size < 1024 ? f.stat.size + " B"
          : f.stat.size < 1048576 ? Math.round(f.stat.size / 1024) + " KB"
          : (f.stat.size / 1048576).toFixed(1) + " MB"
      ])
  );
}
```

---

### Пустые заметки

> [!warning] Заметки размером менее 50 байт (скорее всего пустые)

```dataviewjs
const emptyNotes = dv.pages()
  .where(p => !p.file.path.startsWith(".obsidian/"))
  .where(p => !p.file.path.startsWith("Базы/"))
  .where(p => !p.file.path.startsWith("Шаблоны/"))
  .where(p => p.file.size < 50)
  .sort(p => p.file.size, "asc");

if (emptyNotes.length === 0) {
  dv.paragraph("> [!done] Пустых заметок не найдено");
} else {
  dv.paragraph(`Найдено **${emptyNotes.length}** пустых заметок.`);
  dv.table(
    ["Заметка", "Папка", "Размер"],
    emptyNotes.map(p => [
      p.file.link,
      p.file.folder || "/",
      p.file.size + " B"
    ])
  );
}
```

---

### Заметки без тегов

> [!info] Заметки, у которых нет ни одного тега

```dataviewjs
const untagged = dv.pages()
  .where(p => !p.file.path.startsWith(".obsidian/"))
  .where(p => !p.file.path.startsWith("Базы/"))
  .where(p => !p.file.path.startsWith("Шаблоны/"))
  .where(p => !p.file.path.startsWith("Файлы/"))
  .where(p => !p.file.path.startsWith("Бонус/"))
  .where(p => !p.file.tags || p.file.tags.length === 0)
  .sort(p => p.file.mtime, "desc");

if (untagged.length === 0) {
  dv.paragraph("> [!done] Все заметки имеют теги");
} else {
  dv.paragraph(`Найдено **${untagged.length}** заметок без тегов.`);
  dv.table(
    ["Заметка", "Папка"],
    untagged.map(p => [p.file.link, p.file.folder || "/"])
  );
}
```

---

### Инвентаризация метаданных

> [!info] Все свойства (properties), используемые в хранилище, с количеством файлов

```dataviewjs
const metadataMap = {};

dv.pages()
  .where(p => !p.file.path.startsWith(".obsidian/"))
  .forEach(page => {
    Object.keys(page).forEach(key => {
      if (key === "file") return;
      const normalizedKey = key.toLowerCase().replaceAll(" ", "-");
      if (!metadataMap[normalizedKey]) metadataMap[normalizedKey] = 0;
      metadataMap[normalizedKey]++;
    });
  });

const rows = Object.entries(metadataMap)
  .sort((a, b) => b[1] - a[1])
  .map(([key, count]) => [key, count]);

dv.paragraph(`Найдено **${rows.length}** уникальных свойств.`);
dv.table(["Свойство", "Используется в файлах"], rows);
```
