Я пишу приложение на Svelte, которое должно устанавливаться на каждой странице. Я разобрался, как добавить свой JS-файл в секцию head и заставить его загружаться при первой загрузке страницы. Однако, экспериментируя, я понял, что Discourse загружает новый контент через XHR и заменяет определённые разделы, поэтому моё приложение не переинициализировалось при загрузке новой страницы.
Я пробовал разные способы получить уведомление об изменении страницы, но, похоже, в Ember нет соответствующих хуков, и мне не удалось найти какие-либо пользовательские события, на которые можно было бы подписаться.
Один из вариантов — добавить наблюдателя за изменениями (MutationObserver) к DOM и отслеживать изменения. Я обнаружил, что пересоздаётся именно div с id “#topic” (и его атрибуты меняются, так что можно просто отслеживать изменения атрибутов). Я настроил MutationObserver именно там (MutationObserver — это новый и производительный способ отслеживать изменения в DOM). Принцип работы следующий: отслеживать изменения, и когда они происходят, выполнять обратный вызов для перезапуска моего приложения Svelte на этой странице.
Мне нравится этот подход, и я хотел бы получить обратную связь.
Один вопрос: не лучше ли отслеживать изменения URL? Это более разумная идея — зарегистрировать слушатель для события popstate?
Чтобы использовать это, добавьте следующее в ваш theme/head:
<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 для #main-outlet)
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');