Встраивание виджета в текст темы

Привет!! :slight_smile:

Надеюсь, эта тема будет интересна кому-то ещё: я пытаюсь создать тему в стиле «ленты социальных сетей», в которую можно было бы встроить виджеты разных платформ, чтобы пользователи (и администраторы!) могли быстро просматривать их все на одной странице. Это позволит оставаться на форуме, избегая необходимости переключаться между множеством социальных платформ ради быстрых обновлений (учитывая, что они обычно делятся одним и тем же контентом), тем самым повышая мотивацию использовать форум как центр развития сообщества.

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

Вот код, который я получаю от Woxo для ленты Instagram:

<div data-mc-src="f4b43a8f-c188-4f80-8206-36d9f7529f13#instagram"></div>
        
<script 
  src="https://cdn2.woxo.tech/a.js#616348fb53c1e8001686c619" 
  async data-usrc>
</script>

Что я уже пробовал:

  • Размещал тег <script> в секциях <body>, <footer> и <header> (в итоге оставил его в header).
  • Убедился, что URL, с которого загружается скрипт, находится в белом списке (в данном случае https://cdn2.woxo.tech/).
  • Добавление атрибута defer не помогло (оставил его на всякий случай).

Если я проверю страницу через инструменты разработчика, скрипт действительно появляется в нижней части секции <body>, и поскольку источник находится в белом списке, он должен сработать. Я проверил, не проблема ли это моего браузера, но если запустить этот HTML здесь W3Schools Tryit Editor, всё работает отлично.

Я сузил ошибку до конкретной функции внутри JS-скрипта. Следующий вызов возвращает null. Это единственная ошибка времени выполнения:

e=document.querySelector("div[data-mc-src]")
e is null

Этот div прописан в теме (часть <div data-mc-src="f4b43a8f-c188-4f80-8206-36d9f7529f13#instagram"></div>). Он остаётся в виде чистого HTML-кода, поэтому должен быть читаемым. По какой-то причине скрипт не может его найти.

С атрибутом defer и размещением в footer скрипт больше не выдаёт ошибок (то, что раньше выдавал ошибку, доказывает, что URL JS-файла действительно находится в белом списке), поэтому теперь я в тупике: не понимаю, почему ничего не работает.

Любые советы будут очень кстати, спасибо заранее за ваше время! :slight_smile:
Лисандр

EDIT: в конце концов мне пришлось отказаться от этой идеи. Поскольку поддерживаются только iframe, я сейчас ищу хороший веб-сервис, который может предоставить такой. Большинство бесплатных сервисов слишком ограничены, а платные версии стоят более чем вдвое дороже, чем хостинг самого форума. Извините за жалобы, но мне просто нужно было громко выговориться здесь, потому что я не могу просто вставить бесплатный HTML-код :cry:

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

<html>
  <head>
    содержимое head, включая ваш скрипт
  </head>
  <body>
    <section id="main">
      содержимое страницы
    </section>
  </body>
</html>

При переходе на другую страницу перезагружается только содержимое внутри

<section id="main">

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

.

Итак, теперь вопрос в том, как заставить это работать с Discourse.

В плагин-API есть метод, который можно использовать для «декорирования» постов.

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

Вы можете использовать его для запуска сторонних скриптов при отображении поста.

Вот код, который вам понадобится. Добавьте его во вкладку common > header вашей темы.

<script type="text/discourse-plugin" version="0.8">
const WOXO_SCRIPT_SRC = "https://cdn2.woxo.tech/a.js#616348fb53c1e8001686c619";
const PREVIEW_ICON = "heart";

const loadScript = require("discourse/lib/load-script").default;
const { iconHTML } = require("discourse-common/lib/icon-library");

const composerPreviewIcon = iconHTML(PREVIEW_ICON, {
  class: "woxo-preview-icon"
});

const previewMarkup = () => {
  const markup = `<div class="woxo-preview">${composerPreviewIcon}</div>`;
  return markup;
};

// создаем декоратор постов
api.decorateCookedElement(
  post => {
    const woxoWidgets = post.querySelectorAll("div[data-mc-src]");

    if (woxoWidgets.length) {
      woxoWidgets.forEach(woxoWidget => {
        if (post.classList.contains("d-editor-preview")) {
          woxoWidget.innerHTML = previewMarkup();
          return;
        }

        loadScript(WOXO_SCRIPT_SRC).then(() => {
          const script = document.head.querySelector(
            `script[src*="cdn2.woxo.tech"]`
          );
          script.dataset.usrc = "";
          window.MC.Loader.init();
        });
      });
    }
  },
  { id: "render-woxo-widgets" }
);
</script>

Затем вам нужно добавить несколько доменов для CSP. Добавьте их в настройку сайта

content_security_policy_script_src
https://*.woxo.tech/
https://us-central1-core-period-259421.cloudfunctions.net/availableComponentTracks

Наконец, вам нужно добавить немного CSS для статического предпросмотра в редакторе.

Это помещается во вкладку common > CSS вашей темы.

.woxo-preview {
  height: 400px;
  width: 100%;
  background: var(--primary-low);
  display: flex;
  align-items: center;
  justify-content: center;
  .woxo-preview-icon {
    font-size: var(--font-up-4);
    color: var(--primary-high);
  }
}

Затем вы можете просто добавить

<div data-mc-src="f4b43a8f-c188-4f80-8206-36d9f7529f13#instagram"></div>

в любой пост, и виджеты будут отображаться и работать в полном объеме.

Если вы посмотрите на JavaScript, то заметите, что в самом верху у него есть два варианта.

const WOXO_SCRIPT_SRC = "https://cdn2.woxo.tech/a.js#616348fb53c1e8001686c619";
const PREVIEW_ICON = "heart";

Измените WOXO_SCRIPT_SRC на src, который предоставляет Woxo. Он должен быть одинаковым для всех создаваемых вами встраиваний.

Измените PREVIEW_ICON на имя иконки, которую вы хотите использовать в предпросмотре редактора. Запуск этого кода в редакторе довольно ресурсоемок, поэтому редактор имеет статический предпросмотр, который выглядит так.

Выбранная вами иконка будет отображаться в центре.

Вот версия кода с комментариями, если вы хотите следить за происходящим

код с комментариями
<script type="text/discourse-plugin" version="0.8">
// опции
const WOXO_SCRIPT_SRC = "https://cdn2.woxo.tech/a.js#616348fb53c1e8001686c619";
const PREVIEW_ICON = "heart";

// мы используем библиотеку Discourse Load script для корректной загрузки скриптов.
// Не беспокойтесь, она достаточно умна, чтобы не дублировать скрипт,
// если он уже загружен
const loadScript = require("discourse/lib/load-script").default;

// мы загружаем функцию Discourse Icon HTML для получения SVG иконки,
// которую хотим использовать в статическом предпросмотре редактора
const { iconHTML } = require("discourse-common/lib/icon-library");

// настраиваем иконку предпросмотра в редакторе
const composerPreviewIcon = iconHTML(PREVIEW_ICON, {
  class: "woxo-preview-icon"
});

// создаем вспомогательную функцию для разметки предпросмотра в редакторе
const previewMarkup = () => {
  const markup = `<div class="woxo-preview">${composerPreviewIcon}</div>`;

  return markup;
};

// создаем декоратор постов
api.decorateCookedElement(
  post => {
    // есть ли в этом посте виджеты woxo?
    const woxoWidgets = post.querySelectorAll("div[data-mc-src]");

    // Да, давайте выполним некоторые действия.
    if (woxoWidgets.length) {
      // для каждого виджета woxo
      woxoWidgets.forEach(woxoWidget => {
        // если это виджет редактора, замените его статическим предпросмотром и
        // завершите работу
        if (post.classList.contains("d-editor-preview")) {
          woxoWidget.innerHTML = previewMarkup();
          return;
        }

        // если это не в редакторе, загрузите скрипт woxo.
        loadScript(WOXO_SCRIPT_SRC).then(() => {
          // Скрипт woxo очень странный. Он не будет работать, если тег
          // скрипта не имеет пустого атрибута data-usrc. Давайте добавим его
          const script = document.head.querySelector(
            `script[src*="cdn2.woxo.tech"]`
          );
          script.dataset.usrc = "";

          // всё готово, вызываем метод init в скрипте woxo
          window.MC.Loader.init();
        });
      });
    }
  },
  // добавляем id декоратору, чтобы избежать утечек памяти
  { id: "render-woxo-widgets" }
);

</script>

Прежде всего: О-М-Г, ОГРОМНОЕ СПАСИБО ОЧЕНЬ МНОГО :exploding_head:
Я в шоке от масштаба ответа; единственное утешение при виде такого объема работы — это то, что очевидно, что это станет огромной помощью для многих. Это открывает множество новых возможностей, все к лучшему для развития форума как центра сообщества.

Во-вторых: мне так жаль, что я отвечаю так поздно, учитывая, что вы ответили так быстро. Сначала я не мог добраться до компьютера раньше, а во-вторых, я хотел ответить только после того, как действительно изучил код, который вы так любезно прокомментировали (строка за строкой :flushed:, боже мой, огромное спасибо). Конечно, всё работало идеально, вся лента загружается так быстро… Я в долгу :pray:

Ещё раз спасибо, Йохан, я очень надеюсь, что смогу внести свой вклад своим собственным опытом :pray:

P.S.: Я абсолютно оставлю сердечко для статического предпросмотра.