토픽 텍스트 안에 위젯 삽입하기

안녕하세요!! :slight_smile:

다른 분들도 이 내용이 도움이 될 것 같아 공유합니다. 저는 “소셜 미디어 피드” 토픽을 만들고 있는데, 다양한 플랫폼의 위젯을 임베드하여 사용자(그리고 관리자!)들이 한 페이지에서 모두 빠르게 확인할 수 있도록 하려고 합니다. 이렇게 하면 여러 소셜 플랫폼을 오가며 빠른 업데이트를 확인해야 하는 부담을 줄일 수 있고(보통 같은 콘텐츠를 공유하니까요), 커뮤니티 개발을 위한 허브로 포럼을 사용하는 동기를 높일 수 있을 것 같습니다.

이 목적에 적합하도록 깔끔하고 간단한 위젯 생성기로 Woxo를 찾았습니다. 문제는 이제 이 위젯을 토픽에 임베드하는 방법을 모르겠다는 것입니다. iframe이나 다른 방식으로 우회할 수 있는지 알아보고 있었지만, 일단 이것이 가능한지 여부에 대해 질문을 드리고자 합니다.

Woxo에서 받은 인스타그램 피드용 코드는 다음과 같습니다:

<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 섹션 하단(내부)에 나타나며, 소스가 화이트리스트에 등록되어 있으므로 효과가 있어야 합니다. 브라우저 문제일 수 있는지 확인해 보았지만, W3Schools Tryit Editor 에서 HTML을 실행하면 완벽하게 작동합니다.

에러를 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에 배치했을 때 스크립트는 에러를 발생시키지 않습니다(이전에 에러가 발생했다는 사실은 js 파일 URL이 실제로 화이트리스트에 등록되어 있음을 증명합니다). 이제 왜 아무것도 작동하지 않는지 완전히 막막합니다.

어떤 의견이라도 감사하겠습니다. 시간을 내 주셔서 감사합니다! :slight_smile:
Lisandro

수정: 결국 포기했습니다. iframe만 지원되므로, 현재 이를 제공할 수 있는 좋은 웹 서비스를 찾고 있습니다. 대부분의 무료 서비스는 제한이 너무 많고, 유료 버전은 포럼 호스팅 서비스 비용의 두 배 이상입니다. 무료 HTML 코드를 단순히 삽입할 수 없어 이렇게 큰 소리로 불평을 늘어놓게 되어 죄송합니다. :cry:

Discourse는 단일 페이지 애플리케이션(SPA)입니다. 겪고 계신 문제는 사용 중인 스크립트가 이 점을 인지하지 못하기 때문에 발생합니다. Discourse에서 홈페이지나 다른 어떤 페이지를 방문하더라도 다음과 같은 구조를 받게 됩니다.

<html>
  <head>
    스크립트를 포함한 head 콘텐츠
  </head>
  <body>
    <section id="main">
      페이지 콘텐츠
    </section>
  </body>
</html>

다른 페이지로 이동할 때 새로고침되는 것은 다음 안의 콘텐츠뿐입니다.

<section id="main">

즉, DOM이 변경되었지만 사용자 정의 스크립트는 다시 실행되지 않습니다. 토픽 페이지를 직접 방문해보면 정상적으로 로드되는 것을 확인할 수 있습니다.

.

이제 Discourse에서 이 기능이 작동하도록 하는 방법을 알아봐야 합니다.

plugin-api에는 게시물을 "장식(decorate)"하는 데 사용할 수 있는 메서드가 있습니다.

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

이를 사용하여 게시물이 렌더링될 때 제3자 스크립트를 실행할 수 있습니다.

아래는 필요한 코드입니다. 이 코드를 테마의 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;
};

// create a post decorator
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를 woxo가 제공하는 src로 변경하세요. 만드는 모든 임베드에 대해 동일해야 합니다.

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;

// 정적 컴포저 미리보기에서 사용하려는 아이콘의 svg를 가져오기 위해
// Discourse의 Icon HTML 함수를 로드합니다.
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;
};

// 게시물 장식기(decorator) 생성
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 = "";

          // 모든 준비가 끝났으니 woxo 스크립트의 init 메서드를 호출합니다.
          window.MC.Loader.init();
        });
      });
    }
  },
  // 메모리 누수를 방지하기 위해 장식기에 id를 추가합니다.
  { id: "render-woxo-widgets" }
);

</script>

우선: O-M-G 정말 정말 감사드립니다 :exploding_head:
반응의 수준에 놀라움을 금치 못하고, 이렇게 많은 양의 작업을 볼 때 위안이 되는 것은 이것이 많은 이들에게 큰 도움이 될 것이라는 점입니다. 이는 커뮤니티 허브로서의 포럼 개발에 이로운 많은 새로운 가능성을 열어줍니다.

두 번째로: 이렇게 빠르게 답변해 주셨는데 제가 이렇게 늦게 답장드려서 정말 죄송합니다. 먼저 컴퓨터를 더 일찍 사용할 수 없었고, 두 번째로 친절하게 주석 달아 주신 코드를 (줄마다 :flushed:, 정말 대단하네요, 정말 감사드립니다) 제대로 확인한 후에 답변하고 싶었습니다. 물론, 모든 것이 완벽하게 작동했고, 피드가 매우 빠르게 로드됩니다… 정말 은혜를 입었습니다 :pray:

Johan 씨, 다시 한번 감사드립니다. 제 경험으로 제 몫을 할 수 있기를 진심으로 바랍니다 :pray:

P.S. 정적 미리보기용 하트는 그대로 유지할 예정입니다.