Продолжаем доделывать блог на базе SvelteKit. В этот раз создадим отдельную страничку для пользователя, где будет отображаться основная информация о нем, о написанных статьях, о количестве лайков. Также там будет аналог био из социальных сетей и аватарка.
Предыдущая статья: Блог на Svelte. Часть 8: Лайки
Ссылка на профиль пользователя
Начнем с добавления отдельной страницы, где и будет храниться основная информация о пользователе. То есть новый автоматически генерируемый роут, для разных пользователей показывающий разные данные из базы, соответствующие определенному id, почтовому адресу или никнейму.
Для этого создадим директорию profile в папке routes, а в ней создадим файл [profile].svelte.
Затем нужно добавить ссылку на соответствующий роут в файле index.svelte, где сейчас отображается приветствие с указанием электронной почты пользователя. Информацию берем из хранилища user и ее же привязываем. При желании вы можете выгрузить другие данные о текущем пользователе и использовать в качестве ссылки уже их.
<a href={`/profile/${$user.email}`}>
Страница есть, но она пока что пустая.
Список статей пользователя
Похожий фильтр уже есть, но все же принято показывать пользователю список написанных им статей прямо в профиле. Такое работает и в Комьюнити, и на многих других площадках.
Напишем функцию для фильтрации списка статей и отрендерим его на странице profile.
-
Импортируем базу данных Supabase в компонент профиля: import supabase from '$lib/db'
-
Затем импортируем функцию onMount следующим образом: import { onMount } from 'svelte'
-
И user из хранилища: import { user } from '$lib/stores'
-
Создаем переменную, в которой будут храниться все посты. Пусть по умолчанию это будет пустой массив. Так получится избежать ошибок при загрузке профиля без статей: let posts = []
-
Потом делаем запрос к базе данных:
let { data, error } = await supabase.from('Posts').select('*').eq('userId', $user.id)
-
И записываем полученную информацию в переменную posts следующим кодом: posts = data
Остается лишь отрисовать полученный контент на странице. Благо для этого у нас есть компонент Post, который мы импортируем в [profile].svelte: import Post from '../Post.svelte'
А потом добавляем блок {#each} с перебором все статей: {#each posts as post} <Post {...post} /> {/each}
Готово. Статьи отобразятся в том же виде, что и на главной. И у них по умолчанию будут работать ссылки на полные версии статей.
Фото профиля
Ранее мы уже добавляли картинки на сайт, но закрепляли их за статьями. Повторим ту же схему, но теперь для изображений профиля, которые сможет поставить пользователь в качестве своего аватара. Это же фото можно будет показывать в списке комментариев или же под статьями рядом с именем автора. Вариантов масса.
Сначала создадим новый столбец в базе данных под названием profilePicture. Он нужен, чтобы привязать изображение к конкретному человеку с его логином и паролем. Оставляем тип text и позволяем использовать значение NULL.
Теперь возвращаемся к коду и создаем картинку по умолчанию. Это нужно, чтобы даже у пользователя без аватарки хоть что-то в качестве нее отображалось. Это может быть хоть пустой кружок, но лучше придумать что-то более оригинальное.
Я просто возьму фавиконку из раздела static, которая лежит в базовом проекте SvelteKit.
Создаем переменную imageSource и указываем в ней путь до нужной картинки.
let imageSource = "../../../static/favicon.png"
Затем создаем в разметке элемент, в котором будет отображаться картинка (тег img), и в качестве источника изображения выбираем imageSource.
<img width=60 height=60 src={imageSource} alt />
Теперь у каждого пользователя вместо аватарки будет логотип Svelte. Но только до тех пор, пока он сам не поменяет изображение. Сейчас дадим ему такую возможность.
Для начала создадим интерфейс для загрузки изображений в браузер. Добавим переменную file в [profile].svelte: let file
Потом добавим input, привязанный к переменной file. Его можно разместить рядом с картинкой:
<input bind:this={file} type="file" />
Для добавления изображений нам нужно придумать им название. Я решил, что отличным выбором будет имя пользователя, а лучше его никнейм вкупе с названием самого файла. Поэтому нам нужно вытащить из базы данных nickname пользователя. Для этого в onMount нужно сделать соответствующий запрос к Supabase и сохранить полученное значение в переменную userName.
-
Создаем переменную userName: let userName = ""
-
Внутри onMount делаем запрос к БД. Мы запрашиваем пользовательские данные, ориентируясь на id пользователя, зашедшего на сайт:
let {data: userInfo} = await supabase.from('Users').select('*').eq('userId', $user.id)
-
Затем меняем значение переменной userName на nickname пользователя: userName = userInfo[0].nickname
Далее создадим функцию загрузки изображений, которая будет активироваться автоматически при добавлении новой картинки на сервер Supabase.
-
Прописываем асинхронную функцию uploadImage вот так: const uploadImage = async ( ) => { }
-
В тело функции записываем информацию о будущем изображении, взяв за основу файл, привязанный к переменной file: let image = file.files[0]
-
Потом записываем разрешение картинки: const imageExt = image.name.split('.').pop()
-
Следующий шаг – формирование имени загружаемой картинки: const imagename = `${userName}${file.name}.${imageExt}`
-
Нужные переменные сгенерированы, и можно сделать первый запрос к БД. Сначала загрузим картинку на сервер:
const { data, error } = await supabase.from('pictures').upload(imageName, image)
-
Затем запрашиваем информацию об уже загруженных изображениях. В частности той, что мы только что отправили в БД:
const { data: images } = await supabase.storage.from('pictures').getPublicUrl(imageName)
-
Обновляем информацию об аватарке пользователя в базе данных:
let { data: profilePicture } = await supabase.from('Users').update({profilePicture: images.publicURL}).eq('userId', $user.id)
-
А потом переадресуем пользователя на ту же страницу, чтобы он видел внесенные изменения: goto(#)
Пользователь увидит свою аватарку, так как мы заменяем значение imageSource на userInfo[0].profilePicture при первичной загрузке страницы профиля, и делаем это только при условии, что в базе данных есть какая-то запись. В противном случае отображается логотип Svelte, указанный по умолчанию.
Описание профиля
Еще одна часть интерфейса страницы пользователя, часто встречающаяся в сети – это био. То есть краткое описание, которое дает пользователь о себе, чтобы о нем было известно чуть больше, чем ничего.
Как обычно, начинаем с созданием подходящего столбца в Supabase. Назовем его description. Это будет текст, который может иметь значение NULL.
Потом создаем аналогичную переменную в коде на странице [profile].svelte. По умолчанию она может быть пустой. Это не играет роли.
Описание пользователя мы будем подтягивать через функцию onMount.
-
В асинхронном блоке onMount сначала прописываем запрос к базе, а точнее именно к пункту description того пользователя, что сейчас зашел на сайт.
let { data: desc } = await supabase.from('Users').select('description').eq('userId', $user.id)
-
Меняем значение переменной description на данные, полученные из запроса к Supabase: description = desc[0].description
Теперь нужно сделать интерфейс для отображения описания и для его изменения. Объединим его в единый блок, отвечающий сразу за обе функции.
-
Создаем div-блок, внутрь которого помещаем textarea. Значением textarea будет содержимое переменной description:
<textarea bind:value={description}></textarea>
- И тут же добавляем кнопку для сохранения изменений, внесенных в textarea. Это будет кнопка, привязанная к функции saveDescription (ее мы создадим позже).
<button on:click={saveDescription}>Save</button>
Осталось сделать метод для обновления description в базе данных.
-
Прописываем асинхронную функцию saveDirection вот так: const saveDirection = async ( ) => { }
-
В теле saveDirection делаем запрос к базе с обновлением столбика description:
let { data, error } =await supabase.from('Users').update({description: description}).eq('userId', $user.id)
Теперь на странице пользователя появится поле с описанием. Можно его отредактировать в любой момент, а по нажатию на кнопку Save сохранить новый текст в БД.
Список лайков
Теперь разберемся с блоком лайков на странице пользователя. Здесь мы сделаем сразу две вещи:
-
Укажем, сколько вообще лайков получили посты пользователя (то есть те, что написаны автором, которым являетесь вы).
-
Сделаем блог, в котором отображаются все статьи, которые вам понравились (пока без отдельной страницы).
Начнем с первого пункта.
Количество лайков от других пользователей
Чтобы отобразить лайки для конкретного человека, нужно просто обратиться к ключу likes в объекте data, который мы получаем еще при первоначальной загрузке страницы, когда выводим список постов.
Сначала создадим переменную likes и прировняем ее к нулю. Будем использовать ее, чтобы где-то хранить количество лайков.
Потом пробегаемся по всем статьям в data и вытаскиваем длину массивов likes для каждой из них. Также складываем все строчные значения через символ "+" методом join и подсчитываем общую сумму длины массивов. Звучит сложно, но на практике реализуется одной простой строчкой:
likes = eval(data.map(e => e.likes.length).join('+'))
Осталось сделать хоть какой-то интерфейс, который покажет нам количество пользовательских лайков.
Это будет обычный div с svg в форме сердечка и подписью со значением переменной likes. Структура: <div>{likes}</div>
Теперь вы точно будете знать, сколько раз вас лайкали (подсчитав всех пользователей, лайкнувших ваши посты).
Все лайкнутые посты (сохраненные в избранное)
Еще один вариант применения лайков – хранение статей, которые понравились (например, чтобы вернуться к ним позже). В профиле часто есть отдельный раздел для таких материалов. Мы не будем хранить ссылки на них или делать отдельную страницу, а лишь сделаем специальный блок, показывающий названия тех статей, что вам когда-то приглянулись.
Для этого добавим в код переменную likedPost, которая будет пустым массивом по умолчанию: let likedPosts = []
Затем внутри функции onMount добавляем высчитывание лайкнутых статей. Мы будем делать это с помощью алгоритма поиска собственного ника среди пользователей, лайкнувших тот или иной пост. То есть мы переберем все статьи и отфильтруем те, на которых стоит наше сердечко:
likedPosts = data.filter(e => e.likes.includes(userInfo[0].nickname))
Для примера возьмем массив статей из data (то есть свои же), но вы можете сделать запрос ко всем материалам на сайте.
Ну и в заключение надо сделать какое-то графическое отображение лайкнутых постов. Я сделал простейший список с названиями, но сюда же можно прицепить ссылку или любой другой контент:
Вот что у нас получается:
Пока не очень симпатично, но это временное явление. Мы будем дизайнить блог в следующих материалах
Вместо заключения
Еще одна важная составляющая блога завершена. Мы приближаемся к концу. Осталось приделать комментарии, и можно переходить к «уборке». Нам нужно зарефакторить код, чтобы его дальнейшая модификация и добавление новых функций не вызывали боль ни у нас, ни у других разработчиков, которые могут в будущем столкнуться с работой над вашим сайтом.
Обязательно делайте коммиты и изучите предыдущие материалы из этого цикла, ведь многие концепции, затрагиваемые здесь, объяснялись ранее. Удачи в разработке!
Продолжение: Блог на Svelte. Часть 10: Комментарии
Комментарии