Можно ли прослушать событие входа пользователя, используя компонент Theme?

Всем привет,

Я хочу отображать всплывающее окно с помощью компонента Theme, когда пользователь впервые входит в систему.

1. Я попытался переопределить действие входа контроллера входа, используя компонент Theme, чтобы отображать всплывающее окно после успешного входа пользователя.
В действии входа я добавил код bootbox.alert(“Test Alert”); перед hiddenLoginForm.submit();.

Однако всплывающее окно отображается только до отправки формы hiddenLoginForm, после чего происходит перенаправление на главную страницу.

Моя задача — отображать всплывающее окно после перенаправления на главную страницу после успешного входа.

2. Чтобы проверить, входит ли пользователь в систему впервые, я попытался проверить значение свойства previous_visit_at текущего пользователя.
Если previous_visit_at = null (то есть пользователь входит в систему впервые).
Однако и здесь мой тест не сработал: при повторном входе значение свойства previous_visit_at оставалось равным null.
Мне нужно узнать, когда именно обновляется значение свойства previous_visit_at.

Пожалуйста, помогите мне реализовать вышеуказанные требования.

Спасибо,
Саурabh Кханделвал

Привет @Saurabh_Khandelwal :wave:

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

Если вы помните, когда вы впервые зарегистрировались на этом сайте, вы видели что-то вроде этого.

Условия которые мы используем для проверки этого:

!user.read_first_notification && !user.enforcedSecondFactor

Таким образом, если вы используете те же условия в инициализаторе, вы сможете отобразить окно Bootbox при первом просмотре страницы пользователем после входа.

Вы можете попробовать что-то вроде этого в вашем компоненте темы.

import { withPluginApi } from "discourse/lib/plugin-api";
import bootbox from "bootbox";

export default {
  name: "first-login-bootbox",
  initialize() {
    withPluginApi("0.8", api => {
      const user = api.getCurrentUser();
      if (!user) return;

      if (!user.read_first_notification && !user.enforcedSecondFactor) {
        const text = `Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor`;
        bootbox.alert(text);
      }
    });
  }
};

и это даст вам что-то вроде этого

При необходимости в тексте окна bootbox можно использовать HTML. После того как пользователь закроет окно bootbox и нажмёт на аватар в кружке, он больше никогда не увидит это окно.

Обратите внимание, что окна bootbox предназначены для простых диалогов и подтверждений. Если вам нужно что-то немного более сложное, лучше использовать модальное окно с помощью showModal()

Спасибо, @Johani, за ваш совет. Я воздержусь от внесения изменений в действие входа и постараюсь реализовать это так, как вы указали.

[quote=“Johani, post:3, topic:170976”]
Условия, которые мы используем для проверки этого, следующие:

!user.read_first_notification && !user.enforcedSecondFactor

Таким образом, если вы примените те же условия в инициализаторе, вы сможете отобразить окно Bootbox при первом просмотре страницы пользователем после входа в систему.

Вы можете попробовать что-то подобное в компоненте вашей темы.
[/quote].

Привет, @Johani, я добавил простую гиперссылку (например, «/new-topic») во всплывающее окно, которая открывается в новой вкладке.
При клике на эту ссылку она открывается в новой вкладке, но снова отображается всплывающее окно, так как у нас проверены следующие условия:

!user.read_first_notification && !user.enforcedSecondFactor

В моём случае всплывающее окно не должно отображаться после перехода по ссылке.

Можно ли обновить значения этих свойств после того, как всплывающее окно было показано хотя бы раз? Если да, то как это сделать?

Боюсь, это невозможно.

У этих свойств нет сеттеров; даже если бы они были, ваши изменения применялись бы только временно в первом окне. Как только пользователь перейдёт на вторую вкладку, данные будут основываться на том, что хранится в базе данных. Темы не имеют доступа к бэкенду; они могут изменять только фронтенд.

Что вы можете сделать, так это добавить хеш к вашей ссылке и проверить его, например, так:

import { withPluginApi } from "discourse/lib/plugin-api";
import bootbox from "bootbox";

export default {
  name: "first-login-bootbox",
  initialize() {
    withPluginApi("0.8", api => {
      const user = api.getCurrentUser();
      if (!user) return;

      if (
        !user.read_first_notification &&
        !user.enforcedSecondFactor &&
        !window.location.hash
      ) {
        const text = `Lorem ipsum dolor sit amet <a href="http://localhost:3000/new-topic#some-hash" target="_blank">Ссылка</a>, consectetur adipiscing elit, sed do eiusmod tempor`;
        bootbox.alert(text);
      }
    });
  }
};

