TimelixTimelix
Блочная система

Обзор блочной системы

Как устроена универсальная система контента в Timelix

Блок — базовая единица контента в Timelix. По концепции близко к Notion: каждый блок имеет тип, данные и может содержать дочерние блоки, образуя дерево.

Основные принципы

Иерархическая структура

Каждый блок имеет parentId. Единственное исключение — корневой блок (parentId: null), который создаётся автоматически при первом входе пользователя. У каждого пользователя ровно один корневой блок.

Три режима отображения

Один и тот же набор дочерних блоков можно отображать в трёх режимах: list, flow, grid. Режимы не исключают друг друга — система запоминает настройки для каждого и переключает без потерь.

Оптимистичные обновления

Все изменения применяются мгновенно в UI через React Query optimistic updates. Если запрос к серверу упал — UI откатывается автоматически.

Автосинхронизация

Данные синхронизируются с сервером через Server Actions в фоне, без явных refresh.

Архитектурные слои

UI-компоненты (ui/)
  ↑ пропсы и колбэки
Хуки (hooks/)          ← вся бизнес-логика здесь
  ↑ вызовы
Server Actions (api/)
  ↑
PostgreSQL (Drizzle ORM)

Вся бизнес-логика инкапсулирована в хуках. Компоненты только рендерят и вызывают функции из хуков — никакой логики создания, синхронизации или состояния внутри компонентов нет.

Схема базы данных

CREATE TABLE "block" (
    "id"         uuid      DEFAULT gen_random_uuid() PRIMARY KEY,
    "user_id"    text      NOT NULL,
    "parent_id"  uuid,                        -- null только у корневого блока
    "type"       text      NOT NULL,           -- 'text' | 'todo' | 'container' | ...
    "title"      text,
    "content"    jsonb     DEFAULT '{}',       -- данные конкретного типа блока
    "layout_data" jsonb    DEFAULT '{}',       -- позиции дочерних блоков
    "archived"   boolean   DEFAULT false,
    "tags"       jsonb     DEFAULT '[]',
    "created_at" timestamptz DEFAULT now(),
    "updated_at" timestamptz DEFAULT now(),
    "deleted_at" timestamptz
);

Индексы: block_parent_idx, block_type_idx, block_user_idx.

Ключевые хуки

ХукНазначение
useBlocksPageКомпозитный хук для страниц с полным набором возможностей
useRootBlockУправление корневым блоком пользователя
useGridEditModeDrag & drop редактирование в Grid-режиме
useViewSettingsСохранение настроек отображения между сессиями
useBreadcrumbsНавигационные хлебные крошки по иерархии
useInlineEditРедактирование блока на месте без модалки

Пример использования

// На странице — только один хук, вся логика внутри него
const MyBlockList = ({ parentId }: { parentId: string }) => {
  const blocks = useBlocksPage();
 
  return (
    <div>
      {blocks.blocks.map((block) => (
        <div key={block.id} onClick={() => blocks.handleBlockOpen(block.id)}>
          {block.title}
        </div>
      ))}
      <button onClick={() => blocks.handleCreateBlock({ type: 'text', parentId })}>
        Добавить блок
      </button>
    </div>
  );
};

Server Actions

Все CRUD-операции находятся в entities/block/api/blockActions.ts:

ActionОписание
createBlockСоздание нового блока
updateBlockОбновление данных блока
deleteBlockУдаление блока (мягкое, через deleted_at)
getBlocksСписок дочерних блоков по parentId
getBlockОдин блок по id

Дальше

On this page