nodebb-plugin-katex2 1.0.5 → 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 +209 -78
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,29 +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;
|
|
219
291
|
}
|
|
220
292
|
}
|
|
221
293
|
|
|
222
294
|
/**
|
|
223
|
-
*
|
|
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
|
+
});
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Рендеринг всех постов на странице
|
|
224
312
|
*/
|
|
225
313
|
async function renderAllPosts() {
|
|
226
|
-
console.log("[KaTeX] Try rendering");
|
|
227
314
|
// Проверяем наличие формул
|
|
228
315
|
if (!hasMathContent()) {
|
|
229
|
-
console.log("[KaTeX] No math content found
|
|
316
|
+
console.log("[KaTeX] No math content found");
|
|
230
317
|
return;
|
|
231
318
|
}
|
|
232
319
|
|
|
@@ -234,82 +321,126 @@
|
|
|
234
321
|
// Загружаем KaTeX если нужно
|
|
235
322
|
await loadKaTeX();
|
|
236
323
|
|
|
324
|
+
let renderCount = 0;
|
|
325
|
+
|
|
237
326
|
// Рендерим посты
|
|
238
327
|
const posts = document.querySelectorAll('[component="post/content"]');
|
|
239
328
|
posts.forEach(function (post) {
|
|
240
|
-
renderMath(post)
|
|
329
|
+
if (renderMath(post)) {
|
|
330
|
+
renderCount++;
|
|
331
|
+
}
|
|
241
332
|
});
|
|
242
333
|
|
|
243
|
-
// Рендерим превью
|
|
334
|
+
// Рендерим превью редактора
|
|
244
335
|
const preview = document.querySelector(".preview-container");
|
|
245
336
|
if (preview) {
|
|
246
337
|
renderMath(preview);
|
|
247
338
|
}
|
|
248
339
|
|
|
249
|
-
// Рендерим заголовки
|
|
340
|
+
// Рендерим заголовки тем
|
|
250
341
|
const titles = document.querySelectorAll('[component="topic/title"]');
|
|
251
342
|
titles.forEach(function (title) {
|
|
252
343
|
renderMath(title);
|
|
253
344
|
});
|
|
254
345
|
|
|
255
|
-
|
|
346
|
+
if (renderCount > 0) {
|
|
347
|
+
console.log("[KaTeX] Rendered " + renderCount + " new posts");
|
|
348
|
+
}
|
|
256
349
|
} catch (err) {
|
|
257
|
-
console.error("[KaTeX] Failed to render:", err);
|
|
350
|
+
console.error("[KaTeX] Failed to render posts:", err);
|
|
258
351
|
}
|
|
259
352
|
}
|
|
260
353
|
|
|
261
354
|
/**
|
|
262
|
-
*
|
|
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
|
+
* Инициализация плагина
|
|
263
376
|
*/
|
|
264
377
|
function init() {
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
// События NodeBB
|
|
271
|
-
|
|
272
|
-
//
|
|
273
|
-
window.
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
}, 500);
|
|
378
|
+
console.log("[KaTeX] Initializing plugin");
|
|
379
|
+
|
|
380
|
+
// Первоначальный рендеринг
|
|
381
|
+
debouncedRender();
|
|
382
|
+
|
|
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();
|
|
277
389
|
});
|
|
278
390
|
|
|
279
|
-
//
|
|
280
|
-
window.
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
}, 500);
|
|
391
|
+
// Загрузка новых постов (скролл, пагинация)
|
|
392
|
+
$(window).on("action:posts.loaded", function (event, data) {
|
|
393
|
+
console.log("[KaTeX] New posts loaded:", data.posts.length);
|
|
394
|
+
debouncedRender();
|
|
284
395
|
});
|
|
285
396
|
|
|
286
|
-
//
|
|
287
|
-
window.
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
}, 800);
|
|
397
|
+
// Загрузка темы
|
|
398
|
+
$(window).on("action:topic.loaded", function (event, data) {
|
|
399
|
+
console.log("[KaTeX] Topic loaded:", data.tid);
|
|
400
|
+
debouncedRender();
|
|
291
401
|
});
|
|
292
402
|
|
|
293
|
-
//
|
|
294
|
-
window.
|
|
403
|
+
// Обновление превью в редакторе
|
|
404
|
+
$(window).on("action:composer.preview", function () {
|
|
295
405
|
if (hasMathContent()) {
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
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
|
+
});
|
|
305
417
|
}
|
|
306
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
|
+
});
|
|
307
430
|
}
|
|
308
431
|
|
|
309
|
-
//
|
|
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
|
+
// Запуск после полной загрузки
|
|
310
440
|
if (document.readyState === "loading") {
|
|
311
441
|
document.addEventListener("DOMContentLoaded", init);
|
|
312
442
|
} else {
|
|
443
|
+
// DOM уже загружен
|
|
313
444
|
init();
|
|
314
445
|
}
|
|
315
446
|
})();
|