Не уверен, была ли ссылка на “/new-topic” в вашем посте просто примером или это то, что вы хотите сделать. Если это желаемый результат, то у вас есть ещё одна проблема. Даже если bootbox не отображается на странице с хешем, пользователь всё равно увидит это…

…и композер не откроется, что логично, так как пользователю совершенно неожиданно начать писать тему сразу при первом просмотре страницы.

Можно ли спросить, чего вы пытаетесь добиться здесь? Вы хотите сообщить пользователю что-то конкретное?

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

Вот что я предлагаю:

  1. Создайте тему и добавьте туда всю нужную информацию.
  2. Опубликуйте эту тему.
  3. Добавьте ссылку на эту тему в bootbox и откройте её в новой вкладке.

Таким образом, когда пользователь нажмёт на ссылку, он увидит что-то вроде этого (без оверлея):

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

Таким образом, вам даже не нужно добавлять или проверять хеш. Вот пример фрагмента кода:

import { withPluginApi } from "discourse/lib/plugin-api";
import bootbox from "bootbox";

export default {
  name: "first-login-bootbox",
  initialize() {
    withPluginApi("0.8", api => {
      const user = api.getCurrentUser();
      if (!user) return;

      if (!user.read_first_notification && !user.enforcedSecondFactor) {
        const text = `Lorem ipsum dolor sit amet <a href="http://my.site.com/pub/bentley-flying-spur-s-production-milestone" target="_blank">Ссылка</a>, consectetur adipiscing elit, sed do eiusmod tempor`;
        bootbox.alert(text);
      }
    });
  }
};

Я пытаюсь добавить ссылки вроде “/new-topic” и “/my/preferences” внутри всплывающего окна.

Или, возможно, я мог бы показывать всплывающее окно только на главной странице, проверяя текущий URL.
Таким образом, при посещении других страниц всплывающее окно отображаться не будет.

Даже если я проверяю URL главной страницы после нажатия на ссылку «/new-topic», вышеуказанная проблема всё равно возникнет

Привет, Саварбх,
Да, я думаю, что проверка текущего URL и отображение всплывающего окна только на главной странице должно сработать.

Мы можем показать новое приветственное сообщение от Discobot через некоторое время, например, через 2 минуты:

Привет, @Johani

Можно ли создать виджет, как показано ниже, и вызвать bootbox() внутри него?
А после вызова bootbox можно ли вызвать метод “skipNewUserTips()”, который также используется Discourse в виджете header-notifications.

<script type="text/discourse-plugin" version="0.8">
 const { h } = require('virtual-dom');
 const { ajax } = require("discourse/lib/ajax").default;
 const DiscourseURL =  require("discourse/lib/url");
 const { userPath } = require("discourse/lib/url");

api.createWidget("welcome-popup", {
  tagName: "div.welcome-popup",
  html(attrs) {
      let user = this.currentUser;
      if (!user) return;
      if (
        !user.get("read_first_notification") &&
        !user.get("enforcedSecondFactor")
      ) {
          const title = `<div class="first-login-bootbox-title">Вы новый пользователь. Добро пожаловать!</div>`;
          const body = `<div class="first-login-bootbox-body">Теперь вы можете: </br> <ol class="user-suggestions"><li><a href="/new-topic" target="_blank" >Задать вопрос или начать обсуждение</a></li> <li><a href="/my/preferences/tags" target="_blank">Настроить параметры уведомлений</a></li></ol></div>`;
          bootbox.alert({
                        title: title,
                        message: body
                    });
            this.skipNewUserTips();        
      }
      return null;
  },

  skipNewUserTips() {

    ajax(userPath(this.currentUser.username_lower), {
      type: "PUT",
      data: {
        skip_new_user_tips: true,
      },
    }).then(() => {
      this.currentUser.set("skip_new_user_tips", true);
    });
  },
});
</script>

<script
  type="text/x-handlebars"
  data-template-name="/connectors/below-site-header/welcome-popup"
>
  {{mount-widget widget="welcome-popup"}}
</script> 

Код Discourse для виджета header-notifications:
https://github.com/discourse/discourse/blob/master/app/assets/javascripts/discourse/app/widgets/header.js#L101

Это вызовет какие-либо проблемы?

Это полностью пропускает это оверлей для всех новых пользователей. Таким образом, они никогда его не увидят.

