Идея «опубликовано, но скрыто» кажется удобной ровно до момента, когда надо объяснить, кто и где это видит. Мы отказались. Осталось два состояния: опубликовано и нет.
Как появился unlisted
Сначала статусов было больше. Казалось, что статья может быть:
- черновиком;
- на проверке;
- опубликованной;
- отклонённой;
- скрытой;
- «опубликованной, но не в списке».
Последнее — аналог unlisted на YouTube. Статья доступна по прямой ссылке, но её нет в списке и в sitemap. Удобно? На бумаге — да. Можно показать текст человеку, не анонсируя его.
На практике это создало вопросы:
- Чем «скрытая» отличается от «черновика»?
- Если статья доступна по ссылке, она опубликована или нет?
- Почему её нет в списке, но Google её видит?
- Что делать, если ссылка утекла?
- Как объяснить это автору, который видит статус «опубликовано», но не находит текст на сайте?
Чем больше я отвечал на эти вопросы, тем яснее становилось: это не гибкость, а неопределённость.
Два публичных состояния
В итоге публично есть только два состояния:
- Опубликовано — статья видна в
/articles/, в sitemap, по прямой ссылке. Всё. - Не опубликовано — статья не видна никому, кроме автора, админа и того, у кого есть ссылка предпросмотра.
Внутри «не опубликовано» есть рабочие статусы:
draft— черновик;pending— на проверке;rejected— отклонена с причиной.
Но это внутренние состояния. Для читателя их нет. Для поисковика их нет. Для сайта есть только «есть статья» или «нет статьи».
Кнопка «Снять с публикации» возвращает статью в draft. Не в «скрытую», не в «unlisted». В черновик. Если нужно снова показать — публикуешь заново.
Почему это лучше
Меньше кода
Каждое состояние — это ветка в шаблоне, проверка в экспорте, фильтр в sitemap, объяснение в интерфейсе. Чем меньше состояний, тем меньше мест, где можно забыть проверку.
Когда убрали unlisted, стало проще:
- экспорт статей: только
published; - sitemap: только
published; - страница статьи:
draft/pending/rejected— 404; - предпросмотр: отдельный токен, не статус;
- список в кабинете: три понятных блока.
Меньше вопросов
Автор больше не спрашивает: «Почему статья в статусе “опубликовано”, но её нет в списке?» Такого статуса нет.
Читатель не находит «полустатью». Если ссылка ведёт на черновик — 404. Если на опубликованную — всё работает.
Предпросмотр вместо «временной публикации»
Раньше, чтобы показать черновик, приходилось либо публиковать и прятать, либо делать unlisted. Теперь есть preview_token:
/article/?id=42&preview=8f3c...
Ссылка работает для черновика, не индексируется, токен можно перевыпустить. Это честнее: статья не становится публичной, пока её не опубликовали.
Как это устроено технически
В таблице posts есть status и preview_token.
Публичная страница:
- Ищет статью по
id. - Если
status === 'published'— показывает. - Если статус другой — проверяет
previewи сравнивает с токеном. - Если токен не совпал — 404.
Список /articles/ и data/articles.json содержат только published. Sitemap — только published. Блог — отдельно, статьи туда не попадают.
Кнопка «Снять с публикации»:
UPDATE posts SET status = 'draft', published_at = '' WHERE id = ?
Всё. Никаких «скрытых» флагов.
Где были грабли
«Опубликованные» смешивались с «На проверке»
Это не про unlisted, но про ту же путаницу статусов. В кабинете статьи со статусом pending попадали в список опубликованных. Автор видел статью в «Опубликованных» и думал, что она вышла.
Пришлось разделить списки:
- «Черновики» —
draft+rejected; - «На проверке» —
pending; - «Опубликованные» —
published.
Урок: статус в интерфейсе должен совпадать со статусом в жизни. Иначе интерфейс врёт.
Ссылка на статью в блоге
Когда статьи запускались, в блоге оставались карточки статей. Получалась «статья в статье»: блог и статьи — разные разделы, а карточки пересекались.
Убрали статьи из блога. В /devblog/ — только короткие заметки. В /articles/ — только статьи. Ссылка на статьи — отдельный пункт меню.
Урок: если разделы разные, у них должны быть разные списки. Иначе пользователь не понимает, где он.
Обложки и предпросмотр
Черновик с обложкой выглядит как готовая статья. Это нормально, но может сбивать. В кабинете статус виден явно, в предпросмотре — тоже.
Обложки генерируются отдельно: 1200×630, html2canvas, можно скачать и загрузить. Это не часть публикации, а инструмент.
Почему не оставить unlisted «на всякий случай»
Потому что «на всякий случай» — это состояние, которое никто не тестирует. Оно есть, но им не пользуются. Потом кто-то пользуется, и оказывается, что оно ломает sitemap или показывает черновик в поиске.
Если понадобится «секретная ссылка» — для этого есть preview token. Если понадобится «статья для своих» — это уже не статья, а другой формат: закрытый раздел, рассылка, документ.
Урок: не добавляй состояние, если не можешь описать его одной фразой и показать, где оно видно.
Что в итоге
Сейчас в проекте пять опубликованных статей. Одна удалена. Черновики и материалы на проверке не видны публично.
- «Статьи на InIProject: как устроен раздел» — гид по разделу.
- «От одной страницы до портала: как я делал сайт InIProject» — история сайта.
- «Что я понял, пока делал InIProject: сомнения, ошибки и выводы» — выводы.
- «Искать людей — тоже работа: как мы искали тестеров и участников» — поиск людей.
- «Первый, кто пришёл извне: история Aevornyx» — история Aevornyx.
Мне нравится, что раздел растёт не «пачкой», а по одной статье. Лучше пять нормальных текстов, чем двадцать «скоро».
Вместо вывода
Unlisted кажется удобным, пока не начинаешь объяснять, что он значит. Для небольшого проекта проще два состояния и предпросмотр по токену.
Продукт — это не только то, что можно сделать. Это ещё и то, что можно объяснить. Если состояние требует абзаца объяснений, возможно, оно лишнее.
