Code to insert:

```dataviewjs
const pages = dv.pages('"3. Resourses"').where(p => p.страница && p["всего страниц"]);
const container = this.container;

// === UI: Filter by categories ===
const filterWrapper = document.createElement("div");
filterWrapper.style.display = "flex";
filterWrapper.style.gap = "1rem";
filterWrapper.style.marginBottom = "1rem";

// Categories (tags)
const select = document.createElement("select");
const allOption = document.createElement("option");
allOption.textContent = "All categories";
allOption.value = "";
select.appendChild(allOption);

const allTags = new Set();
for (let p of pages) {
  const tags = Array.isArray(p.tags) ? p.tags : [p.tags];
  tags.forEach(t => t && allTags.add(t));
}
for (let tag of Array.from(allTags).sort()) {
  const opt = document.createElement("option");
  opt.textContent = tag;
  opt.value = tag;
  select.appendChild(opt);
}
filterWrapper.appendChild(select);

// === UI: Search field ===
const searchInput = document.createElement("input");
searchInput.type = "text";
searchInput.placeholder = "🔍 Search for a book...";
searchInput.style.flex = "1";
filterWrapper.appendChild(searchInput);

// === Rendering the table ===
container.appendChild(filterWrapper);

function renderTable(selectedTag = "", searchTerm = "") {
  const oldTable = container.querySelector("table");
  if (oldTable) oldTable.remove();

  const table = document.createElement("table");
  table.className = "dataview table-view-table";

  const header = table.insertRow();
  ["📕 Cover", "📘 Book", "✍️ Author", "📚 Category", "📖 Read", "📈 Progress bar"].forEach(title => {
    const th = document.createElement("th");
    th.textContent = title;
    header.appendChild(th);
  });

  for (let p of pages) {
    const tagMatch = !selectedTag || (Array.isArray(p.tags) ? p.tags.includes(selectedTag) : p.tags === selectedTag);
    const searchMatch = !searchTerm || p.file.name.toLowerCase().includes(searchTerm.toLowerCase());

    if (!(tagMatch && searchMatch)) continue;

    const current = Number(p.страница);
    const total = Number(p["всего страниц"]);
    const percent = Math.round((current / total) * 100);
    const color = percent === 100 ? 'green' : 'orange';

    const row = table.insertRow();

    const cellCover = row.insertCell();
    cellCover.innerHTML = p.cover ? `<img src="${p.cover}" width="100" style="border-radius: 8px;">` : "—";

    const cellTitle = row.insertCell();
    const link = document.createElement("a");
    link.href = p.file.path;
    link.textContent = p.file.name;
    link.className = "internal-link";
    cellTitle.appendChild(link);

    const cellAuthor = row.insertCell();
    cellAuthor.textContent = p.Автор || "–";

    const cellTags = row.insertCell();
    cellTags.textContent = Array.isArray(p.tags) ? p.tags.join(", ") : (p.tags || "–");

    const cellProgressText = row.insertCell();
    cellProgressText.textContent = `${current} / ${total}`;

    const cellBar = row.insertCell();
    const progressBarContainer = document.createElement("div");
    progressBarContainer.style.background = "#eee";
    progressBarContainer.style.borderRadius = "6px";
    progressBarContainer.style.height = "16px";
    progressBarContainer.style.width = "100%";
    const progressBar = document.createElement("div");
    progressBar.style.background = color;
    progressBar.style.height = "100%";
    progressBar.style.borderRadius = "6px";
    progressBar.style.width = `${percent}%`;
    progressBarContainer.appendChild(progressBar);
    const percentText = document.createElement("div");
    percentText.style.fontSize = "12px";
    percentText.style.textAlign = "right";
    percentText.textContent = `${percent}%`;
    cellBar.appendChild(progressBarContainer);
    cellBar.appendChild(percentText);
  }

  container.appendChild(table);
}

// === Listeners ===
select.onchange = () => renderTable(select.value, searchInput.value);
searchInput.oninput = () => renderTable(select.value, searchInput.value);

// Initialisation
renderTable();```

from “const pages = dv.pages(‘“3. Resourses”’)” — replace with your own path where your notes with YAML are stored Move the last three apostrophe characters to a new line


🧠 An explanation for those who want to understand this code more deeply:

🔍 1. Loading the books from the folder

const pages = dv.pages('"3. Resourses"')
  .where(p => p.страница && p["всего страниц"]);

Here we:

  • Take all the notes from the "3. Resourses" folder (replace the path for yourself).

  • Filter only those that have the fields страница (the current page) and всего страниц (total pages).

📌 That is, you track how much you’ve read of each book — right in the YAML.


const filterWrapper = document.createElement("div");
...

Here two controls are created:

  • a dropdown list with categories (by tags);

  • a search field by file name (the note name = the book title).

📌 Everything is done by hand via document.createElement, so the element is embedded right into the note.


🧠 3. Gathering the unique categories

for (let p of pages) {
  const tags = Array.isArray(p.tags) ? p.tags : [p.tags];
  tags.forEach(t => t && allTags.add(t));
}

We go through all the books and gather the unique tags (categories) into a list.


📋 4. Drawing the books table

function renderTable(selectedTag = "", searchTerm = "") {

The renderTable function:

  • filters the books by category and search;

  • creates a table with covers, authors, progress;

  • recreates the table on each new filter/search.

📌 It’s exactly this function that makes your table live and updatable.


🖼️ 5. What’s displayed in a table row?

Each row has 6 cells:

ColumnWhat it shows
📕 CoverThe image, if cover: is specified in the YAML
📘 BookThe book title — as a link to the note
✍️ AuthorThe Автор: field from the YAML
📚 CategoryThe book’s tags
📖 ReadThe format 23 / 200
📈 ProgressA visual progress bar

🌡️ The progress bar in action

const percent = Math.round((current / total) * 100);
const color = percent === 100 ? 'green' : 'orange';
  • The % read is calculated;

  • If you’ve read to the end — green;

  • If you’re still reading — orange.

A visual indicator right in Obsidian — like in the usual trackers.


🎮 6. Adding “life”

select.onchange = () => renderTable(select.value, searchInput.value);
searchInput.oninput = () => renderTable(select.value, searchInput.value);

When the user changes the category or types a query — the table is redrawn taking the filters into account.


🧩 What you need to run it

  • A folder with notes about books;

  • Each note should have YAML with these fields:

cover: https://link.to/cover.jpg
Автор: Author Name
tags: [Productivity, Philosophy]
страница: 80
всего страниц: 200

💥 What you get

  • A catalogue of books with filters and search;

  • A nice visual — covers, progress, categories;

  • It works right in Obsidian, without external services.