Мы все ближе к завершению разработки блога. На этот раз от скучных вещей перейдем к более веселым. Добавим всякие полезности на наш сайт: фильтрацию статей по тегам, базовую пагинацию и даже поддержку markdown.
В общем, всячески прокачаем возможности нашего блога и сделаем его более похожим на полноценный веб-ресурс.
Предыдущий материал: Блог на Svelte. Часть 5: Многостраничный сайт, авторизация, редактирование статей
Список авторов и ссылки на них в статьях
Не знаю, насколько эта функция вообще нужна, но она встречается практически во всех блогах и на многих новостных ресурсах. Пусть и в нашем блоге тоже будет. На этом этапе мы наконец воспользуемся таблицей Users, которую создали в Supabase две статьи назад, настроим взаимосвязь между этой таблицей и статьями в блоге по никнейму пользователей.
Но перед началом надо слегка модифицировать компонент signUp. Нам нужно добавить туда два новых поля и привязать две новые переменные: name и nickname.
nickname нам нужен, чтобы определять автора статей без необходимости трогать его уникальный идентификатор.
Так что добавим в функцию signUp еще и функцию передачи в базу данных параметра nickname.
Надо отметить, что вам, скорее всего, придется создать это свойство в таблице Users самостоятельно, указав в отдельном поле пункты nickname и userName.
Создаем список авторов
Начнем генерацию новых пользователей в таблице Users при помощи новой функции addUserToTable. Давайте разберем ее код:
• Указываем название самой функции и аргумент userInfo – const addUserToTable = async (userInfo) =>
• В теле функции делаем несложный запрос к базе данных, который будет заполнять таблицу Users:
let data, error = await supabase.from(‘Users’).insert([userId: userInfo.id, userName: name, nickname: nickname])
Тут все, думаю, понятно без дополнительных комментариев.
addUserToTable мы будем запускать внутри функции signUp. Сразу после переноса информации о пользователе в хранилище запускаем addUserToTable с аргументом userInfo. Так мы передадим в таблицу Users наиболее актуальную информацию о зарегистрировавшемся человеке.
Готово. Теперь после каждой регистрации необходимая информация о пользователе будет попадать не только во внутреннюю БД Supabase, но и в нашу таблицу Users, которая ляжет в основу фильтрации материалов по их автору.
Создаем страницу со списком авторов
До того как начнем связывать посты и их создателей, отправим всех пользователей в единый список. Так посетители смогут посмотреть каждого, кто создает контент для вашего блога. Да и вообще у нас появится еще одна кнопка в навигационной панели.
Создаем файл authors.svelte и внутри пишем следующий код:
• Импортируем базу данных при помощи import supabase from ‘$lib/db’
• Импортируем onMount таким образом: import onMount from ‘svelte’
• Создаем пустой массив для пользователей из БД – let users = []
• Прописываем функцию onMount, в теле которой будет запрос к таблице Users:
let data, error = await supabase.from(‘Users’).select(‘*’)
• Значение переменной users меняем на список полученных пользователей из БД – users = data
И делаем простенький интерфейс для описанной выше логики.
С помощью блока #each перебираем все записи в таблице и отображаем их как кнопку с текстом user.nickname (будем отображать их никнеймы, да). К этой же кнопке потом привяжем ссылки на статьи конкретного автора.
Теперь это добро нужно добавить на панель навигации.
Открываем файл layout.svelte и добавляем туда еще одну кнопку со ссылкой на файл authors.svelte. Готово.
Прикрепляем ссылки на авторов к статьям
Имя автора должно отображаться на странице slug.svelte. То есть после того, как мы откроем пост и посмотрим его полностью. Поэтому и соответствующую логику будем добавлять в компонент slug.
Сначала сделаем функцию getAuthorName. Она как раз и будет отвечать за весь процесс добычи никнейма автора и его отображения под названием статьи. Вот как она выглядит:
• Cоздаем сам метод – const getAuthorName = async ( ) =>
• В тело забрасываем стандартный запрос к Supabase:
let data, error = await supabase.from(‘Users’).select(‘*’).eq(‘userId’, post.userId)
Так мы сможем вытащить сразу все записи из Users, у которых id совпадает с id поста. В нашем случае это будет только одна запись.
• Закрепляем за переменной author, которую нужно объявить до вызова функции, никнейм автора – author = data[0].nickname. Снова лезем в data по индексу, так как БД возвращает нам массив.
Функцию getAuthorName можно отправить в getData, чтобы объединить логику получения информации в один блок кода.
Что касается интерфейса, то достаточно добавить строку <p>{author}</p> под названием статьи.
Настраиваем страницу для фильтра статей по автору
Сам по себе список авторов не особо полезен, поэтому нам нужна возможность нажать на имя и получить весь список статей, написанных одним человеком. Для этого сделаем еще один аналог slug.svelte в директории authors. Назовем его authorsArticles.svelte.
Внутри добавим функцию, в которой будет заключена следующая логика:
• Мы импортируем туда базу данных – import supabase from ‘$lib/db’
• Туда же импортируем компонент Post – import Post from ‘../Post.svelte’. Напоминаю, что Post используется для отрисовки отдельных постов.
• Добавляем в компонент onMount – import onMount from ‘svelte’
• Импортируем объект page – import page from ‘$app/stores’
• Здесь же создаем переменную id, в которую запишем параметры страницы, передаваемые извне – const id = $page.params.authorsArticles
• Создаем переменную для хранения постов. По умолчанию это будет пустой массив.
• Теперь описываем асинхронную функцию внутри onMount. В ней будет следующий запрос к базе данных:
let data, error = await supabase.from(‘Posts’).select(‘’).eq(‘userId’, id)
Так мы не только получим все посты, а еще и отфильтруем их по id автора. Но здесь можно использовать и никнейм, если хотите. id просто для примера.
• Обновляем значение posts на data – posts = data
Интерфейс рендеринга должен выглядеть так же, как выглядит рендеринг в компоненте index.svelte. То есть простой перебор постов через блок #each и их отправка в компонент Post с помощью spread-аргументов.
Теги
Одна из наиболее полезных функций в нашем SvelteKit-приложении. Создадим возможность отсеивать статьи по темам, заранее прописанным со стороны бэкенд-составляющей сайта.
Создаем список тегов
Сначала надо обзавестись этими самыми тегами. По умолчанию их нет, более того, я не планирую реализовывать возможность со стороны пользователей добавлять свои теги. Понятно, что в нашем случае некому заспамить секцию с тегами, но на большом и популярном ресурсе это может стать проблемой. Поэтому все теги будут храниться в Supabase.
Начнем мы с того, что создадим в интерфейсе Supabase новую таблицу.
• Открываем в боковой панели пункт управления данными.
• Нажимаем на кнопку создания новой таблицы.
• Даем ей имя Tags.
• В списке строк оставляем только id и tagName с форматом text.
Сохраняем получившуюся таблицу.
Вы уже в курсе, что добавлять информацию в БД можно прямо через браузер в веб-интерфейсе Supabase. Дополнительные приложения не нужны. Так что мы прямо тут добавим два первых тега. Пусть это будут теги: цитаты и статьи.
Я указал на скриншоте решетки, потому что так принято, но сам их не использую, потому что они мешают переадресации с одной страницы на другую.
И это все. Нам не нужно ничего добавлять в интерфейсе приложения, писать код для добавления тегов в базу тоже не будем.
Настраиваем интерфейс для добавления тегов
Есть разные решения для прописывания тегов. Мы не будем искать изыски и выберем самый простой вариант – банальный список кнопок, нажатие на которые добавляет теги к статьям. Там не будет поиска или шибко симпатичного интерфейса. Но все работает, и работает достаточно хорошо. А если потребуется, то дополнительную функциональность реализуем позже.
Открываем файл NewPost.svelte. В верхнюю часть добавляем сразу три новых переменных:
• let tags – нужна, чтобы хранить в ней теги. По умолчанию оставляем там пустой массив, чтобы приложение не сыпало ошибками при первичной загрузке или при отсутствии тегов у поста.
• let tagsSelected – здесь будут лежать те теги, что мы выбрали на этапе создания поста. Они же будут отправляться в базу данных (в блок Tags конкретного поста).
• let tagsFocused – эта переменная отвечает за отображение блока с тегами. По умолчанию установлено значение false.
Перед тем как перейти к интерфейсу, создадим функцию getTags, которая будет собирать из базы данных список тегов и предлагать нам при создании новых постов. Как должна выглядеть эта функция:
• Объявляем сам метод – const getTags = async ( ) =>
• В тело getTags отправляем соотвествующий запрос в БД:
let data, error = await supabase.from(‘Tags’).select(‘*’)
Никакой фильтрации не нужно, берем сразу все теги. Их все равно не будет слишком много.
• Переназначаем переменную – tags = data
Эту функцию мы будем запускать внутри функции toggleInputVisibility, чтобы запрос к тегам происходил только в момент нажатия на кнопку создания нового поста. Это сэкономит ресурс на тот случай, если человек просто зашел почитать статьи, а не писать свои.
Что касается визуальный репрезентации этого блока, то мы сделаем следующее:
• Начинается все с общего блока, в котором будет функция для переключения режимов отображения списка тегов:
on:click=() => {tagsFocused = ! tagsFocused}
• Создадим div, в который поместим код tagsSelected, чтобы в нем подряд отображались все выбранные теги.
• Добавим в блок с редактором код {#if} tagsFocused {/if}
• Внутрь логического выражения вставляем отдельный div, скрывающий список тегов, но намекающий на их существование.
• Еще ниже разместим блок {#each} tags as tag {/each}
• В него мы запишем кнопку для добавления тега в список tagsSelected:
on:click=(e) => {tagsSelected.push(e.target.innerHTML)}
Туда же стоит добавить переназначение переменной tagsSelected на саму себя, чтобы отобразить в списке тегов актуальную информацию.
• В качестве innerHTML будем использовать tag.tagName.
Теперь в интерфейсе для добавления статей будет отображаться блок с выбранными тегами. Нажав на него, вы получите доступ ко всем тегам из БД. Клик по каждому будет закреплять его за статьей.
Чтобы в итоге информация оказалась в БД, надо дополнить функцию addPost так, чтобы в ней появилось поле tags, в которое и будет помещаться значение переменной tags, созданной нами ранее.
Прикрепляем теги к блоку со статьей
У нас уже отображается никнейм автора под названием поста. Почему бы туда не отправить еще и список использованных тегов с возможностью сразу перескочить на интересующую тему и посмотреть похожие материалы? Звучит удобно, так что сделаем. Тем более опыт уже есть – мы быстро реализуем и нужную функцию, и интерфейс для нее.
Фактически будем копировать то же, что делали в случае с именами авторов.
• Сначала создаем переменную tags в файле [slug].svelte. В нее помещаем пустой массив, чтобы сайт не рухнул при отсутствии тегов для какой-либо из статей.
• Модифицируем функцию getData, чтобы массив tags дополнялся данными из объекта post.tags, прописав: tags = post.tags
• После этого добавляем простой #each перебора тегов и отображаем его под именем автора. К каждому можно прикрепить ссылку вида <a href=/tags/${tag}></a>. Это будет ссылка, ведущая на файл [tagFilters].svelte в папке tags, который мы создадим чуть позже.
Настраиваем страницы для фильтра статей по тегу
Нам нужна страница с примененным фильтром в духе той же, что используется в списке статей конкретных авторов.
• Теперь создаем папку tags, а внутри нее файл [tagFilters].svelte.
Внутрь записываем следующий код:
• Импортируем Supabase – import supabase from ‘$lib/db’
• Сюда же добавляем Post – import Post from ‘../Post.svelte’
• Таким же образом добавляем функции onMount и page.
• Создаем константу tag и приравниваем ее к параметру, получаемому извне – const tag = $page.params.tagFilters
• Создаем переменную для отфильтрованных постов с пустым массивом внутри – let posts = []
• В onMount отправляем следующий код:
let data, error = await supabase.from(‘Posts’).select(‘*’).contains(‘tags’, tag)
Здесь мы впервые используем сложный фильтр contains. Он позволяет нам анализировать целые массивы и проверять, нет ли в них запрашиваемой информации. Это необходимо, чтобы мы могли проверить, подходит ли статья по тегу, при этом анализируя совпадение только с одним из тегов, а не с несколькими сразу. А еще в onMount надо добавить переназначение переменной posts – posts = data
Что касается интерфейса, то можно скопировать тот же код, что используется в [authorsArticles].svelte.
Пагинация
Пагинация – это метод деления страницы на части. Логика заключается в том, чтобы отображать на экране только часть контента и не загружать сразу все материалы, коих может быть великое множество.
Если вы видите в каком-нибудь блоге цифры под списком статей, то вы столкнулись с пагинацией. Благодаря ей можно быстро перемещаться между материалами.
Сделаем такой же механизм на своем сайте, причем без сторонних библиотек. Реализуем простейший вариант пагинации:
• Откроем index.svelte и создадим в нем переменную range с массивом цифр – let range = [0, 4]
• Затем мы добавим в функцию getData аргумент range. Он будет использоваться для ограничения подачи контента прямо из базы данных. Для этого мы перемешаем алгоритм метода, дополнив его новой директивой:
let data, error = await supabase.from(‘Posts’).select(‘’).range(range[0], range[1])
Мы используем массив с цифрами для ограничения количества постов при первичной загрузке.
• Теперь нам нужна функция для изменения значения range. Назовем ее updateData.
Мы будем проверять передаваемый аргумент. Если этот аргумент ‘next’, то будем увеличивать значения обоих чисел в range. Если же аргументом будет ‘back’, то мы будем уменьшать значения range. После изменения показателей range снова запустим getData(range), чтобы обновить информацию на странице.
А ниже нарисуем такой несложный интерфейс:
Одна кнопка будет запускать функцию updateData с аргументом ‘next’. А другая – с аргументом ‘back’. Тут важно их ограничить, сделав так, чтобы нужные кнопки отображались в нужное время. Например, в кнопке «Назад» нет смысла, если значение range[0] меньше 0, так как это означает, что мы на первой странице. То же касается кнопки «Вперед». Если длина массива posts не равна длине промежутка между значениями в массиве range, то эту кнопку можно блокировать, так как она не будет работать должным образом.
Markdown
С поддержкой markdown все куда сложнее. Простого варианта реализовать полноценную конвертацию символов не существует. Поэтому мы впервые воспользуемся сторонней библиотекой для Svelte. Благо на просторах npm есть легковесная и простая в использовании библиотека svelte-markdown.
Чтобы ее установить, открываем терминал в директории с нашим проектом и пишем:
npm install svelte-markdown
По завершении установки надо вернуться к коду и модифицировать пару функций.
• Открываем файл slug.svelte.
• Импортируем в него SvelteMarkdown:
import MarkdownSvelte from ‘svelte-markdown’
• Потом добавляем компонент SvelteMarkdown на место post.body, а post.body передаем в качестве пропса – <SvelteMarkdown source=post.body />
Теперь вы можете добавлять в редакторе символы, присущие Markdown, и они будут отображаться согласно правилам разметки.
Это хорошо видно на скриншотах.
Добавляем звездочки, чтобы выделить слово population жирным.
И вот оно.
Вместо заключения
Как видите, блог почти готов. Мы добавили кучу важных функций и полезных опций. Дело осталось за малым. Надо решить вопрос с изображениями, реализовать подобие лайков и добавить на сайт поддержку профилей (можно будет вести базовую статистику, такую как количество статей автора, число лайков и т.п.). Возможно, прикрутим комментарии, Supabase это позволяет. А на завершающем этапе проведем оптимизацию кода, удалим мусор и проведем редизайн.
Пока все. Если будут вопросы, постараюсь помочь в комментариях. Спасибо за внимание!
Продолжение: Блог на Svelte. Часть 7: Загрузка картинок в оглавление статьи
Комментарии