各ページにカスタムJSを設定するための「on-discourse」JavaScriptに関するフィードバック?

Svelteアプリを各ページにインストールする必要があり、それを記述していました。JSファイルをヘッドセクションに追加し、最初のページロード時にロードする方法を見つけました。しかし、実験したところ、DiscourseはXHR経由で新しいコンテンツを取得し、特定のセクションを置き換えていることに気づきました。そのため、新しいページがロードされたときにアプリが再初期化されていませんでした。

ページが変更されたときに通知を受けるためのさまざまな試みをしましたが、Emberにはフックがないようで、リッスンできるカスタムイベントも見つかりませんでした。

1つの方法は、DOMにミューテーションオブザーバーを追加し、変更を監視することのようです。「#topic div」が再読み込みされる(そして属性が変更されるため、属性変更を監視するだけでよい)ことがわかりました。そこでミューテーションオブザーバーを設定しました(ミューテーションオブザーバーはDOMの変更を監視するための新しくパフォーマンスの高い方法です)。その仕組みは、変更を監視し、変更が発生したときにコールバックを実行して、そのページのSvelteアプリをリロードすることです。

このアプローチは気に入っており、フィードバックを求めています。

1つの質問:代わりにURLの変更を監視すべきでしょうか?popstateのリスナーを登録するのは良い考えでしょうか?

使用するには、テーマ/ヘッドで次のようなことを行います。

<script src="https://files.extrastatic.dev/community/on-discourse.js"></script>
<script src="https://files.extrastatic.dev/community/index.js"></script>
<link rel="stylesheet" type="text/css" href="https://files.extrastatic.dev/community/index.css" >

次に、ライブラリ内で次のようにon-discourseを呼び出すことができます。

function log(...msg) {
  console.log('svelte', ...msg);
}

// これはSvelteのインストールコードです
function setup() {
  try {
    const ID = 'my-special-target-id';
    log('Inside setup()');
    const el = document.getElementById(ID);
    if (el) {
      log('Removed existing element', ID);
      el.remove();
    }
    const target = document.createElement("div");
    target.setAttribute("id", ID);
    log('Created target');
    document.body.appendChild(target);
    log('Appended child to body');
    const app = new App({
      // eslint-disable-next-line no-undef
      target
    });
    log('Created app and installed');
  } catch(err) {
    console.error('Unable to complete setup()', err.toString() );
  }
}

