Введение

Около двух лет я использую Obsidian как основное приложение для ведения заметок, планирования и написания статей. В этой статье я хочу поделиться своим полным workflow: от распределения заметок по папкам и горячих клавиш до детального Cheatsheet по хранилищу и организации метода Zettelkasten.

Сразу уточню: Obsidian становится действительно удобным только тогда, когда человек сам собирает под себя хранилище и находит подходящий ему метод. Ниже я разбираю реализацию по частям, чтобы каждый мог изучить интересующий блок, взять полезные идеи или настроить всё аналогично у себя.

Ниже — краткий план того, о чём будет статья:

  1. Архитектура папок (где и что хранится, а также подробный Cheatsheet.md, который я составлял для себя).

  2. Плагины, которые я использую.

  3. Настройка Homepage и каталога книг.

  4. Синхронизация ПК + телефон (плагин Git + Termux на телефоне, у меня репозиторий в Gitea).

Архитектура

Это исчерпывающее руководство по моей системе заметок и записей (Obsidian и Habitica). Если я что-то забуду — вся информация здесь.

Obsidian

Термины и правила

  1. Файлы-якоря (taglinks) — замена тегов в их привычном понимании. Смысл в том, что это те же теги, но в виде отдельных файлов. Их преимущество — явная наглядность и возможность добавить внутреннюю логику (например, указать прямые ссылки на другие файлы для быстрого поиска или расписать описание самого тега). Имеют вид [[Мой якорь]], где скобки обозначают ссылку на файл.

  2. Индекс (index) — taglink с расширенной внутренней логикой. Как правило, у такого index-файла много связей с другими «полноценными» заметами. В начале имени любого индекса ставится 00, а название пишется с большой буквы. Например, taglink [[физика]] превращается в [[00 Физика]]. Это нужно для того, чтобы с первого взгляда отделить индекс от обычного taglink.

  3. Теги (tags) — классические теги с решёткой (например: #тег). Используются для неявного указания свойств файла. Я указываю тип файла (#plan, #note, #zettel, #book, #index) для быстрого поиска. Если это #zettel-файл, я добавляю теги прогресса #baby, #teen, #adult:

    • #baby — заметка только на стадии зарождения;

    • #teen — заметка в процессе написания;

    • #adult — заметка самодостаточна и всегда полезна (вечнозелёная).

    Важно помнить: даже если заметка уже «выросла», её всё равно можно дополнять. Для этого создаётся новый файл со ссылкой на старый, который раскрывает тему дальше или с другого ракурса.

Типы файлов

  • note — для простых и оперативных записей (которые со временем могут потерять актуальность);

  • plan — для планов, программ и проектов;

  • zettel — для постоянных знаний в Zettelkasten (статусы: #baby, #teen, #adult);

  • book — для конспектов и карточек книг;

  • index — для индексов.

Файловая структура

_files

Здесь хранятся все медиафайлы, используемые в заметках (фотографии, схемы, рисунки и т.д.).

_templates

Здесь хранятся шаблоны для разных типов файлов.

  1. [[Book temp]] — шаблон для книг в папке Books. Состоит из:

    • title (название книги),

    • cover (ссылка на обложку),

    • author (автор),

    • genre (жанр),

    • tags (#book),

    • taglinks (ссылки на файлы-якоря),

    • status (статус: прочитана / читаю / в планах),

    • current_page (текущая страница),

    • total_pages (всего страниц),

    • date_started (дата начала чтения),

    • date_ended (дата окончания чтения),

    • other_internal_links (дополнительные ссылки на связанные файлы; по умолчанию стоит ссылка на каталог книг),

    • other_external_links (дополнительные ссылки на внешние ресурсы).

  2. [[Index temp]] — шаблон для индексов в папке Indexes. Состоит из:

    • created_at (дата и время создания),

    • tags (#index),

    • other_internal_links (дополнительные внутренние ссылки),

    • other_external_links (дополнительные внешние ссылки).

  3. [[Note temp]] — шаблон для заметок в папке Plans & Notes. Состоит из:

    • created_at (дата и время создания),

    • tags (#note),

    • taglinks (ссылки на файлы-якоря),

    • other_internal_links (дополнительные внутренние ссылки),

    • other_external_links (дополнительные внешние ссылки).

  4. [[Plan temp]] — шаблон для планов и программ в папке Plans & Notes. Состоит из:

    • created_at (дата и время создания),

    • tags (#plan),

    • taglinks (ссылки на файлы-якоря),

    • other_internal_links (дополнительные внутренние ссылки),

    • other_external_links (дополнительные внешние ссылки).

  5. [[Zettel temp]] — шаблон для записей в папке Zettelkasten. Состоит из:

    • created_at (дата и время создания),

    • tags (#zettel и #baby, который затем меняется на #teen и #adult по мере готовности),

    • taglinks (ссылки на файлы-якоря),

    • other_internal_links (дополнительные внутренние ссылки),

    • other_external_links (дополнительные внешние ссылки).

Taglinks

Папка для хранения всех файлов-якорей.

Indexes

Хранилище всех индексов (файлов с внутренней логикой). Позволяет быстро находить нужные темы через систему связей.

Plans & Notes

Здесь хранятся все планы, программы (plans) и обычные заметки (notes). Отдельная папка нужна, чтобы не засорять основную базу, так как эти записи не имеют бесконечной ценности.

Zettelkasten

Главная база знаний. Сюда попадают идеи и материалы, содержащие ценные умозаключения, которые пригодятся в будущем.

Главный критерий: «Хотел бы я, чтобы эта информация осталась со мной надолго?». Если ответ «да» — материал ценен и его нужно зафиксировать.

  1. Rough Notes (Черновики):

    • Что это: Папка для «сырых» и только зарождающихся заметок. Все записи в Zettelkasten начинаются отсюда.

    • Что внутри: Заметки с потенциалом, еще не получившие законченную форму. Это «рассада» моей базы знаний.

    • Критерий: Если заметка неполная или требует доработки — она остаётся здесь.

  2. Grown Notes (Выросшие заметки):

    • Что это: Основное хранилище ценных и сформированных заметок.

    • Что внутри: Доработанные записи с законченной мыслью, понятные без стороннего контекста.

    • Критерий: Как только заметка становится самодостаточной, я перемещаю её из Rough Notes в Grown Notes.

Books

Папка для прочитанных книг. Главный файл — Каталог, в котором наглядно отображаются все книги.

  • Создаётся файл с названием книги, применяется и заполняется шаблон. Ниже заголовка можно писать краткие мысли. Если требуется полноценный разбор книги, создаётся отдельный файл, который отправляется в Zettelkasten.

Остальные файлы

  • [[Cheatsheet.md]] — исчерпывающая шпаргалка по системе (этот файл);

  • [[Homepage.md]] — домашняя страница с ключевыми ссылками;

  • [[Inbox.md]] — файл для быстрых заметок и невыполненных за день дел;

  • Bookmarks — встроенные закладки Obsidian для мгновенного доступа к частым файлам.

Основные горячие клавиши

  • Alt + S — вставка шаблона

  • Alt + Q — вставка Callout

  • Ctrl + O — открыть Quick Switcher

  • Ctrl + P — открыть Command Palette

  • Ctrl + E — переключить режим просмотра (Current View)

  • Ctrl + ЛКМ — открыть ссылку в новой вкладке

  • Ctrl + Shift + F — поиск по всем файлам

  • Ctrl + ↑ — переместить строку вверх

  • Ctrl + ↓ — переместить строку вниз

  • Alt + 1 — замена сниппета (Snippet Replacement)

  • Alt + A — свернуть все списки и заголовки

  • Alt + D — развернуть все списки и заголовки

Как правильно записывать информацию?

  1. Найти интересный материал.

  2. Прочитать и разбирать его до полного понимания.

  3. Закрыть источник и записать суть в Obsidian в удобном формате (рисунок, таблица, список, текст).

  4. Задать себе вопросы: «Будет ли польза от этой заметки в будущем?» и «В каком конкретном сценарии я это применю?». Если ответы положительные — оставляем.

  5. Проставить ссылки (taglinks, other_internal_links), теги и проверить связность с другими файлами (если это не #index).

  6. Открыть первоисточник и сверить точность записей, при необходимости скорректировать.

  7. Перечитать заметку и проверить, понятен ли материал. Если есть пробелы — повторить шаг 6, пока заметка не станет на 100% понятной и автономной.

  8. При возникновении сопутствующих вопросов создать новую связанную заметку и пройти алгоритм с 1-го пункта.

Habitica

Привычки (habits)

Регулярные или пассивные действия, которые выполняются в течение дня.

Дела (tasks)

Быстрые разовые задачи с коротким сроком жизни (1–2 дня): загрузить посудомойку, помыть пол и т.д.

Ежедневные дела (recurring tasks)

Обязательные регулярные задачи, формирующие распорядок дня.

Отложенные (deferred)

Задачи на перспективу («когда-нибудь»). Находятся в мобильном приложении (HabitNow / Habitica) для оперативного доступа с телефона. Задачи из Obsidian Inbox касаются именно рабочих процессов за ПК, поэтому их удобнее вести за компьютером.

Плагины, которые я использую

  1. Better Word Count — подсчёт слов и символов.

  2. Commander — кастомизация интерфейса и добавление быстрых команд.

  3. Dataview — агрегация и отображение сложных данных через запросы.

  4. Excalidraw — рукописные схемы и подготовка к экзаменам на графическом планшете.

  5. Git — автоматическая синхронизация с репозиторием.

  6. Homepage — настройка стартовой домашней страницы.

  7. Ink — быстрое создание рукописных заметок.

  8. List Callouts — стильное оформление списков и акцентов.

  9. Templater — расширенная работа с шаблонами и скриптами.

  10. Text Snippets — автозамена текста на заранее заготовленные шаблоны.

Настройка Homepage

Файл домашней страницы (Homepage.md):

---
cssclasses: dashboard
---

<div class="fade-banner">
  <img src="_files/sandpile.jpg" alt="Banner">
  <div class="banner-gradient"></div>
</div>

# ?️ My Dashboard
- ## ? [[3 - Plans & Notes/NetDevSecOps.canvas|NetDevSecOps]]
- ## ? [[5 - Books/00 Каталог.md|Каталог книг]]
- ## ? [[Inbox]]
---
- ### ?️ Тренировки
	- [[Тренировки зал full-body]]
	- [[3 - Plans & Notes/Тренировки лето новые.md|Тренировки лето]]
	- [[Доп. Тренировки (3 в неделю)]]
- ### ? Цели и Планы
	- [[3 - Plans & Notes/Цели на 2026.md|Цели на 2026]]
	- [[3 - Plans & Notes/Еженедельный план 2026.md|Еженедельный план 2026]]
	- [[2 - Indexes/00 Хочу.md|Все что я хочу]]
- ### ? Полезные материалы
	- [[Interview Questions|Вопросы DevOps Advanced]]
	- [[DevOps Questions Basic Level|Вопросы DevOps Basic]]
	- [[Механизм минимальных затрат]]

CSS-сниппет для оформления панели (простой стиль без лишних элементов):

.fade-banner {
    position: relative;
    width: 100%;
    height: 200px;
    overflow: hidden;
    margin-bottom: 25px;
    border-radius: 8px;
}

.fade-banner img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.banner-gradient {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background: linear-gradient(to bottom, transparent 50%, var(--background-primary) 100%);
}

.dashboard {
    font-family: inherit;
    padding-left: 25px !important;
    padding-right: 25px !important;
    padding-top: 20px !important;
}

.dashboard .markdown-preview-section {
    max-width: 100%;
}

.dashboard h1 {
    font-size: 2em;
    font-weight: 600;
    margin-bottom: 20px;
}

.dashboard div>ul>li>b,
.dashboard div>ul>li>strong {
    display: block;
    font-size: 1.2em;
    font-weight: 600;
    margin-bottom: 12px;
    color: var(--text-normal);
}

.dashboard div>ul>li {
    background-color: transparent !important;
    border: none !important;
    box-shadow: none !important;
    padding: 10px !important;
}

.dashboard div>ul {
    list-style: none;
    display: flex;
    column-gap: 40px;
    row-gap: 25px;
    flex-flow: row wrap;
    padding-left: 0px !important;
}

.dashboard div>ul>li {
    min-width: 260px;
    flex: 1;
}

.dashboard div.markdown-preview-section>div>ul>li>.list-bullet {
    display: none !important;
}

.dashboard.markdown-rendered.show-indentation-guide li>ul::before,
.dashboard.markdown-rendered.show-indentation-guide li>ol::before {
    display: none;
}

div.markdown-preview-section>div>ul.has-list-bullet>li {
    padding-left: 0px !important;
}

.dashboard ul.dataview {
    row-gap: 0px !important;
    list-style-type: disc !important;
}

.dashboard-welcome {
    font-size: 1.25em;
    font-weight: 800;
    letter-spacing: 7px;
    text-transform: uppercase;
    color: #FFFFFF !important;

    text-shadow: 0px 2px 4px rgba(255, 255, 255, 0.6),
                 0px 4px 10px rgba(0, 0, 0, 0.4);

    margin-bottom: -15px;
    opacity: 1;
    text-align: center;
    display: block;
}

Настройка каталога книг

Основной файл каталога (работает на dataviewjs):

const bookPages = dv.pages('"5 - Books"').where(p => p.title);
const container = this.container;

const style = document.createElement("style");
style.textContent = `
    .book-table {
        width: 100%;
        border-collapse: collapse;
        font-family: Cascadia Mono, monospace;
        margin-top: 1rem;
        font-size: 15px;
    }
    .book-table th {
        background: rgba(138, 43, 226, 0.15);
        color: #d6bcfa;
        padding: 12px 14px;
        border-bottom: 2px solid rgba(214, 188, 250, 0.3);
        font-weight: 600;
        text-align: left;
    }
    .book-table td {
        padding: 12px 14px;
        border-bottom: 1px solid rgba(214, 188, 250, 0.15);
        color: #e9d8fd;
        vertical-align: middle;
    }
    .book-table tr {
        transition: all 0.2s ease;
    }
    .book-table tr:hover {
        background: rgba(138, 43, 226, 0.12) !important;
    }
    .filter-container {
        background: rgba(138, 43, 226, 0.1);
        backdrop-filter: blur(10px);
        border-radius: 12px;
        padding: 1.2rem;
        margin-bottom: 2rem;
        border: 1px solid rgba(214, 188, 250, 0.2);
        display: flex;
        gap: 1rem;
        align-items: center;
        flex-wrap: wrap;
    }
    .filter-select, .filter-search {
        padding: 10px 14px;
        border-radius: 10px;
        border: 1px solid rgba(214, 188, 250, 0.3);
        background: rgba(26, 26, 32, 0.8);
        color: #e9d8fd;
        font-size: 14px;
        outline: none;
        transition: all 0.2s ease;
        height: 40px;
        box-sizing: border-box;
        font-family: Cascadia Mono, monospace;
    }
    .filter-select:hover, .filter-search:focus {
        border-color: rgba(214, 188, 250, 0.6);
        box-shadow: 0 0 0 4px rgba(138, 43, 226, 0.15);
    }
    .filter-search {
        flex: 1;
        min-width: 250px;
    }
    .book-title-link {
        color: #d6bcfa;
        text-decoration: none;
        font-weight: 600;
        font-size: 16px;
        transition: color 0.2s;
        line-height: 1.3;
    }
    .book-title-link:hover {
        color: #e9d8fd;
    }
    .book-meta-text {
        font-size: 12px;
        color: rgba(214, 188, 250, 0.7);
        margin-top: 3px;
    }
    .status-dropdown {
        padding: 4px 10px;
        border-radius: 14px;
        font-size: 13px;
        font-weight: 600;
        border: 1px solid transparent;
        outline: none;
        cursor: pointer;
        transition: all 0.2s;
        width: 130px;
        text-align: center;
        height: 36px;
        box-sizing: border-box;
        font-family: Cascadia Mono, monospace;
    }
    .page-num-input {
        width: 55px;
        background: rgba(138, 43, 226, 0.15);
        border: 1px solid rgba(214, 188, 250, 0.3);
        color: #e9d8fd;
        border-radius: 6px;
        padding: 4px 6px;
        text-align: center;
        font-size: 13px;
        font-family: Cascadia Mono, monospace;
        outline: none;
        transition: all 0.2s;
        box-sizing: border-box;
    }
    .page-num-input:focus {
        border-color: rgba(214, 188, 250, 0.6);
        background: rgba(26, 26, 32, 0.9);
        box-shadow: 0 0 0 3px rgba(138, 43, 226, 0.2);
    }
    .page-num-input::-webkit-outer-spin-button,
    .page-num-input::-webkit-inner-spin-button {
        -webkit-appearance: none;
        margin: 0;
    }
    .page-num-input {
        -moz-appearance: textfield;
    }
    .progress-bar-container {
        background: rgba(214, 188, 250, 0.15);
        border-radius: 12px;
        height: 6px;
        width: 100%;
        overflow: hidden;
    }
    .progress-bar-fill {
        background: linear-gradient(90deg, #8A2BE2, #9370DB);
        height: 100%;
        transition: width 0.3s ease;
    }
    .book-cover-img {
        width: 75px;
        height: 108px;
        border-radius: 8px;
        object-fit: cover;
        box-shadow: 0 6px 14px rgba(0,0,0,0.4);
        border: 1px solid rgba(214, 188, 250, 0.2);
        transition: transform 0.2s;
        cursor: pointer;
    }
    .book-cover-img:hover {
        transform: scale(1.05);
    }
    .total-page-label {
        font-size: 12px;
        color: rgba(214, 188, 250, 0.8);
        white-space: nowrap;
    }

    /* Мобильная оптимизация */
    @media (max-width: 600px) {
        .book-table th, .book-table td {
            padding: 8px 6px;
            font-size: 13px;
        }
        .book-cover-img {
            width: 50px;
            height: 72px;
        }
        .book-title-link {
            font-size: 14px;
        }
        .book-meta-text {
            font-size: 11px;
        }
        .status-dropdown {
            width: 105px;
            font-size: 12px;
            height: 32px;
            padding: 2px 4px;
        }
        .page-num-input {
            width: 45px;
            font-size: 12px;
            padding: 3px 2px;
        }
        .total-page-label {
            font-size: 11px;
        }
        .filter-container {
            padding: 0.8rem;
            flex-direction: column;
            align-items: stretch;
            gap: 0.6rem;
        }
        .filter-select, .filter-search {
            width: 100%;
        }
    }
`;
document.head.appendChild(style);

const filterContainer = document.createElement("div");
filterContainer.className = "filter-container";

const genreSelect = document.createElement("select");
genreSelect.className = "filter-select";
const allOption = document.createElement("option");
allOption.textContent = "Все жанры";
allOption.value = "";
genreSelect.appendChild(allOption);

const allGenres = new Set();
for (let p of bookPages) {
    if (p.genre) {
        const genres = Array.isArray(p.genre) ? p.genre : [p.genre];
        genres.forEach(g => g && allGenres.add(g));
    }
}
for (let genre of Array.from(allGenres).sort()) {
    const opt = document.createElement("option");
    opt.textContent = genre;
    opt.value = genre;
    opt.style.background = "#1a1a20";
    genreSelect.appendChild(opt);
}
filterContainer.appendChild(genreSelect);

const statusSelect = document.createElement("select");
statusSelect.className = "filter-select";
const statusOptions = [
    { value: "", text: "Все статусы" },
    { value: "reading", text: "Читаю" },
    { value: "completed", text: "Прочитано" },
    { value: "planned", text: "В планах" }
];
statusOptions.forEach(o => {
    const opt = document.createElement("option");
    opt.textContent = o.text;
    opt.value = o.value;
    opt.style.background = "#1a1a20";
    statusSelect.appendChild(opt);
});
filterContainer.appendChild(statusSelect);

const searchInput = document.createElement("input");
searchInput.type = "text";
searchInput.className = "filter-search";
searchInput.placeholder = "? Поиск по названию или автору...";
filterContainer.appendChild(searchInput);

container.appendChild(filterContainer);

function determineBookStatus(page) {
    if (page.status) return page.status;
    const today = new Date().toISOString().split('T')[0];
    if (page.date_ended && page.date_ended <= today) return "completed";
    if (page.date_started) return "reading";
    const total = page.total_pages || 100;
    const current = page.current_page || 0;
    if (total > 0 && current >= total) return "completed";
    if (page.current_page && page.current_page > 0) return "reading";
    return "planned";
}

function applyStatusStyle(selectElement, status) {
    if (status === "completed") {
        selectElement.style.background = "rgba(102, 221, 170, 0.15)";
        selectElement.style.color = "#66ddaa";
        selectElement.style.borderColor = "rgba(102, 221, 170, 0.25)";
    } else if (status === "reading") {
        selectElement.style.background = "rgba(147, 112, 219, 0.15)";
        selectElement.style.color = "#9370db";
        selectElement.style.borderColor = "rgba(147, 112, 219, 0.25)";
    } else {
        selectElement.style.background = "rgba(214, 188, 250, 0.15)";
        selectElement.style.color = "#d6bcfa";
        selectElement.style.borderColor = "rgba(214, 188, 250, 0.25)";
    }
}

async function updatePageProperty(page, property, value) {
    try {
        const file = app.vault.getAbstractFileByPath(page.file.path);
        if (!file) return false;
        await app.fileManager.processFrontMatter(file, (fm) => {
            if (value === undefined || value === "") { delete fm[property]; } 
            else { fm[property] = value; }
        });
        page[property] = value;
        return true;
    } catch (e) { console.error(e); return false; }
}

async function removePageProperty(page, property) {
    try {
        const file = app.vault.getAbstractFileByPath(page.file.path);
        if (!file) return false;
        await app.fileManager.processFrontMatter(file, (fm) => { delete fm[property]; });
        delete page[property];
        return true;
    } catch (e) { console.error(e); return false; }
}

function renderTable(selectedGenre = "", selectedStatus = "", searchTerm = "") {
    const oldTable = container.querySelector(".book-table");
    if (oldTable) oldTable.remove();

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

    const header = table.insertRow();
    ["Обложка", "Книга", "Статус", "Прогресс"].forEach(t => {
        const th = document.createElement("th");
        th.textContent = t;
        header.appendChild(th);
    });

    for (let p of bookPages) {
        const genreMatch = !selectedGenre || (p.genre && ((Array.isArray(p.genre) && p.genre.includes(selectedGenre)) || p.genre === selectedGenre));
        const actualStatus = determineBookStatus(p);
        const statusMatch = !selectedStatus || actualStatus === selectedStatus;
        const searchMatch = !searchTerm || (p.title && p.title.toLowerCase().includes(searchTerm.toLowerCase())) || (p.author && p.author.toLowerCase().includes(searchTerm.toLowerCase()));

        if (!(genreMatch && statusMatch && searchMatch)) continue;

        let total = p.total_pages || 100;
        let current = p.current_page || 0;
        let percent = total > 0 ? Math.round((current / total) * 100) : 0;

        const row = table.insertRow();
        row.onclick = (e) => {
            if (!e.target.closest('a') && !e.target.closest('select') && !e.target.closest('input') && !e.target.closest('img')) {
                window.location.href = p.file.path;
            }
        };

        const cellCover = row.insertCell();
        if (p.cover) {
            const img = document.createElement("img");
            img.src = p.cover;
            img.className = "book-cover-img";
            img.onclick = () => window.location.href = p.file.path;
            cellCover.appendChild(img);
        } else {
            const placeholder = document.createElement("div");
            placeholder.className = "book-cover-img";
            placeholder.style.background = "rgba(138, 43, 226, 0.12)";
            placeholder.style.display = "flex";
            placeholder.style.alignItems = "center";
            placeholder.style.justifyContent = "center";
            placeholder.style.fontSize = "22px";
            placeholder.style.border = "1px dashed rgba(214, 188, 250, 0.2)";
            placeholder.innerHTML = "?";
            placeholder.onclick = () => window.location.href = p.file.path;
            cellCover.appendChild(placeholder);
        }

        const cellInfo = row.insertCell();
        const infoWrapper = document.createElement("div");
        infoWrapper.style.display = "flex";
        infoWrapper.style.flexDirection = "column";

        const link = document.createElement("a");
        link.href = p.file.path;
        link.textContent = p.title || p.file.name;
        link.className = "internal-link book-title-link";

        const metaText = document.createElement("div");
        metaText.className = "book-meta-text";
        const author = p.author || "Автор не указан";
        const genre = Array.isArray(p.genre) ? p.genre.join(", ") : (p.genre || "");
        metaText.textContent = author + (genre ? ` • ${genre}` : "");

        infoWrapper.appendChild(link);
        infoWrapper.appendChild(metaText);
        cellInfo.appendChild(infoWrapper);

        const cellStatus = row.insertCell();
        const sel = document.createElement("select");
        sel.className = "status-dropdown";
        
        const opts = [
            { value: "planned", text: "? В планах" },
            { value: "reading", text: "? Читаю" },
            { value: "completed", text: "✓ Готово" }
        ];
        opts.forEach(o => {
            const opt = document.createElement("option");
            opt.value = o.value;
            opt.textContent = o.text;
            opt.style.background = "#1a1a20";
            if (o.value === actualStatus) opt.selected = true;
            sel.appendChild(opt);
        });
        applyStatusStyle(sel, actualStatus);
        cellStatus.appendChild(sel);

        sel.onchange = async () => {
            const newStatus = sel.value;
            applyStatusStyle(sel, newStatus);
            if (newStatus !== actualStatus) {
                let success = await updatePageProperty(p, "status", newStatus);
                if (success) {
                    if (actualStatus === "completed" && newStatus !== "completed") {
                        const newCurrent = Math.max(0, Math.floor(total * 0.9));
                        await updatePageProperty(p, "current_page", newCurrent);
                        await removePageProperty(p, "date_ended");
                    } else if (newStatus === "completed") {
                        await updatePageProperty(p, "current_page", total);
                        await updatePageProperty(p, "date_ended", new Date().toISOString().split('T')[0]);
                    }
                    renderTable(genreSelect.value, statusSelect.value, searchInput.value);
                }
            }
        };

        const cellProgress = row.insertCell();
        const progressWrapper = document.createElement("div");
        progressWrapper.style.display = "flex";
        progressWrapper.style.flexDirection = "column";
        progressWrapper.style.gap = "6px";
        progressWrapper.style.minWidth = "120px";

        const barContainer = document.createElement("div");
        barContainer.className = "progress-bar-container";
        const barFill = document.createElement("div");
        barFill.className = "progress-bar-fill";
        barFill.style.width = `${percent}%`;
        barContainer.appendChild(barFill);
        progressWrapper.appendChild(barContainer);

        const inputRow = document.createElement("div");
        inputRow.style.display = "flex";
        inputRow.style.alignItems = "center";
        inputRow.style.gap = "4px";
        inputRow.style.justifyContent = "flex-end";

        const numInput = document.createElement("input");
        numInput.type = "number";
        numInput.className = "page-num-input";
        numInput.value = current;
        numInput.min = 0;
        numInput.max = total;

        const label = document.createElement("span");
        label.className = "total-page-label";
        label.textContent = `/${total} (${percent}%)`;

        inputRow.appendChild(numInput);
        inputRow.appendChild(label);
        progressWrapper.appendChild(inputRow);
        cellProgress.appendChild(progressWrapper);

        const saveProgressValue = async () => {
            let val = parseInt(numInput.value);
            if (isNaN(val)) val = current;
            val = Math.max(0, Math.min(val, total));

            if (val !== current) {
                let success = await updatePageProperty(p, "current_page", val);
                if (success) {
                    if (val >= total) {
                        await updatePageProperty(p, "status", "completed");
                        await updatePageProperty(p, "date_ended", new Date().toISOString().split('T')[0]);
                    } else if (actualStatus === "completed" && val < total) {
                        await updatePageProperty(p, "status", "reading");
                        await removePageProperty(p, "date_ended");
                    } else if (actualStatus === "planned" && val > 0) {
                        await updatePageProperty(p, "status", "reading");
                        await updatePageProperty(p, "date_started", new Date().toISOString().split('T')[0]);
                    }
                    renderTable(genreSelect.value, statusSelect.value, searchInput.value);
                }
            } else {
                numInput.value = current;
            }
        };

        numInput.onblur = saveProgressValue;
        numInput.onkeydown = (ev) => {
            if (ev.key === "Enter") numInput.blur();
            if (ev.key === "Escape") { numInput.value = current; numInput.blur(); }
        };
    }

    container.appendChild(table);
}

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

renderTable();

Синхронизация ПК и смартфона (Git + Termux + Gitea)

Для независимой синхронизации без сторонних платных сервисов я использую связку Git, собственный инстанс Gitea и Termux на Android:

  1. На ПК: В самом Obsidian настроен плагин Obsidian Git. Он автоматически коммитит и отправляет (push) изменения в мой закрытый репозиторий на Gitea при закрытии программы или каждые 10–15 минут.

  2. На телефоне:

    • Настроен Termux с установленным пакетом git и SSH-ключами для доступа к Gitea.

    • Синхронизацию я запускаю через комманду (alias) gsync в ~/.zshrc (или ~/.bashrc), которая делает сначала pull, потом push репозитория. Да, приходится заходить в терминал и писать gsync, но меня пока что устраивает (alias gsync="cd ~/storage/shared/Obsidian/Vault && git pull origin main && git add . && git commit -m 'mobile auto-sync' && git push origin main")

    • Также уточню, что git-crypt я не использую, так как репозиторий находится в моем self-hosted Gitea, данные никуда не утекают.

  3. Почему именно Gitea: Полный контроль над данными, отсутствие ограничений на размер файлов и бесплатный приватный сервер.

Обязательно стоит добавить .gitignore: (чтобы не было конфликтов веток)

.obsidian/workspace.json
.obsidian/workspace-mobile.json
.obsidian/cache
.trash/

Также вот неплохая статья с гайдом по настройке (подход немного отличается от моего)

Заключение

Эта система складывалась постепенно и продолжает эволюционировать. Главный вывод за два года использования — хранилище не должно быть идеальным с первого дня. Оно должно помогать думать и работать, а не забирать всё время на свою настройку.

Надеюсь, мой workflow подаст вам пару интересных идей для организации собственного цифрового сада в Obsidian. Если у вас есть вопросы по работе со скриптами или структурой — пишите в комментариях!

Постараюсь ответить на все вопросы. Со мной также можно связаться в телеграм (в хабр профиле), там я точно вам отвечу.

Комментарии (33)


  1. opium
    26.07.2026 14:27

    В этом плане полностью перешёл на ИИ агента и ношн с обсидианом сейчас кажется каменным веком как когда то был евернот


    1. ITsuperiorRF Автор
      26.07.2026 14:27

      Честно говоря, я не очень понимаю как ИИ агент может заменить полноценные заметки. Главный смысл заметок в том, что ты их пишешь сам, тем самы закрепляя материал, а не просто складываешь, добавляя ссылки. (иначе проще просто загуглить).

      Если я что-то и добавляю в заметки, то это не какие-то сухие факты по типу набора терминов и подобное, а какой-то мыслительный/творческий процесс написания, который невозможно воссоздать иначе, кроме как самостоятельно.


      1. opium
        26.07.2026 14:27

        Вообще никто мне кажется так ну пишет, все пишут просто заметки и забывают про них, я очень много видел заметок у знакомых и работников своих это всегда ад и Израиль, я вел заметки очень структурировано, но это требует так много усилий и времени. Сейчас я веду их как попало без напряга и на выходе получаю результат в разы лучше на выходе


    1. ibr06
      26.07.2026 14:27

      Можно подробнее?


      1. opium
        26.07.2026 14:27

        ну просто ведешь в агенте , он просто ведет все красиво и правильно и с нужными контекстами в гите , если прямо очень много всего можно векторный поиск прикрутить но с миллионым контекстом я везде убрал векторный поиск и раг за ненадобностью


        1. Mixael_sas
          26.07.2026 14:27

          А как агент сращивается с Obsidian?


          1. opium
            26.07.2026 14:27

            никак обсидиан ношн и все подобные софты не нужны как прослойки , они не добавляют никакого профита в этой схеме , агент сам все прекрасно ведет


            1. Mixael_sas
              26.07.2026 14:27

              Т.е. просто ведешь заметки в нужном тебе агенте и он всё помнит? А как же переполнение контекста и галлюцинации? Нужны же точные данные, которые записал сам


              1. opium
                26.07.2026 14:27

                Контекст уже очень давно миллион, его можно переполнить ну и компакт или клевер поможет

                Галлюцинации на простых задачах вроде заметок ну наверно не совру 0. Так как пользуюсь уже давно и ни разу не встретил как было раньше


    1. Proidoha
      26.07.2026 14:27

      Это ортогональные вещи. Можно агента использовать вместе с obsidian, и он же может делать и заметки в том числе, создавая свою базу знаний.


      1. opium
        26.07.2026 14:27

        а зачем лишняя тяжелая прослойка в виде обсидиана , какую она несет функциональность то ? обсидиан и ношн и прочие такие системы в такой реализации не нужны


        1. Starmaster1
          26.07.2026 14:27

          Не очень то тяжёлая прослойка, всего лишь текстовые файлы. Сам обсидиан агенту не нужен. Обсидиан нужен человеку, чтобы читать заметки самостоятельно


          1. opium
            26.07.2026 14:27

            не понял зачем мне для чтения заметок нужен обсидиан, это просто мд файлы , я могу их читать супер быстро и удобно и с компа и с мобилы без всяких лишних софтов , удобно навигировать по ним сам и искать иишкой то что нужно


            1. Starmaster1
              26.07.2026 14:27

              Без софтов никак не получится в любом случае. Хоть блокнот, хоть vim, хоть nano, хоть что. Но чтобы было удобно, функционально и красиво, нужен Obsidian. А так, да - есть масса вариантов для любого стиля работы. Как хорошо, что можно выбрать то, что по душе.


              1. opium
                26.07.2026 14:27

                ничего не понял , это же просто гит и мд , в браузере просто смотрю, оно супер функционально и красиво всегда , с офигенной навигацией , отсылками и пасхалками


                1. levkib
                  26.07.2026 14:27

                  А если надо посмотреть заметки где-то за пределами стационарных сетей, а мобильный интернет ограничен? Как вы выходите из ситуации?


                  1. opium
                    26.07.2026 14:27

                    Пока не было таких проблем ну а так гит что на телефоне что на компе у меня есть, ну и если надо могу копию заметок держать


    1. Starmaster1
      26.07.2026 14:27

      Лично я совмещаю оба подхода. У меня Openclaw с wiki-memory в Obsidian и loseless-claw долговременной памятью. В Obsidian же что-то типа PARA workflow настроено со связями. Могу и сам писать/читать заметки и задачи, и агенты в ходе работы разные артефакты туда пишут в свои разделы и пользуются общей базой знаний.


  1. CEOCTO
    26.07.2026 14:27

    Всегда интересно смотреть такие статьи через пару лет использования. Но почти у всех замечаю одну закономерность: чем больше появляется собственных правил, шаблонов и соглашений, тем выше порог входа для самого себя через год. Иногда проще написать обычную markdown-заметку, чем решить, это note, zettel или уже index.


    1. ITsuperiorRF Автор
      26.07.2026 14:27

      Я отчасти согласен, так как 90% времени я пишу в основном планы и заметки, zettelkasten заметки добавляются редко, когда я хочу изучить определенную тему.

      Тут важно понимать, что для человека, который видит конкретный подход в первый раз, система может показаться замудренной, однако на самом деле все сводится к простой вставке шаблона в заметку и заполнением properties за 2 минуты, не более. Остальное либо делается автоматически, либо делается очень редко.

      Главный смысл в том - что это система, которую проектировал я сам, поэтому она мне интуитивно понятна, и мне не нужно ничего запоминать ради ее реализации. И я лично настаиваю на том, чтобы каждый человек делал систему такой, какой ему нужно, не придумывая лишнего без смысла.

      Я лишь хотел привести свой подход, чтобы кто-то мог взять из этого какую-то малую часть, и добавить в свой процесс, если ему это нужно.


    1. DrewUnknown
      26.07.2026 14:27

      данный аргумент не совсем валидный.

      Человек сам под себя проектирует систему, он в ней проводит много времени, поэтому это всё уже запоминается на мышечной памяти. То что человек забудет свою систему справедливо, когда он обсидианом пользуется раз в 100500 лет.

      + чтобы не забыть свою систему, все правила так же пишутся\описываются в соответствующей заметки.


  1. bashka-pro
    26.07.2026 14:27

    Какой то рерайт, у вас уважаемый. syncthing вот это мошь, синхронизирует компы, мобилы почти без инета. И это я еще не пишу про слак


    1. ITsuperiorRF Автор
      26.07.2026 14:27

      Я раньше пользовался syncthing, однако мне было важно версионирование, поэтому git меня больше всего привлекает


      1. mih-kopylov
        26.07.2026 14:27

        А как насчёт приложения GitSync? Делает то же самое, по расписанию, без консоли на телефоне.


  1. RulenBagdasis
    26.07.2026 14:27

    А у меня просто иерархия папок по теме и обычные md файлы безо всяяких шаблонов. Из плагинов только HTML Reader, который показывает в обсидиане веб-страницы, сохранённые через SingleFile. Картинки всегда в папке с заметкой хранятся. Это меня полностью устраивает, доволен, как слон.

    Зачем вся эта машинерия? Пост начинается сразу с технических подробностей без объяснения, а какую, собственно, проблему решали.


    1. ITsuperiorRF Автор
      26.07.2026 14:27

      Здесь не решается какая-то проблема, я просто рассказывал свой подход которым я пользуюсь. Главный смысл статьи был в том, чтобы каждый мог для себя взять какую-то часть, а не копировать все полностью. Даже больше скажу - конкретно мой подход один в один подойдет только мне и никому более.


      1. RulenBagdasis
        26.07.2026 14:27

        Так зачем вы используете именно такой подход? Это же самое интересное.


        1. ITsuperiorRF Автор
          26.07.2026 14:27

          Я могу частично согласиться с претензией, так как я действительно не писал что и зачем конкретно вводилось, но тут скорее дело в том, что если человеку не понятно зачем вводилось то или иное, значит, вероятно, оно ему не нужно.

          Здесь дело не в решении, а в удобстве, так как в действительности информацию можно держать просто в txt в папке, а сам obsidian и надстройки поверх него - лишь "сахар".

          Если есть какие-то конкретные моменты, которые нужно объяснить, то спрашивайте, я отвечу.


  1. alexanderniki
    26.07.2026 14:27

    Надеюсь, мой workflow подаст вам пару интересных идей

    Я пролистал статью несколько раз и у меня вопрос: так а workflow-то, собссно, где? Структура папок и ваш custom css - это не workflow :)


    1. ITsuperiorRF Автор
      26.07.2026 14:27

      Workflow переводится как рабочий процесс.

      В статье я писал про структуру своего Obsidian с описаниями как и что я делаю, данные действия как раз и составляют workflow, т.е как выглядит мой рабочий процесс в obsidian.

      Также я затрагивал не только css и структуру папок, как вы пишете, а и то, как я создаю заметки и ими управляю, что и является самой главной частью.


  1. Deric
    26.07.2026 14:27

    Единственная проблема Обсидиана - это отсутствие возможности быстро делать заметки. Давно использую его для хранения информации - но из-за того что на Андроидет Обсидиан открывается ооочень долго рабочий процесс чаще всего выглядит как: добавить быструю заметку в Google Keep - вспомнить что добавил заметку - добавить заметку в Obsidian. Само собой второй этап частенько продалбыввается из-за чего воскресное ретро частенько затягивается потому что надо перенести заметки из Keep в Обсидиан. Очень не хватает виджета быстрых заметок или быстрого доступа к ежеденевной заметке.


    1. ITsuperiorRF Автор
      26.07.2026 14:27

      Тут я согласен, на телефоне obsidian загружается довольно долго. Я частично решил это с помощью добавления отдельной папки с плагинами и настройками для андроид (вместо .obsidian использую .obsidian-mobile). В нем убраны почти все плагины, кроме самых нужных. Это немного ускоряет загрузку.