nodebb-plugin-katex2 1.0.4 → 1.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/static/js/render.js +207 -73
package/package.json
CHANGED
package/static/js/render.js
CHANGED
|
@@ -1,23 +1,53 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Ленивая загрузка и рендеринг KaTeX
|
|
3
|
-
*
|
|
2
|
+
* Ленивая загрузка и рендеринг KaTeX для NodeBB
|
|
3
|
+
* Версия 2.0 - исправлены проблемы с SPA-навигацией
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
(function () {
|
|
7
7
|
"use strict";
|
|
8
8
|
|
|
9
|
-
console.log("
|
|
9
|
+
console.log("[KaTeX] Plugin initialized");
|
|
10
10
|
|
|
11
|
-
//
|
|
11
|
+
// === Состояние загрузки ===
|
|
12
12
|
let katexLoaded = false;
|
|
13
13
|
let katexLoading = false;
|
|
14
14
|
let loadCallbacks = [];
|
|
15
15
|
|
|
16
|
+
// === Debounce таймеры ===
|
|
17
|
+
let renderTimer = null;
|
|
18
|
+
const RENDER_DELAY = 100; // Задержка перед рендерингом (мс)
|
|
19
|
+
|
|
20
|
+
// === Регулярное выражение для быстрой проверки ===
|
|
21
|
+
const MATH_PATTERN = /\$\$|\\\[|\\\(/;
|
|
22
|
+
|
|
16
23
|
/**
|
|
17
|
-
*
|
|
18
|
-
*
|
|
24
|
+
* Debounce функция - откладывает выполнение
|
|
25
|
+
* @param {Function} func - Функция для выполнения
|
|
26
|
+
* @param {number} wait - Задержка в мс
|
|
19
27
|
*/
|
|
20
|
-
|
|
28
|
+
function debounce(func, wait) {
|
|
29
|
+
return function executedFunction() {
|
|
30
|
+
const context = this;
|
|
31
|
+
const args = arguments;
|
|
32
|
+
|
|
33
|
+
clearTimeout(renderTimer);
|
|
34
|
+
renderTimer = setTimeout(function () {
|
|
35
|
+
func.apply(context, args);
|
|
36
|
+
}, wait);
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Проверка наличия формул в элементе
|
|
42
|
+
* @param {HTMLElement} element
|
|
43
|
+
* @returns {boolean}
|
|
44
|
+
*/
|
|
45
|
+
function hasFormulas(element) {
|
|
46
|
+
if (!element) return false;
|
|
47
|
+
|
|
48
|
+
// Быстрая проверка через textContent
|
|
49
|
+
return MATH_PATTERN.test(element.textContent);
|
|
50
|
+
}
|
|
21
51
|
|
|
22
52
|
/**
|
|
23
53
|
* Проверка наличия формул на странице
|
|
@@ -25,24 +55,25 @@
|
|
|
25
55
|
*/
|
|
26
56
|
function hasMathContent() {
|
|
27
57
|
// Проверяем в постах
|
|
28
|
-
const posts = document.querySelectorAll(
|
|
29
|
-
|
|
58
|
+
const posts = document.querySelectorAll(
|
|
59
|
+
'.posts-container [component="post/content"]',
|
|
60
|
+
);
|
|
30
61
|
for (let i = 0; i < posts.length; i++) {
|
|
31
|
-
if (
|
|
62
|
+
if (hasFormulas(posts[i])) {
|
|
32
63
|
return true;
|
|
33
64
|
}
|
|
34
65
|
}
|
|
35
66
|
|
|
36
67
|
// Проверяем в превью редактора
|
|
37
68
|
const preview = document.querySelector(".preview-container");
|
|
38
|
-
if (
|
|
69
|
+
if (hasFormulas(preview)) {
|
|
39
70
|
return true;
|
|
40
71
|
}
|
|
41
72
|
|
|
42
|
-
// Проверяем в заголовках
|
|
73
|
+
// Проверяем в заголовках тем
|
|
43
74
|
const titles = document.querySelectorAll('[component="topic/title"]');
|
|
44
75
|
for (let i = 0; i < titles.length; i++) {
|
|
45
|
-
if (
|
|
76
|
+
if (hasFormulas(titles[i])) {
|
|
46
77
|
return true;
|
|
47
78
|
}
|
|
48
79
|
}
|
|
@@ -51,8 +82,8 @@
|
|
|
51
82
|
}
|
|
52
83
|
|
|
53
84
|
/**
|
|
54
|
-
*
|
|
55
|
-
* @param {string} href - Путь к CSS
|
|
85
|
+
* Загрузка CSS файла
|
|
86
|
+
* @param {string} href - Путь к CSS
|
|
56
87
|
* @returns {Promise}
|
|
57
88
|
*/
|
|
58
89
|
function loadCSS(href) {
|
|
@@ -74,16 +105,24 @@
|
|
|
74
105
|
}
|
|
75
106
|
|
|
76
107
|
/**
|
|
77
|
-
*
|
|
78
|
-
* @param {string} src - Путь к JS
|
|
108
|
+
* Загрузка JavaScript файла
|
|
109
|
+
* @param {string} src - Путь к JS
|
|
79
110
|
* @returns {Promise}
|
|
80
111
|
*/
|
|
81
112
|
function loadScript(src) {
|
|
82
113
|
return new Promise(function (resolve, reject) {
|
|
83
|
-
//
|
|
114
|
+
// Проверяем глобальный объект
|
|
115
|
+
if (window.renderMathInElement && window.katex) {
|
|
116
|
+
resolve();
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Проверяем, не загружен ли уже скрипт
|
|
84
121
|
const existing = document.querySelector('script[src="' + src + '"]');
|
|
85
122
|
if (existing) {
|
|
86
|
-
|
|
123
|
+
// Ждем, пока загрузится
|
|
124
|
+
existing.onload = resolve;
|
|
125
|
+
existing.onerror = reject;
|
|
87
126
|
return;
|
|
88
127
|
}
|
|
89
128
|
|
|
@@ -96,12 +135,12 @@
|
|
|
96
135
|
}
|
|
97
136
|
|
|
98
137
|
/**
|
|
99
|
-
* Загрузка библиотеки KaTeX
|
|
138
|
+
* Загрузка библиотеки KaTeX
|
|
100
139
|
* @returns {Promise}
|
|
101
140
|
*/
|
|
102
141
|
async function loadKaTeX() {
|
|
103
142
|
// Если уже загружено
|
|
104
|
-
if (katexLoaded) {
|
|
143
|
+
if (katexLoaded && window.renderMathInElement && window.katex) {
|
|
105
144
|
return;
|
|
106
145
|
}
|
|
107
146
|
|
|
@@ -116,19 +155,21 @@
|
|
|
116
155
|
katexLoading = true;
|
|
117
156
|
|
|
118
157
|
try {
|
|
119
|
-
//
|
|
120
|
-
// const basePath = "/plugins/nodebb-plugin-katex/node_modules/katex/dist/";
|
|
121
|
-
// Находим правильный путь
|
|
122
|
-
// Путь к файлам через modules
|
|
158
|
+
// Правильный путь согласно plugin.json staticDirs
|
|
123
159
|
const basePath = "/assets/plugins/nodebb-plugin-katex2/katex/";
|
|
124
160
|
|
|
125
|
-
// Параллельная загрузка всех
|
|
161
|
+
// Параллельная загрузка всех ресурсов
|
|
126
162
|
await Promise.all([
|
|
127
163
|
loadCSS(basePath + "katex.min.css"),
|
|
128
164
|
loadScript(basePath + "katex.min.js"),
|
|
129
165
|
loadScript(basePath + "contrib/auto-render.min.js"),
|
|
130
166
|
]);
|
|
131
167
|
|
|
168
|
+
// Проверяем, что библиотека действительно загрузилась
|
|
169
|
+
if (!window.renderMathInElement || !window.katex) {
|
|
170
|
+
throw new Error("KaTeX library not available after loading");
|
|
171
|
+
}
|
|
172
|
+
|
|
132
173
|
katexLoaded = true;
|
|
133
174
|
katexLoading = false;
|
|
134
175
|
console.log("[KaTeX] Library loaded successfully");
|
|
@@ -140,13 +181,14 @@
|
|
|
140
181
|
loadCallbacks = [];
|
|
141
182
|
} catch (err) {
|
|
142
183
|
katexLoading = false;
|
|
184
|
+
katexLoaded = false;
|
|
143
185
|
console.error("[KaTeX] Failed to load library:", err);
|
|
144
186
|
throw err;
|
|
145
187
|
}
|
|
146
188
|
}
|
|
147
189
|
|
|
148
190
|
/**
|
|
149
|
-
* Конфигурация KaTeX
|
|
191
|
+
* Конфигурация рендеринга KaTeX
|
|
150
192
|
*/
|
|
151
193
|
const KATEX_CONFIG = {
|
|
152
194
|
delimiters: [
|
|
@@ -158,14 +200,18 @@
|
|
|
158
200
|
errorColor: "#cc0000",
|
|
159
201
|
strict: false,
|
|
160
202
|
trust: false,
|
|
203
|
+
// Игнорируем уже отрендеренные элементы
|
|
204
|
+
ignoredTags: ["script", "noscript", "style", "textarea", "pre", "code"],
|
|
205
|
+
ignoredClasses: ["katex", "katex-display", "katex-rendered"],
|
|
161
206
|
};
|
|
162
207
|
|
|
163
208
|
/**
|
|
164
209
|
* Очистка HTML-тегов внутри формул
|
|
165
|
-
* Markdown может добавить <br>, <p> и другие теги
|
|
166
210
|
* @param {HTMLElement} element
|
|
167
211
|
*/
|
|
168
212
|
function cleanMathElements(element) {
|
|
213
|
+
if (!element) return;
|
|
214
|
+
|
|
169
215
|
const walker = document.createTreeWalker(
|
|
170
216
|
element,
|
|
171
217
|
NodeFilter.SHOW_TEXT,
|
|
@@ -176,7 +222,7 @@
|
|
|
176
222
|
const nodesToProcess = [];
|
|
177
223
|
let node;
|
|
178
224
|
|
|
179
|
-
// Собираем
|
|
225
|
+
// Собираем текстовые узлы с формулами
|
|
180
226
|
while ((node = walker.nextNode())) {
|
|
181
227
|
const text = node.textContent;
|
|
182
228
|
if (MATH_PATTERN.test(text)) {
|
|
@@ -184,13 +230,20 @@
|
|
|
184
230
|
}
|
|
185
231
|
}
|
|
186
232
|
|
|
187
|
-
// Очищаем
|
|
233
|
+
// Очищаем от лишних HTML-тегов
|
|
188
234
|
nodesToProcess.forEach(function (textNode) {
|
|
189
235
|
let parent = textNode.parentNode;
|
|
190
236
|
|
|
237
|
+
// Пропускаем уже отрендеренные элементы
|
|
238
|
+
if (parent && parent.classList && parent.classList.contains("katex")) {
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
|
|
191
242
|
if (
|
|
192
243
|
parent &&
|
|
193
|
-
(parent.innerHTML.includes("<br") ||
|
|
244
|
+
(parent.innerHTML.includes("<br") ||
|
|
245
|
+
parent.innerHTML.includes("<p") ||
|
|
246
|
+
parent.innerHTML.includes("<span"))
|
|
194
247
|
) {
|
|
195
248
|
const cleanText = parent.textContent;
|
|
196
249
|
|
|
@@ -204,28 +257,63 @@
|
|
|
204
257
|
/**
|
|
205
258
|
* Рендеринг формул в элементе
|
|
206
259
|
* @param {HTMLElement} element
|
|
260
|
+
* @returns {boolean} - true если что-то отрендерено
|
|
207
261
|
*/
|
|
208
262
|
function renderMath(element) {
|
|
209
|
-
if (!element)
|
|
263
|
+
if (!element || !window.renderMathInElement) {
|
|
264
|
+
return false;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Проверяем наличие формул
|
|
268
|
+
if (!hasFormulas(element)) {
|
|
269
|
+
return false;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Проверяем, не рендерили ли уже этот элемент
|
|
273
|
+
if (element.hasAttribute("data-katex-rendered")) {
|
|
274
|
+
return false;
|
|
275
|
+
}
|
|
210
276
|
|
|
211
277
|
try {
|
|
212
|
-
//
|
|
278
|
+
// Очищаем от HTML-тегов
|
|
213
279
|
cleanMathElements(element);
|
|
214
280
|
|
|
215
|
-
//
|
|
216
|
-
renderMathInElement(element, KATEX_CONFIG);
|
|
281
|
+
// Рендерим формулы
|
|
282
|
+
window.renderMathInElement(element, KATEX_CONFIG);
|
|
283
|
+
|
|
284
|
+
// Помечаем как отрендеренный
|
|
285
|
+
element.setAttribute("data-katex-rendered", "true");
|
|
286
|
+
|
|
287
|
+
return true;
|
|
217
288
|
} catch (err) {
|
|
218
289
|
console.error("[KaTeX] Render error:", err);
|
|
290
|
+
return false;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Сброс флага рендеринга для обновленного контента
|
|
296
|
+
* @param {HTMLElement} element
|
|
297
|
+
*/
|
|
298
|
+
function markForRerender(element) {
|
|
299
|
+
if (element && element.hasAttribute("data-katex-rendered")) {
|
|
300
|
+
element.removeAttribute("data-katex-rendered");
|
|
301
|
+
|
|
302
|
+
// Также сбрасываем для вложенных элементов
|
|
303
|
+
const rendered = element.querySelectorAll("[data-katex-rendered]");
|
|
304
|
+
rendered.forEach(function (el) {
|
|
305
|
+
el.removeAttribute("data-katex-rendered");
|
|
306
|
+
});
|
|
219
307
|
}
|
|
220
308
|
}
|
|
221
309
|
|
|
222
310
|
/**
|
|
223
|
-
* Рендеринг всех постов
|
|
311
|
+
* Рендеринг всех постов на странице
|
|
224
312
|
*/
|
|
225
313
|
async function renderAllPosts() {
|
|
226
314
|
// Проверяем наличие формул
|
|
227
315
|
if (!hasMathContent()) {
|
|
228
|
-
console.log("[KaTeX] No math content found
|
|
316
|
+
console.log("[KaTeX] No math content found");
|
|
229
317
|
return;
|
|
230
318
|
}
|
|
231
319
|
|
|
@@ -233,80 +321,126 @@
|
|
|
233
321
|
// Загружаем KaTeX если нужно
|
|
234
322
|
await loadKaTeX();
|
|
235
323
|
|
|
324
|
+
let renderCount = 0;
|
|
325
|
+
|
|
236
326
|
// Рендерим посты
|
|
237
327
|
const posts = document.querySelectorAll('[component="post/content"]');
|
|
238
328
|
posts.forEach(function (post) {
|
|
239
|
-
renderMath(post)
|
|
329
|
+
if (renderMath(post)) {
|
|
330
|
+
renderCount++;
|
|
331
|
+
}
|
|
240
332
|
});
|
|
241
333
|
|
|
242
|
-
// Рендерим превью
|
|
334
|
+
// Рендерим превью редактора
|
|
243
335
|
const preview = document.querySelector(".preview-container");
|
|
244
336
|
if (preview) {
|
|
245
337
|
renderMath(preview);
|
|
246
338
|
}
|
|
247
339
|
|
|
248
|
-
// Рендерим заголовки
|
|
340
|
+
// Рендерим заголовки тем
|
|
249
341
|
const titles = document.querySelectorAll('[component="topic/title"]');
|
|
250
342
|
titles.forEach(function (title) {
|
|
251
343
|
renderMath(title);
|
|
252
344
|
});
|
|
253
345
|
|
|
254
|
-
|
|
346
|
+
if (renderCount > 0) {
|
|
347
|
+
console.log("[KaTeX] Rendered " + renderCount + " new posts");
|
|
348
|
+
}
|
|
255
349
|
} catch (err) {
|
|
256
|
-
console.error("[KaTeX] Failed to render:", err);
|
|
350
|
+
console.error("[KaTeX] Failed to render posts:", err);
|
|
257
351
|
}
|
|
258
352
|
}
|
|
259
353
|
|
|
260
354
|
/**
|
|
261
|
-
*
|
|
355
|
+
* Debounced версия рендеринга
|
|
356
|
+
*/
|
|
357
|
+
const debouncedRender = debounce(renderAllPosts, RENDER_DELAY);
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Обработчик для событий навигации
|
|
361
|
+
* Критично для SPA-режима NodeBB
|
|
362
|
+
*/
|
|
363
|
+
function handleNavigation() {
|
|
364
|
+
// Сбрасываем все флаги рендеринга при навигации
|
|
365
|
+
const allPosts = document.querySelectorAll("[data-katex-rendered]");
|
|
366
|
+
allPosts.forEach(function (post) {
|
|
367
|
+
post.removeAttribute("data-katex-rendered");
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
// Запускаем рендеринг с задержкой
|
|
371
|
+
debouncedRender();
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* Инициализация плагина
|
|
262
376
|
*/
|
|
263
377
|
function init() {
|
|
264
|
-
|
|
265
|
-
renderAllPosts();
|
|
378
|
+
console.log("[KaTeX] Initializing plugin");
|
|
266
379
|
|
|
267
|
-
//
|
|
380
|
+
// Первоначальный рендеринг
|
|
381
|
+
debouncedRender();
|
|
268
382
|
|
|
269
|
-
//
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
383
|
+
// === События NodeBB ===
|
|
384
|
+
|
|
385
|
+
// Навигация в SPA (КРИТИЧНО!)
|
|
386
|
+
$(window).on("action:ajaxify.end", function (event, data) {
|
|
387
|
+
console.log("[KaTeX] Page navigation detected:", data.url);
|
|
388
|
+
handleNavigation();
|
|
274
389
|
});
|
|
275
390
|
|
|
276
|
-
//
|
|
277
|
-
window.
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
}, 50);
|
|
391
|
+
// Загрузка новых постов (скролл, пагинация)
|
|
392
|
+
$(window).on("action:posts.loaded", function (event, data) {
|
|
393
|
+
console.log("[KaTeX] New posts loaded:", data.posts.length);
|
|
394
|
+
debouncedRender();
|
|
281
395
|
});
|
|
282
396
|
|
|
283
|
-
//
|
|
284
|
-
window.
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
}, 100);
|
|
397
|
+
// Загрузка темы
|
|
398
|
+
$(window).on("action:topic.loaded", function (event, data) {
|
|
399
|
+
console.log("[KaTeX] Topic loaded:", data.tid);
|
|
400
|
+
debouncedRender();
|
|
288
401
|
});
|
|
289
402
|
|
|
290
|
-
//
|
|
291
|
-
window.
|
|
403
|
+
// Обновление превью в редакторе
|
|
404
|
+
$(window).on("action:composer.preview", function () {
|
|
292
405
|
if (hasMathContent()) {
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
406
|
+
loadKaTeX()
|
|
407
|
+
.then(function () {
|
|
408
|
+
const preview = document.querySelector(".preview-container");
|
|
409
|
+
if (preview) {
|
|
410
|
+
markForRerender(preview);
|
|
411
|
+
renderMath(preview);
|
|
412
|
+
}
|
|
413
|
+
})
|
|
414
|
+
.catch(function (err) {
|
|
415
|
+
console.error("[KaTeX] Preview render error:", err);
|
|
416
|
+
});
|
|
302
417
|
}
|
|
303
418
|
});
|
|
419
|
+
|
|
420
|
+
// Редактирование поста
|
|
421
|
+
$(window).on("action:posts.edited", function (event, data) {
|
|
422
|
+
console.log("[KaTeX] Post edited:", data.post.pid);
|
|
423
|
+
debouncedRender();
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
// Создание нового поста
|
|
427
|
+
$(window).on("action:posts.loaded", function () {
|
|
428
|
+
debouncedRender();
|
|
429
|
+
});
|
|
304
430
|
}
|
|
305
431
|
|
|
306
|
-
//
|
|
432
|
+
// === Точка входа ===
|
|
433
|
+
|
|
434
|
+
// NodeBB использует jQuery, поверяем его наличие
|
|
435
|
+
// if (typeof $ === "undefined" || typeof jQuery === "undefined") {
|
|
436
|
+
// console.error("[KaTeX] jQuery not found! Plugin may not work correctly.");
|
|
437
|
+
// }
|
|
438
|
+
|
|
439
|
+
// Запуск после полной загрузки
|
|
307
440
|
if (document.readyState === "loading") {
|
|
308
441
|
document.addEventListener("DOMContentLoaded", init);
|
|
309
442
|
} else {
|
|
443
|
+
// DOM уже загружен
|
|
310
444
|
init();
|
|
311
445
|
}
|
|
312
446
|
})();
|