(function start() {
  log('Starting custom Svelte app');
  // 変更時に再インストール
  window.onDiscourse && window.onDiscourse( setup );
  // 初回ページロード時にアプリをロード
  window.addEventListener('load', () => {
    setup();
  });
  log('Finished custom Svelte app);  
})();

基本的に、コールバック(複数インストールするために複数回実行できます)とともにwindow.onDiscourse(callback)を呼び出すだけで、ミューテーションが発生すると、そのコールバックが実行されてアプリが初期化されます。

on-discourse.jsの完全なコードは次のとおりです。(編集:これを#topicを使用するように更新しました。これは、ページがロードされるときに属性が変更されるため、監視するのに良いことのようです。そして、ミューテーションはDOMツリー全体を検索するのではなく、属性の変更のみを監視する必要があります。)

let mutationObservers = [];

function log(...msg) {
  console.log("on-discourse", ...msg);
}

function observeMainOutlet() {
  log('Observing main outlet');
  // ミューテーションを監視するノードを選択します
  const targetNode = document.getElementById("topic");
 
  if (targetNode) {
    // オブザーバーのオプション(どのミューテーションを監視するか)
    const config = { attributes: true };
    
   // childListが変更されたときにリセットするオブザーバーインスタンスを作成します
    const observer = new MutationObserver(function(mutations) {
      let reset = false;
      mutations.forEach(function(mutation) {
	if (mutation.type === 'attributes') {
	  log('Found main-outlet mutation, running callbacks');
	    mutationObservers.forEach( (s,i) => {
		try {
		    log(`Running div callback ${i+1}`);		    
		    s();
		    log(`Finished div callback ${i+1}`);		    
		} catch( err ) {
		    log(`Div callback error (${i+1})`, err );		    	      
		}
	 });
       }
      });
    });
    
    // 設定されたミューテーションを監視するためにターゲットノードの監視を開始します
    observer.observe(targetNode, config);
    
    // 後で監視を停止できます
    // observer.disconnect();
    log('Done with outlet observer');
  } else {
    console.error('on-discourse FATAL ERROR: Unable to find main-outlet');
  }
}

window.addDiscourseDivMutationObserver = (cb) => {
    log('Adding on-discourse div mutation callback');  
    mutationObservers.push(cb);
    log('Added on-discourse div mutation callback');    
}

window.addEventListener("load", () => {
    log('Setting up topic observer');
    if (mutationObservers.length > 0) {
	observeMainOutlet();
    }
    log('Created topic observer');  
});

log('Completed setup of on-discourse.js');

コードをヘッドではなくイニシャライザに配置したいのだと思います。開発者ガイドを参照して、JSを別ファイルに配置する方法を確認してください。

「いいね!」 1

返信ありがとうございます!

それが私が混乱している理由の一部だと思います。独自のJSを追加してDiscourseを拡張することに関する参照を見つけるのに苦労しています。

「Discourse developer guide」でduckduckgo検索をすると、最初のリンクはgithubリポジトリへのリンクになります。

次に「Discourse Advanced Developer Install Guide」というリンクが表示されます。このガイドはRailsを開発用に設定する方法を説明するものですが、カスタムJSをインストールする方法に関するリンクは、私の知る限りありません。Rails時代の経験から、複雑なビルドプロセスは避けたいと思っています。このJS拡張コードを単独で開発し、その後、サイトにタグを挿入したいのです。そのため、ローカルでRails環境をセットアップしてビルドする必要があるのは避けたいです。その有用性を見落としているのかもしれませんか?しかし、単にタグがいくつか含まれるテーマを使用してDockerコンテナを更新できるのは非常に気に入っています。

次のリンクは「Beginner’s guide to developing Discourse Themes」ですが、これはテーマの開発に関するもので、私が求めているものではないですよね?

Discourse APIへのリンクが表示されますが、明らかに私が求めているものではありません。

「discourse javascript initializer」で検索すると、5年前のこのリンクが見つかります:Execute JavaScript code from a plugin once after load しかし、それはRailsにプラグインするようなもので、もっと簡単な方法があるはずだと感じています。また、このスレッドも解決されていないようです。

「discourse javascript initializer」に関する別のリンクは、私がJSをインストールしている方法を示唆していますが、ページの内容が変更されるたびに(フルページリフレッシュまたはXHR「turbolinks」のようなリクエストのいずれかによって)確実に実行する方法についての提案はありません:https://stackoverflow.com/questions/48611621/how-do-i-add-an-external-javascript-file-into-discourse

このディスカッションを確認すべきでしょうか? A versioned API for client side plugins

あるいは、こちらでしょうか?一見したところ、構文が理解できません(それらの注釈はJSのように見えませんが、それらはRailsの慣例ですか?)ので、これが求めているものかどうかわかりません:Using Plugin Outlet Connectors from a Theme or Plugin

それほど単純ではありません。

Discourse は EmberJS アプリです。

理想的には、JavaScript 拡張機能は、Theme Component (または Plugin)、適切な場合は Discourse JavaScript API、および plugin outlet を使用して EmberJS フレームワークで記述する必要があります。

アドホックな外部スクリプトを使用する際に遭遇する主な問題は、適切なタイミングでスクリプトを実行させることです。

これを確実にするには、Component のアクション (insert または update でトリガーされるようにすることができます) にリンクする必要があります。

「いいね!」 1

それは良い確認ですね。しかし、言わざるを得ないのは、私のミューテーションオブザーバーコードには本当に満足しているということです。ページのコンテンツが変更されたときにそれを正しく検出し、カスタムJSコードを実行できるようにします。それは美しく機能しており、Emberを学ぶ必要も、RoRアプリを一切変更する必要もありませんでした。今のところ、このソリューションには本当に満足しています。コメントをすべてありがとうございました。

「いいね!」 1

popstate イベントオブザーバーを試してみましたが、それが機能していれば、コードは 20 行ではなく 5 行で済んだはずです。しかし、クリックしてもイベントは発生しないようです。戻るボタンや進むボタンを使用すると、イベントが発生するのがわかります。popstate についてはまだよく理解できていないため、今のところは div ミューテーションオブザーバーを使用しています。

ここで愚かなことをしていることに気づきました。テーマを変更し、headにコードを追加しています。別のテーマに切り替えると、それらの変更は失われます。正しい方法は、プラグインを使用してコードを追加することです。ここではテンプレートを使用しました:https://github.com/discourse/discourse-plugin-skeleton/。次に、次のようにJavaScriptコードを追加しました。

export default {
  name: 'alert',
    initialize() {
        const scripts = [
            'https://files.extrastatic.dev/community/on-discourse.js',
            'https://files.extrastatic.dev/community/index.js'
        ];
        scripts.forEach( s => {
            const script = document.createElement('script');
            script.setAttribute('src', s);
            document.head.appendChild(script);
        });

        const link = document.createElement('link');
        link.setAttribute('rel', "stylesheet");
        link.setAttribute('type', "text/css");
        link.setAttribute('href', "https://files.extrastatic.dev/community/index.css");
        document.head.appendChild(link);
    }
};

次に、./launcher rebuild appを実行し、プラグインを有効にしました。
最後に、それらのスクリプトの読み込みを許可するために、設定にCSPポリシーを追加する必要がありました。管理 → 設定 → セキュリティに移動し、「content security policy script src」にfiles.extrastatic.devを追加して適用をクリックしました。

それは真実ではありません。

APIを変更していない場合(たとえば、100%JavaScriptを使用している場合)、展開や切り替えがより面倒なプラグインを使用する必要はありません。

JavaScriptの変更または追加のみを行っている場合は、テーマコンポーネントで十分です。

「いいね!」 2

OK。しかし、テーマを切り替える(またはユーザーに独自のテーマを選択させる)場合、各テーマが 1) 編集可能で、新しいヘッドタグを追加できるようにする必要があるという問題に直面しませんか?さらに悪いことに 2) すべてのテーマでコードを維持する必要がありますか?

