Закрепите уведомление о новых или обновленных темах

Здравствуйте,

Это может быть полезно, если бы строка Показать x новых или обновлённых тем оставалась зафиксированной в верхней части под заголовком, чтобы вы сразу видели её при прокрутке списка тем. При клике на эту строку страница будет прокручиваться к началу для загрузки новых или обновлённых тем.

Фиксация работает отлично с этим :arrow_down:

#list-area .show-more.has-topics {
  position: sticky;
  top: var(--header-offset);
}

Вторая часть (JS) должна быть функцией для перехода в начало страницы или прокрутки к началу, но я не знаю, как это сделать или какой способ будет лучшим.

Я нашёл этот участок в шаблонах discovery/topics.hbs и discovery/categories.hbs. Если изменить <a href на <a href="/", это может сработать (не уверен, я не пробовал), но в этом случае при каждом клике будет происходить полная перезагрузка страницы, как при клике на логотип.

Спасибо за помощь :slightly_smiling_face:

Да, добавление URL вызовет навигацию, так как нет логики для перехвата события.

Ссылка вызывает действие Ember. Все действия в Discourse расширяемы. Таким образом, они работают как хуки кастомизации. Так как же изменить действие? Давайте сначала посмотрим, что оно делает.

Ищите на GitHub или локально по имени действия. Действия всегда определяются в JS-файлах и упоминаются в Handlebars. Нам нужно увидеть определение, поэтому сузим поиск до JS-файлов.

Repository search results · GitHub

Вы получите четыре файла. Какой из них нужно просмотреть? Вы хотите кастомизировать шаблон

discovery/topics.hbs.

Значит, вам нужен файл

discovery/topics.js.

discourse/app/assets/javascripts/discourse/app/controllers/discovery/topics.js at e15bd194fde2c5ec21b216e7b594c543e8dc2739 · discourse/discourse · GitHub

Помогает ли какой-то из этих фрагментов кода? Нет, но теперь мы знаем, где определено действие. Давайте его изменим.

discovery/topics.js — это класс Ember. Вы можете модифицировать классы Ember с помощью метода в plugin-api, который называется… modifyClass :stuck_out_tongue:

https://github.com/discourse/discourse/blob/main/app/assets/javascripts/discourse/app/lib/plugin-api.js#L166-L195

Мы уже знаем класс, который хотим изменить. Это discovery/topics. Нам нужно понять, к какому типу классов он относится. Проверим структуру каталогов.

discourse/app/controllers/discovery/topics.js

Это контроллер.

Также мы знаем, что хотим изменить действие showInserted в этом классе. Начнём с этого:

api.modifyClass("controller:discovery/topics", {
  pluginId: 'sticky-new-topics-banner',
  actions: {
    showInserted() {
      // давайте сделаем какую-то работу
    }
  }
});

Затем вы можете добавить любой код, который прокручивает окно при клике пользователя на баннер «новые темы». Я использовал что-то вроде этого:

const listControls = document.querySelector(".list-controls");
listControls.scrollIntoView();

Подробнее о scrollIntoView() можно прочитать здесь.

Затем добавьте это в метод plugin-api следующим образом:

api.modifyClass("controller:discovery/topics", {
  pluginId: 'sticky-new-topics-banner',
  actions: {
    showInserted() {
+     const listControls = document.querySelector(".list-controls");
+     listControls.scrollIntoView();
    }
  }
});

Итак, мы закончили? Нет. Это ломает Discourse, потому что вы полностью переопределяете действие. При клике на ссылку теперь будет происходить прокрутка к элементу list-controls, но новые темы не загрузятся. Почему? Потому что код из ядра больше не выполняется. Имеется в виду вот этот код:

Так как это исправить? С помощью одной простой строки:

this._super(...arguments);

Вам не нужно копировать код из ядра, если вы просто хотите добавить что-то к нему. Выполните свою задачу, затем добавьте эту строку. Всё, что она делает, — гарантирует, что код из ядра будет применён.

api.modifyClass("controller:discovery/topics", {
  pluginId: 'sticky-new-topics-banner',
  actions: {
    showInserted() {
     const listControls = document.querySelector(".list-controls");
     listControls.scrollIntoView();
+
+    this._super(...arguments);
    }
  }
});

Если вы протестируете это, то увидите, что почти всё работает отлично, кроме одного… заголовок перекрывает list-controls. Почему? Потому что заголовок установлен как sticky.

Это можно исправить несколькими способами на JS — рассчитать высоту, получить смещение, импортировать вспомогательный метод из Discourse и т.д. Я не буду углубляться в эти варианты.

Самый простой способ — использовать CSS со свойством scroll-margin-top, о котором можно прочитать здесь.

Добавим следующее:

.list-controls {
  scroll-margin-top: calc(var(--header-offset) * 2);
}

По-русски: при клике на ссылку прокрутка должна остановиться на верхней части list-controls минус удвоенная высота заголовка, чтобы не было перекрытия и оставался небольшой отступ снизу.

Итак, давайте соберём всё вместе.

Вкладка common header:

<script type="text/discourse-plugin" version="0.8">
api.modifyClass("controller:discovery/topics", {
  pluginId: "sticky-new-topics-banner",
  actions: {
    showInserted() {
      const listControls = document.querySelector(".list-controls");
      listControls.scrollIntoView();
      this._super(...arguments);
    }
  }
});
</script>

Общие стили CSS:

#list-area {
  // на мобильных устройствах используется другая раскладка
  .alert-info,
  .show-more.has-topics {
    position: sticky;
    // Safari иногда капризен без префикса
    position: -webkit-sticky;
    top: var(--header-offset);
    // баннер должен быть поверх контента
    z-index: z("header");
  }
}

.list-controls {
  scroll-margin-top: calc(var(--header-offset) * 2);
}

Спасибо, @Johani! Это очень помогло мне, и я, кажется, наконец-то понял многое. Мне очень нравятся ваши подробные ответы, потому что из них можно многому научиться: как всё устроено и, конечно же, как это работает. Ещё раз спасибо! :slightly_smiling_face: