Если вы посещаете страницу /tags/category-slug/tag-name и нажимаете кнопку Новая тема, поле создания темы уже содержит предустановленный тег, как описано здесь:
Это отлично. Но теперь я (и как минимум ещё один человек) хотим иметь возможность устанавливать такое поведение с тегом по умолчанию при посещении страницы /c/cat-slug/cat-id. Похоже, что компонент темы должен иметь возможность нацеливаться на эту кнопку и либо изменять её, либо скрывать и добавлять новую кнопку (прямо там есть выход для плагина, который я сейчас не могу найти, но видел минуту назад).
Предполагается, что оно должно работать только в одной конкретной категории, или вам нужна поддержка «тега по умолчанию» для множества категорий, где этот тег различается для каждой из них?
Представляю, что я бы создал настройку для установки тега по умолчанию для нескольких категорий. Скорее всего, это возможно, но я не знаю, где и как изменить кнопку «Создать тему», чтобы она автоматически включала этот тег по умолчанию.
Идентификаторы HTML-элементов должны быть уникальными, то есть… два элемента на одном экране не могут иметь одинаковый атрибут id. Так что этого достаточно, чтобы начать.
Если я поищу "create-topic" на GitHub, вот что я увижу…
Там только один результат, так что нам повезло. Если бы результатов было больше, можно было бы сузить список, но это выходит за рамки данной темы.
Итак, давайте проверим этот файл.
Тогда вы увидите, что действие кнопки задано следующим образом:
action=action
Ну… это не очень помогает… Что же делать дальше?
Когда вы видите action=action, это означает, что действие передаётся в компонент из родительского шаблона.
Давайте попробуем найти, какие шаблоны используют этот компонент. Для этого перейдём на GitHub и поищем название компонента так, как оно используется в шаблоне. В нашем примере это будет что-то вроде "{{create-topic-button"
Обратите внимание, что я добавил только {{COMPONENT_NAME и пропустил остальное. Мы не знаем другие аргументы, передаваемые компоненту, поэтому нужен общий поиск.
Получили два результата… один из них находится в плагине styleguide, так что мы его просто игнорируем. Второй находится в ядре (core). Давайте посмотрим, как это выглядит:
Ах… мы приближаемся. Теперь вы видите, что действие для кнопки — это:
action=(action "clickCreateTopicButton")
Теперь нужно выяснить, что делает это действие. Для этого поищем имя действия. Затем отфильтруем результаты по файлам .js, так как теперь нам нужно найти определение этого действия в JS-файле компонента.
Снова получаем только один результат, так что давайте посмотрим на него.
Похоже, что действие делает одно из двух: если категория доступна только для чтения и у пользователя ещё нет черновика, оно показывает предупреждение. В противном случае вызывает метод createTopic().
Нас интересует второе, так что давайте посмотрим на него.
Если вы поищете createTopic() в этом файле (встроенный поиск, не на GitHub)… вы заметите, что есть только одна ссылка на него. Что происходит? Как этот компонент вызывает метод, который не определён?
Ответ находится выше в файле.
Что это значит?
Я не хочу тратить здесь много времени, но Ember использует классы. Представьте классы как переиспользуемые пакеты кода. Вся выделенная строка выше означает:
Взять пакет Component из Ember, добавить к нему пакет FilterModeMixin и позволить мне добавить несколько новых методов или переопределить некоторые существующие, чтобы создать новый компонент Ember для моего приложения.
Итак, теперь вернёмся к действию, которое мы пытаемся отследить.
Оно вызывает this.createTopic(). Это не стандартный метод компонента Ember. Это кастомный метод Discourse, поэтому он должен поступать из FilterModeMixin. Что такое FilterModeMixin? Ну… он определён в верхней части файла.
import FilterModeMixin from "discourse/mixins/filter-mode";
Остановитесь на секунду и выполните встроенный поиск createTopic() в этом файле. Я имею в виду это. Перестаньте читать и сделайте это. Я подожду… не обманывайте… я слежу за вами .
ОК. Вы искали, и результатов не было. Что теперь?
То, что я описал выше, — это лишь один из способов передачи данных вниз. Если вы не нашли то, что искали, сделайте шаг назад и попробуйте другой подход.
Итак, давайте подытожим… где мы сейчас? До того как мы застряли, мы смотрели на JS-файл компонента d-navigation. Давайте посмотрим на его шаблон.
Это имеет значение? Может быть. Имеет ли это значение в данном случае? Нет. Мы просто пытаемся понять, откуда берётся createTopic() или что это такое. Так что давайте просто возьмём первый результат.
Отлично… ещё больше жаргона… потому что всем это нравится
Но если серьёзно, давайте поговорим о действиях маршрута (route actions). Что это такое? Ну, это действия маршрута… то есть действия, определённые на маршруте. Почему они удобны? Потому что маршруты в Discourse могут быть вложенными.
Посмотрите на это так:
- route-1
- route-1-1
- route-1-2
- route-1-3
Если у меня есть общий компонент, который мне нужно использовать на маршрутах 111, 112 и 113 с разными параметрами, не было бы проще просто использовать один и тот же компонент во всех них и передавать одно и то же действие? Затем при необходимости модифицировать его для каждого маршрута?
Именно это и делают route-actions.
ОК, вернёмся к вопросу. Мы смотрели на
createTopic=(route-action "createTopic")
в компоненте navigation/default.
Теперь нам просто нужно выяснить, какой это маршрут, чтобы проверить, что делает это действие маршрута.
Вы хотите изменить поведение кнопки создания новой темы на страницах /c/cat-slug/cat-id. Так что давайте посетим одну из таких страниц. Например: http://localhost:4200/c/meta/6
Что это за маршрут? Если вы не очень хорошо знакомы с Discourse, вы не сможете этого сказать. Что теперь?
Здесь пригодится расширение Ember для вашего браузера.
Установите его здесь, если у вас его ещё нет. Я подожду.
(ссылка ведёт на репозиторий GitHub, но в описании есть ссылки на расширения для разных браузеров)
ОК, теперь у вас оно установлено, снова посетите страницу /c/cat-slug/cat-id и откройте страницу расширения.
После загрузки нажмите на Routes, затем включите опцию “Current Route only”.
Похоже, оно делает одно из двух: если у пользователя есть черновик, оно открывает его. Если нет, вызывает openComposer() с параметром. Что дальше? Вы уже должны знать ответ. Нам нужно выяснить, откуда берётся openComposer() или что он делает.
Итак, мы ищем в файле openComposer() и… конечно, результатов нет. В этом маршруте нет метода с именем openComposer().
Что дальше? Помните про классы Ember? Давайте попробуем это.
В верхней части файла маршрута у нас есть:
Это означает, что этот маршрут наследует все методы из пакета DiscourseRoute, а также тех, что определены в пакете OpenComposer.
openComposer скорее всего то, что нам нужно, так что давайте посмотрим на него. Но перед этим… нам нужно посмотреть, как openComposer определён в этом файле.
import OpenComposer from "discourse/mixins/open-composer";
Посмотрите на URL. Это не компонент Ember. Это не маршрут, не модель. Это миксин. Что, чёрт возьми, такое миксин? Очень, очень короткий ответ… это пакет переиспользуемых функций.
Вы определяете их в своём миксине.
add(number) {
return number + 1
}
substract(number) {
return number - 1
}
затем добавляете миксин к вашему компоненту Ember, и тогда вы можете сделать что-то вроде этого:
// начальное значение равно 1
myMethod () {
this.add(value) // возвращает 2
this.substract(value) // возвращает 0
}
Так как это связано с тем, что мы пытаемся сделать?
Ну, open-composer здесь.
import OpenComposer from "discourse/mixins/open-composer";
является миксином. Один из методов в этом миксине — OpenComposer().
Это нормально, если вы чувствуете себя сбитым с толку. У них одинаковые имена, за исключением того, что одно начинается с заглавной буквы, что указывает на то, что это класс.
Они означают разные вещи.
Чтобы понять это, нужно знать, что имена, которые вы даёте импортируемым модулям, не имеют значения (в данном конкретном случае), если они экспортируются как “default”.
Объяснение этого выходит за рамки данной темы. Всё, что вам нужно знать, это то, что
HTML id кнопки New topic < действие кнопки New topic < действие компонента d-navigation < действие маршрута discovery < миксин OpenComposer < метод openComposer()
Итак… это метод, который в конечном итоге вызывается при нажатии кнопки + New Topic на этом маршруте.
Мы выяснили, как найти действие для этой кнопки на /c/cat-slug/cat-id, но оно кажется отличным от того, что происходит при посещении /tags/category-slug/tag-name, что вы и хотите сделать.
Так что же делать дальше? Давайте посмотрим, что делает этот маршрут для обработки действия createTopic().
Ну… вы заметите, что оно обрабатывает действие по-другому.
Итак, всё, что вам нужно сделать, это… модифицировать действие createTopic() в маршруте discovery, чтобы оно работало так же, как на маршруте tag-show. Как это сделать?
Помните, как мы говорили о том, что Ember использует классы? Да, нам придётся вернуться к этому снова.
API плагинов позволяет вам изменять классы Ember с помощью этого метода.
Итак, что мы пытаемся изменить? Маршрут discovery… потому что… помните, именно там определяется createTopic(), когда вы находитесь на странице вроде /c/cat-slug/cat-id.
Что это делает? Оно ломает кнопку + New Topic; однако это говорит нам, что мы движемся в правильном направлении. Если вы попробуете добавить фрагмент выше, вы заметите, что при нажатии кнопки редактор больше не открывается. Вместо этого просто выводится сообщение в консоль. Это хорошо, потому что означает, что мы targeting правильный класс и правильное действие — route:discovery и createTopic().
Итак, что дальше? Ну, помните, что кнопка на /tags/category-slug/tag-name делает именно то, что нам нужно. Так что давайте скопируем код из этого маршрута — и добавим необходимые импорты.
Сработает ли это? Нет, но мы на шаг ближе. Почему это не работает? Потому что теги, которые добавляются при открытии редактора, не определены. Почему? Потому что они загружаются из контроллера tag.show, а это не то, что нам нужно. Давайте модифицируем код, чтобы он работал с маршрутом, на котором мы находимся.
Прежде чем мы это сделаем, нам нужен какой-то индекс для наших желаемых тегов по умолчанию. Давайте создадим новый объект так:
// category-slug: [DEFAULT_TAGS_ARRAY]
const defaultTagIndex = {
// slug из одного слова
meta: ["a", "b", "c"],
core: ["g", "h"],
// slug с дефисом
["general-chat"]: ["d", "e", "f"]
};
Это означает: если редактор открывается на странице категории meta, добавить теги «a, b, c».
Если редактор открывается на странице категории core, добавить теги «g, h» и так далее.
Теперь, когда у нас это есть, мы можем модифицировать действие, чтобы оно выглядело так.
Я обернул всё в блок try…catch. Если код не сработает, мы выполним this._super(...arguments).
Если вы знакомы с Ember, вы знаете, что делает this._super(...arguments). Если нет, вот простое объяснение. Мы переопределяем createTopic(), поэтому, если переопределение не сработает из-за ошибки — возможно, ядро было обновлено — мы вернёмся к методу в ядре, определённом здесь.
Если у пользователя есть черновик новой темы, мы просто возвращаемся к this._super(...arguments) и позволяем ядру делать своё дело.
Этого должно быть достаточно. Всё, что вам нужно добавить сейчас, — это способ создания индекса тегов по умолчанию через настройки темы.