さまざまなテーマを使い始めたとき、テーマを編集するには GitHub リポジトリを変更する必要があることを示すものもありました。これは非常に面倒で柔軟性に欠けるように思えます。しかし、各テーマでスクリプトタグを維持することは非常に間違いやすく、さらに大きな問題です。

何を見落としていますか?テーマのみを使用してこれらの問題を解決する方法はありますか?

「いいね!」 1

この場合、テーマコンポーネントにして、そのコンポーネントをすべてのテーマに追加します。(これは追加の複雑さであることを認めます)。

サイトをプロフェッショナルに運営したいのであれば、すべてのコードをGitHubに置くことは確かに非常に良い考えです。

しかし、アイデアを試しているだけの初期段階では、もちろん、好きなように「ローカル」でいじることができます。

コードの変更が落ち着いたら、ソース管理に入れるべきですが、早ければ早いほど良いと主張します。

GitHubと迅速な進化を組み合わせる方法は、これと組み合わせることです。

そして、テストテーマのテストコンポーネントにデプロイします…しかし、これは本当に面白くなってきました…

これにより、オンザフライでデプロイできるようになり、満足したら変更をgitリポジトリに保存できます。

「いいね!」 1

GitHub - discourse/discourse-theme-skeleton: Template for Discourse themes を確認してください

これが必要なものです How do you force a script to refire on every page load in Discourse? - #5 by simon

<script type="text/discourse-plugin" version="0.8">
    api.onPageChange(() =>{
        // コード
    });
</script>
「いいね!」 3

素晴らしい @RGJ、完璧に見えます!ありがとうございます!

「いいね!」 2

こんにちは、@RGJさん。これをどうやるのか調べてみましたが、正直混乱しています。プラグインAPIは数年で少し変わったようで、現在のものをどうやって取得するのか、または例を見るのか分かりません。

あなたのコードはスクリプトタグで囲まれているため、どこかのHTMLページに入るように見えます。私はJSファイル自体であるこのようなコードを使用していました。

https://extrastatic.dev/publicdo/publicdo-discourse-plugin/-/blob/main/assets/javascripts/discourse/initializers/kanji.js?ref_type=heads

提供されたコードを使用するようにプラグインをどうやって変更すればよいですか?このコードはプラグインに入れるものですか、それともテーマに入れるのですか、あるいはページに追加するのですか?

このようなコードを使ってみました。

export default {
  name: 'publicdo',
    initialize() {
     withPluginApi('0.1', api => {
                api.onPageChange(() => {
                   console.log('ここに私のコードを実行します。');
                });
      });
    }
}

しかし、これはUncaught (in promise) ReferenceError: withPluginApi is not definedで失敗するため、一般的にロードされるJSが受け取るものではないことは明らかです。

ただ単に

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

する必要があります。

「いいね!」 3

Theme component にリンクされているソースを参照することをお勧めします(エコシステムのほとんどはオープンソースです。自由に利用してください!)。

インポートが必要で一般的であること(そして api.onPageChange やその他の便利な api 関数の使用も同様であること)に気づくでしょう。

「いいね!」 4

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.