Если вас это устраивает, то да, это должно работать.

Однако вам нужно изменить вызов bootbox. Иначе вы получите вот это.

вместо того, чтобы делать вот так

Вам следует передавать только один аргумент.

Эти опции были добавлены в более новые версии Bootbox и не будут работать с той версией, которую мы сейчас используем в Discourse (мы обсуждаем это внутри, но пока вы не можете их использовать).

Кроме того, поскольку вы создаете PUT-запрос, вы также можете пропустить это.

this.currentUser.set("skip_new_user_tips", true);

Так что, возможно, что-то вроде этого.

  api.createWidget("welcome-popup", {
    tagName: "div.welcome-popup",
    html(attrs) {
      let user = this.currentUser;
      if (!user) return;
      if (
        !user.get("read_first_notification") &&
        !user.get("enforcedSecondFactor")
      ) {
        const body = `<h2 class="first-login-bootbox-title">Вы стали участником. Добро пожаловать!</h2>
                      <hr>
                      <div class="first-login-bootbox-body">
                        Теперь вы можете:
                        <br>
                        <ol class="user-suggestions">
                          <li>
                            <a href="/new-topic" target="_blank"
                              >Задать вопрос или начать обсуждение</a
                            >
                          </li>
                          <li>
                            <a href="/my/preferences/tags" target="_blank"
                              >Настроить параметры уведомлений</a
                            >
                          </li>
                        </ol>
                      </div>`;
        bootbox.alert(body);
        this.skipNewUserTips();
      }
      return null;
    },

    skipNewUserTips() {
      ajax(userPath(this.currentUser.username_lower), {
        type: "PUT",
        data: {
          skip_new_user_tips: true
        }
      });
    }
  });

Привет @Johani,

Поскольку мы установили skip_new_user_tips в true, уведомление Discobot Greeting также пропускается. Если не отображается первое наложение маски уведомления — это нормально, но мы хотим, чтобы уведомление Discobot Greeting всё же показывалось.

Уведомление Discobot:

Для этого мы изменили код так, чтобы уведомление Discobot Greeting загружалось для всех новых пользователей.
Теперь мы добавили новый метод в виджет заголовка — “closeFirstNotificationMask()”, где установили “ringBackdrop” в false, а “userVisible” — в инвертированное значение. Этот метод вызывается после вызова метода bootbox в виджете welcome-popup.

Мы взяли пример из метода “toggleUserMenu” виджета заголовка, который вызывается при клике на иконку пользователя.

<script src="https://cdnjs.cloudflare.com/ajax/libs/bootbox.js/5.4.1/bootbox.min.js" integrity="sha512-eoo3vw71DUo5NRvDXP/26LFXjSFE1n5GQ+jZJhHz+oOTR4Bwt7QBCjsgGvuVMQUMMMqeEvKrQrNEI4xQMXp3uA==" crossorigin="anonymous"></script>

<script type="text/discourse-plugin" version="0.8">
 const { h } = require('virtual-dom');
 const { ajax } = require("discourse/lib/ajax").default;

/* Добавляем новый метод в виджет заголовка, который будет вызываться после отображения Welcome-popup */
api.reopenWidget("header",{
    closeFirstNotificationMask() {
        this.state.ringBackdrop = false;
        this.state.userVisible = !this.state.userVisible;
        this.toggleBodyScrolling(this.state.userVisible);
    }
});

/* Создаём новый виджет "welcome-popup", который будет отображаться только при первом входе пользователя */
api.createWidget("welcome-popup", {
  tagName: "div.welcome-popup",
  html(attrs) {
      let user = this.currentUser;
      if (!user) return;
      if ( !user.get("read_first_notification") && !user.get("enforcedSecondFactor") ) {
          const title = `<div class="first-login-bootbox-title">Вы стали участником. Добро пожаловать!</div>`;
          const body = `<div class="first-login-bootbox-body">Теперь вы можете: </br> <ol class="user-suggestions"><li><a href="/new-topic" target="_blank" >Задать вопрос или начать обсуждение</a></li> <li><a href="/my/preferences/tags" target="_blank">Настроить параметры уведомлений</a></li></ol></div>`;
          
          bootbox.alert({
                        title: title,
                        message: body
                    });
            // Вызываем метод действия closeFirstNotificationMask виджета заголовка
            this.sendWidgetAction("closeFirstNotificationMask", this.attrs);
        }
      return null;
  },
  
});

