Я сейчас пытаюсь добавить видео на конкретные страницы профилей пользователей, чтобы у всех наших патрона был определённый фоновый видеоролик на странице профиля. (Прежде чем вы начнёте возмущаться, это будет просто зацикленная анимация, а не полноценное видео — должно выглядеть довольно неплохо, в духе фоновых изображений профиля Steam.)
Следующий HTML и CSS код работает для всех пользователей, но, очевидно, это не совсем то, чего мы хотим:
В отличие от использования body.category-general для добавления изображения только на страницы категории «general», кажется, что для страниц профилей пользователей определённой группы или с определённым именем пользователя нет специальных слогов. Мы довольно новые в этом деле и в основном имеем опыт работы с CSS, а не с HTML напрямую, поэтому не уверены, есть ли простой и удобный способ реализовать это так, как мы хотим.
Мы предполагаем, что лучшим подходом будет добавление похожего слогового идентификатора для профилей пользователей на основе их группы, но не знаем, как это сделать и как настроить отображение видео только на страницах с нужным содержимым. Кроме того, мы не привязаны именно к этому подходу, если существует другой, более простой метод.
Например, мы также были бы открыты к идее реализации этого для каждого пользователя отдельно, а не для каждой группы, если это как-то проще.
Мы просто не хотим вручную прописывать видео на каждой странице, чтобы оно загружалось только при посещении конкретных пользователей.
Редактирование: Стоит отметить, что мы используем стабильную ветку, на случай если это что-то меняет.
Наш текущий подход заключается в том, чтобы проверить, находимся ли мы на странице определённого пользователя, используя каноническую ссылку, и если да, то применить видео. В связи с этим у нас есть следующее:
<script type="text/discourse-plugin" version="0.8">
api.onPageChange(() =>{
determineUser();
});
function determineUser() {
var pageURL = document.querySelector("link[rel='canonical']").getAttribute("href");
var isUserPage = pageURL.includes("https://www.fortressoflies.com/u/");
document.documentElement.style.setProperty('--currUsername', pageURL);
if(isUserPage)
{
document.documentElement.style.setProperty('--lastUsername', pageURL);
$('body').css('background-color', '#'+(Math.random()*0xFFFFFF<<0).toString(16));
}
}
</script>
Однако это работает только при полной перезагрузке страницы — по какой-то причине переход между страницами не обновляет свойство --currUsername. Вместо того чтобы применять случайный цвет фона только для страниц пользователей, он применяется ко всем страницам, если последняя перезагрузка (F5) была выполнена на странице пользователя, и не применяется ни к одной странице, если F5 был нажат на не-пользовательской странице.
Честно говоря, у меня недостаточно опыта в JavaScript, чтобы понять, почему это происходит. Мне кажется, что при смене страницы функция должна запускаться (что и происходит), обновляя переменную pageURL, и это должно приводить к обновлению свойства --currUsername при загрузке страницы. Однако это происходит только при полной перезагрузке; в противном случае переменные, похоже, не меняются.
Похоже, это происходит потому, что канонический URL не обновляется, тогда как свойство «og:url» обновляется.
Проблема лишь в том, что код var pageURL = document.querySelector("meta[property='og:url']").getAttribute("content"); выполняется до того, как обновится сам мета-тег — то есть этот код возвращает URL предыдущей страницы, а не текущей.
Если вы хотите добавить контент на конкретную страницу, ваш лучший вариант — plugin-outlet. Если говорить кратко, plugin-outlet — это места, зарезервированные в шаблонах Discourse, которые вы можете использовать для добавления нового контента.
Первое, что вам нужно выяснить, — существует ли plugin-outlet на странице, на которую вы хотите ориентироваться. Для этого есть компонент темы, который вы можете установить.
После установки этого компонента включите его, перейдите на целевую страницу и проверьте, с чем вам предстоит работать. В вашем случае такой plugin-outlet существует (выделен зеленым цветом)
Итак, тот, который нам нужен, — above-user-profile.
Предположим, его не существует… что тогда? В этом случае лучший вариант — попросить добавить его или отправить PR для добавления в ядро. В большинстве случаев это будет принято, если ваш случай использования имеет смысл.
В любом случае, как я уже сказал, в данном случае он уже существует. Так что давайте посмотрим, как вы можете добавить в него разметку. Для остальной части этого руководства вам не понадобится указанный выше компонент, поэтому вы можете отключить его, так как вы уже знаете имя plugin outlet.
Всё, что вам нужно сделать, — добавить что-то вроде этого во вкладку «header» вашей темы.
<script type="text/x-handlebars" data-template-name="/connectors/OUTLET_NAME/SOME_NAME">
Ваш код разметки идет здесь...
</script>
Вам нужно заменить OUTLET_NAME на имя outlet, на который вы хотите ориентироваться. Затем замените SOME_NAME на имя, которое вы хотите дать этой настройке. Имя может быть любым, но старайтесь быть описательным, если это возможно. Это хорошая практика. Таким образом, у нас получается следующее.
<script type="text/x-handlebars" data-template-name="/connectors/above-user-profile/add-profile-videos">
Ваш код разметки идет здесь... например
<h1>Hello World!!</h1>
</script>
Давайте попробуем это и посмотрим, что произойдет… помните, что приведенный выше фрагмент кода идет во вкладку common > header вашей темы.
Вы не хотите, чтобы ваши видео отображались на каждом профиле; вы хотите, чтобы они отображались только при выполнении определенного условия. Так как же это сделать? Вам понадобятся две вещи: данные для использования и немного JavaScript.
Давайте найдем данные. Помните, я говорил, что plugin-outlet — это зарезервированные места? Так в чем смысл их наличия без контекста? Именно поэтому Discourse передает соответствующие части контекста каждому plugin outlet… но сначала давайте сделаем шаг назад. Когда вы добавляете это
<script type="text/x-handlebars" data-template-name="/connectors/above-user-profile/add-profile-videos">
Ваш код разметки идет здесь, например...
<h1>Hello World!!</h1>
</script>
это выглядит как HTML — и теги script таковыми и являются — но то, что внутри них, обрабатывается как код handlebars.
Это означает, что вы можете сделать что-то вроде этого вместо этого
Теперь, полезно ли что-то из этого? Да… но не в данный момент. Мы вернемся к этому позже. Давайте сделаем еще один шаг назад и посмотрим, как Discourse передает контекст в outlet. Если вы поищете имя outlet на Github — или локально — вы получите это.
Давайте откроем этот файл. Первое, что вы видите, — это строка
Посмотрите на последнюю часть этой строки
args=(hash model=model)
Вы увидите, что Discourse передает model в качестве аргумента в outlet. Для всех практических целей и чтобы сохранить простоту, model = data.
Итак, одним из аргументов для нашего outlet является model, и именно там будут находиться нужные нам данные. Так что давайте вернемся к нашему фрагменту кода.
Вы можете просматривать эти данные и проверять, есть ли там то, что вам нужно. Должно быть, так как они содержат все данные о пользователе, используемые в других элементах на этой странице. Это «модель» страницы пользователя для конкретного пользователя.
Одно из доступных там свойств — … барабанная дробь … группы, к которым принадлежит пользователь.
Итак, если вы выполните
{{log args.model.groups}}
вы получите все группы, к которым принадлежит пользователь, в консоли.
Отлично, теперь у нас есть нужные данные, так что единственное, что осталось, — добавить условие(я) на их основе.
Вам может прийти в голову мысль, что мы можем сделать это в том же фрагменте кода, но, к сожалению, мы не можем. Handlebars — это язык шаблонов. Он имеет очень, очень базовую поддержку логики — ничего beyond простых условий true/false и циклов. Вы не можете делать сравнения и другие подобные вещи.
Так где именно это можно сделать? В классе connector, звучит сложно… знаю.
Если говорить кратко, класс connector — это, по сути, немного JavaScript, прикрепленного к outlet. Это гораздо более тонкая тема, но пока вам достаточно знать только это.
Внутри нашего класса connector мы можем выполнять некоторые действия… но… нам нужно помнить, что это не просто любой файл JavaScript. Из-за отсутствия лучшего описания… представьте это как компонент Ember на диете. Расширение этой темы немного выходит за рамки данного руководства, так что давайте двигаться дальше.
По умолчанию к нему подключены четыре метода
actions позволяет определять действия следующим образом
api.registerConnectorClass("above-user-profile", "add-profile-videos", {
actions: {
myAction() {
// сделать что-то
}
}
});
Затем вы можете вызвать это действие изнутри outlet, например, при нажатии кнопки. Нам это здесь не понадобится, так что давайте двигаться дальше.
api.registerConnectorClass("above-user-profile", "add-profile-videos", {
shouldRender(args, component) {
// вернуть true или false здесь
}
});
Мы также не будем использовать этот метод, поскольку outlet отрисовывается только на страницах профилей, и пока у нас нет других требований. Однако вы можете использовать его для добавления любых условий, которые вы хотите проверить перед отрисовкой outlet. Например, уровень доверия текущего пользователя или что-то подобное. Продолжаем…
api.registerConnectorClass("above-user-profile", "add-profile-videos", {
setupComponent(args, component) {
// сделать что-то
}
});
Это именно то, на чем нам нужно сосредоточиться. Какие бы условия JavaScript или переменные вы ни хотели установить, они идут сюда. Прежде чем углубиться в этот метод, давайте сначала рассмотрим последний метод для полноты картины
api.registerConnectorClass("above-user-profile", "add-profile-videos", {
teardownComponent(args, component) {
// сделать что-то
}
});
это срабатывает, когда outlet собирается быть удаленным. Таким образом, он позволяет выполнить любую необходимую очистку, например, удалить слушатели событий и так далее.
Хорошо, давайте вернемся к setupComponent
api.registerConnectorClass("above-user-profile", "add-profile-videos", {
setupComponent(args, component) {
// сделать что-то
}
});
Вы видите, что ему передаются две вещи. Сначала args, а затем component.
args здесь — это те же данные, которые мы рассматривали ранее. Это контекстные данные, которые Discourse передал в outlet. Так что, если вы выполните
вы увидите в консоли браузера ту же информацию, что и раньше. Группы, к которым принадлежит владелец профиля. Вот где начинается веселье: теперь у вас есть данные и правильный хук. Так что вы можете сделать здесь всё, что угодно. Итак, если я хочу, чтобы видео отображалось только на профилях членов, принадлежащих к определенной группе, я могу сделать это
Если вы попробуете это на странице профиля пользователя из группы staff, в консоли будет выведено true. Итак, теперь единственное, что нам осталось сделать, — передать это в шаблон outlet. Вот как это сделать.
component, переданный в setupComponent, здесь разделяется между connector и outlet. Вы можете передавать данные в outlet, устанавливая их как свойства компонента следующим образом
затем проверьте страницу профиля пользователя из группы staff. Вы увидите, что видео загружается.
Как только вы перейдете со страницы профиля сотрудника, видео исчезнет. Видео не будет отображаться на профилях пользователей, которые не входят в группу staff.
Итак, давайте соберем всё это вместе. Это то же самое, что и выше.
Вот CSS, который я использовал. Вкладка common > css
Измените TARGET_GROUP на имя группы, на которую вы хотите ориентироваться, и добавьте атрибуты src для ваших видео.
Этот пост получился довольно длинным… не пугайтесь этого. Как только вы поймете концепцию, всё, что мы сделали выше, можно сделать менее чем за 3–5 минут.
Хорошая новость в том, что всё, о чем мы говорили, практически одинаково для любого plugin outlet. Единственное, что меняется, — это имя. Так что это применимо к любым изменениям plugin-outlet, которые вы захотите сделать в будущем.
Это невероятно подробно, и я обязательно изучу это на следующей неделе, когда у меня появится время. Но если говорить кратко, то после беглого просмотра это кажется гораздо лучше моей текущей реализации (встраивать видео на каждую страницу и показывать его только на странице профиля пользователя, чего я добился с помощью скрипта, добавляющего тег в тело страницы пользователя, если имя его аккаунта совпадает с определённым значением). Спасибо за подробное объяснение, не терпится приступить к этому!
Однако у нас возникает проблема с CSS — мы хотим внести некоторые изменения в отображение страницы пользователя только для этих пользователей.
В данный момент мы делаем это с помощью того же кода, что и раньше:
<script type="text/discourse-plugin" version="0.8">
api.onPageChange(() =>{
window.onload = determineUser();
});
async function determineUser() {
await sleep(50);
var pageURL = document.querySelector("meta[property='og:url']").getAttribute("content");
var isUserPage = pageURL.includes("https://www.siteurl.com/u/");
var isUser1 = pageURL.includes("u/User1/");
document.body.className = document.body.className.replace(" user-page-animated","");
if(isUserPage)
{
if(isUser1)
{
document.body.className += ' user-page-animated';
}
}
}
function sleep(ms)
{
return new Promise(resolve => setTimeout(resolve, ms));
}
</script>
Это позволяет нам просто копировать и вставлять код для “User1” для каждого нового пользователя, но полагается на задержку в 50 мс после каждой загрузки страницы перед выполнением, что заметно для конечного пользователя (и если её убрать, по какой-то причине ничего не работает).
Есть ли способ также подключить добавление класса к body к коду, который вы предоставили, чтобы мы могли использовать его для стилизации страниц с видео иначе, чем без них?