/* Ниже приведён код, который отображает виджет "welcome-popup" после отображения виджета "header-notifications" при первом входе пользователя */
api.decorateWidget("header-notifications:after", helper => { 
    if(!helper.attrs.active && helper.attrs.ringBackdrop){
        return helper.attach("welcome-popup", helper.attrs);     
    }else{
        return null;    
    }
});
</script>

Ещё один момент: мы используем версию Bootbox 5.4.1, подключив соответствующий скрипт в компоненте нашей темы, так как в настоящее время Discourse не поддерживает метод bootbox с несколькими параметрами. Это приемлемо?

<script src="https://cdnjs.cloudflare.com/ajax/libs/bootbox.js/5.4.1/bootbox.min.js" integrity="sha512-eoo3vw71DUo5NRvDXP/26LFXjSFE1n5GQ+jZJhHz+oOTR4Bwt7QBCjsgGvuVMQUMMMqeEvKrQrNEI4xQMXp3uA==" crossorigin="anonymous"></script>

Пожалуйста, дайте нам своё мнение, спасибо!

Выглядит отлично :+1:

Я проверил это локально, проблем не обнаружил. Внёс несколько незначительных изменений.

<script
  src="https://cdnjs.cloudflare.com/ajax/libs/bootbox.js/5.4.1/bootbox.min.js"
  integrity="sha512-eoo3vw71DUo5NRvDXP/26LFXjSFE1n5GQ+jZJhHz+oOTR4Bwt7QBCjsgGvuVMQUMMMqeEvKrQrNEI4xQMXp3uA=="
  crossorigin="anonymous"
></script>

<script type="text/discourse-plugin" version="0.8">
  const user = api.getCurrentUser();
  if (!user || user.read_first_notification || user.enforcedSecondFactor) {
    return;
  }

  const bootboxTitle = `<div class="first-login-bootbox-title">Вы стали участником. Добро пожаловать!</div>`;
  const bootboxBody = `<div class="first-login-bootbox-body">Теперь вы можете: </br> <ol class="user-suggestions"><li><a href="/new-topic" target="_blank">Задать вопрос или начать обсуждение</a></li> <li><a href="/my/preferences/tags" target="_blank">Настроить параметры уведомлений</a></li></ol></div>`;

  /* Добавляем новый метод в виджет заголовка, который будет вызываться после отображения всплывающего окна приветствия */
  api.reopenWidget("header", {
    closeFirstNotificationMask() {
      this.state.ringBackdrop = false;
      this.state.userVisible = !this.state.userVisible;
    }
  });

  /* Создаём новый виджет "welcome-popup", который будет отображаться только при первом входе пользователя */
  api.createWidget("welcome-popup", {
    tagName: "div.welcome-popup",
    html(attrs) {
      // Вызываем метод действия closeFirstNotificationMask виджета заголовка
      this.sendWidgetAction("closeFirstNotificationMask", attrs);

      bootbox.alert({
        title: bootboxTitle,
        message: bootboxBody
      });
    }
  });

  /* Ниже приведён код, который отобразит виджет "welcome-popup" после отрисовки виджета "header-notifications" при первом входе пользователя */
  api.decorateWidget("header-notifications:before", helper => {
    if (!helper.attrs.active && helper.attrs.ringBackdrop) {
      return helper.attach("welcome-popup", helper.attrs);
    }
  });
</script>

Да, это нормально. Discourse загружает используемую нами версию bootbox в виде обёртки (shim), поэтому загрузка другой версии в компонент вашей темы не изменит ничего в ядре. Новая версия будет использоваться только в компоненте вашей темы. Единственный недостаток — это добавляет один дополнительный запрос и около 4 кБ к начальной загрузке страницы.

Привет, @Johani. Поскольку вы загружаете виджет welcome-popup до виджета header-notifications, похоже, что уведомление Discobot не загружается. Кроме того, после нажатия на ссылки внутри welcome-popup пользователю снова отображается welcome-popup.
Я протестировал это, загрузив виджет welcome-popup после виджета header-notifications — это работает корректно. Поэтому могу ли я изменить это на “header-notifications:after”?

Странно :thinking:

Я только что снова попробовал тот же код с тремя новыми пользователями, и каждый раз получаю ожидаемый результат. Я вижу всплывающее окно при первом просмотре страницы, и сообщение приветствия от Discobot отправляется. При последующих просмотрах страницы всплывающее окно не появляется.

Конечно, если у вас это работает, то расположение декоратора не должно иметь значения.

Спасибо @Johani, мы будем использовать декоратор after.