nodebb-plugin-katex2 1.0.0

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/.editorconfig ADDED
@@ -0,0 +1,8 @@
1
+ root = true
2
+
3
+ [{*.js, *.css, *.tpl, *.json}]
4
+ indent_style = tab
5
+ end_of_line = lf
6
+ charset = utf-8
7
+ trim_trailing_whitespace = true
8
+ insert_final_newline = false
package/.eslintrc.json ADDED
@@ -0,0 +1,8 @@
1
+ {
2
+ "extends": "airbnb-base",
3
+ "env": {
4
+ "node": true,
5
+ "browser": true,
6
+ "es6": true
7
+ }
8
+ }
package/LICENSE ADDED
@@ -0,0 +1,26 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 Benjamin Abel.
4
+
5
+ This software also uses portions of the katex auto-render extension from the
6
+ Katex project, which is MIT licensed with the following copyright:
7
+
8
+ Copyright (c) 2015 Khan Academy
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in
18
+ all copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
26
+ THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,277 @@
1
+ # KaTeX Math Renderer for NodeBB
2
+
3
+ [English](#english) | [Русский](#russian)
4
+
5
+ ---
6
+
7
+ <a name="english"></a>
8
+ ## English
9
+
10
+ Modern KaTeX math rendering plugin for NodeBB 4+.
11
+
12
+ ### Features
13
+
14
+ - ✨ **Lazy loading** - KaTeX loads only when math expressions are detected on the page
15
+ - 📐 **Display mode syntax** - Currently supports `$$...$$` delimiters
16
+ - 🎨 **Server-side ready** - Minimal server logic, client-side rendering
17
+ - 🌐 **CDN delivery** - Uses bundled KaTeX for fast delivery
18
+ - 🚀 **Parser hook integration** - Seamlessly integrates with NodeBB's parser
19
+ - 📱 **Responsive design** - Works on mobile and desktop devices
20
+ - 🌙 **Dark theme support** - Automatically adapts to color schemes
21
+
22
+ ### Installation
23
+
24
+ #### Via npm (recommended)
25
+
26
+ ```bash
27
+ cd /path/to/nodebb
28
+ npm install nodebb-plugin-katex2
29
+ ```
30
+
31
+ #### Manual installation
32
+
33
+ 1. Clone the plugin into `/nodebb/node_modules/`
34
+ 2. Activate the plugin in NodeBB admin panel
35
+ 3. Restart NodeBB
36
+
37
+ ```bash
38
+ cd /path/to/nodebb/node_modules
39
+ git clone https://github.com/mysteren/nodebb-plugin-katex.git
40
+ cd ../..
41
+ npm install ./node_modules/nodebb-plugin-katex
42
+ ./nodebb restart
43
+ ```
44
+
45
+ ### Usage
46
+
47
+ #### Display mode (separate line)
48
+
49
+ Currently, only the `$$...$$` syntax is fully supported and tested:
50
+
51
+ ```
52
+ $$
53
+ \frac{-b \pm \sqrt{b^2 - 4ac}}{2a}
54
+ $$
55
+ ```
56
+
57
+ Example with integrals:
58
+
59
+ ```
60
+ $$
61
+ \int_{0}^{\infty} e^{-x^2} dx = \frac{\sqrt{\pi}}{2}
62
+ $$
63
+ ```
64
+
65
+ **Note:** While the code includes support for `\[...\]` (display) and `\(...\)` (inline) delimiters, these modes are not currently guaranteed to work correctly. Use `$$...$$` for reliable math rendering.
66
+
67
+ ### Architecture
68
+
69
+ #### `lib/index.js`
70
+ Main plugin module with hooks:
71
+ - `init()` - Plugin initialization on load
72
+
73
+ #### `static/js/render.js`
74
+ Client-side script for:
75
+ - Loading KaTeX CSS and JS from bundled assets
76
+ - Lazy initialization (only when expressions are present)
77
+ - Dynamic content support (AJAX navigation)
78
+ - Cleaning HTML tags inside math expressions
79
+
80
+ #### `static/css/katex.css`
81
+ Additional styles for:
82
+ - Proper inline expression spacing
83
+ - Display expression alignment
84
+ - Dark theme support
85
+
86
+ ### Technical Details
87
+
88
+ #### Server-side
89
+ - Minimal server-side logic
90
+ - Plugin initialization hook only
91
+ - No server-side parsing overhead
92
+
93
+ #### Client-side Loading
94
+ - KaTeX CSS loads only when math expressions are detected
95
+ - KaTeX JS loads from bundled assets
96
+ - Auto-render extension for automatic detection
97
+ - Support for dynamic content via AJAX
98
+ - HTML tag cleanup inside math expressions
99
+
100
+ ### Compatibility
101
+
102
+ - **NodeBB**: 4.0+
103
+ - **Node.js**: 18+
104
+ - **Browsers**: All modern browsers (Chrome, Firefox, Safari, Edge)
105
+
106
+ ### Dependencies
107
+
108
+ - **katex** (^0.16.27) - KaTeX math typesetting library
109
+
110
+ ### Development Dependencies
111
+
112
+ - **eslint** (^9.35.0) - Code linting
113
+
114
+ ### Performance
115
+
116
+ - Fast client-side rendering
117
+ - Lazy resource loading
118
+ - Minimal page load impact
119
+ - Caching support
120
+ - No server-side parsing overhead
121
+
122
+ ### Scripts
123
+
124
+ ```bash
125
+ npm run lint # Check code style
126
+ npm run fix # Auto-fix linting issues
127
+ ```
128
+
129
+ ### License
130
+
131
+ MIT
132
+
133
+ ### Author
134
+
135
+ Timofey (mysteren)
136
+
137
+ ### Acknowledgments
138
+
139
+ - Original plugin by [Benjamin Abel](https://github.com/benabel/nodebb-plugin-katex)
140
+ - [KaTeX](https://katex.org/) - Fast math typesetting library by Khan Academy
141
+
142
+ ---
143
+
144
+ <a name="russian"></a>
145
+ ## Русский
146
+
147
+ Современный плагин для поддержки математических выражений KaTeX в NodeBB 4+.
148
+
149
+ ### Возможности
150
+
151
+ - ✨ **Ленивая загрузка** - KaTeX подгружается только когда на странице есть математические выражения
152
+ - 📐 **Режим отображения** - В настоящее время поддерживается синтаксис `$$...$$`
153
+ - 🎨 **Готов к серверу** - Минимальная серверная логика, рендеринг на клиенте
154
+ - 🌐 **CDN доставка** - Использует встроенный KaTeX для быстрой доставки
155
+ - 🚀 **Интеграция с парсером** - Бесшовно интегрируется с парсером NodeBB
156
+ - 📱 **Адаптивный дизайн** - Работает на мобильных и десктопных устройствах
157
+ - 🌙 **Поддержка тёмной темы** - Автоматически адаптируется к схеме цветов
158
+
159
+ ### Установка
160
+
161
+ #### Через npm (рекомендуется)
162
+
163
+ ```bash
164
+ cd /путь/к/nodebb
165
+ npm install nodebb-plugin-katex2
166
+ ```
167
+
168
+ #### Ручная установка
169
+
170
+ 1. Клонируйте плагин в `/nodebb/node_modules/`
171
+ 2. Активируйте плагин в панели администратора NodeBB
172
+ 3. Перезапустите NodeBB
173
+
174
+ ```bash
175
+ cd /путь/к/nodebb/node_modules
176
+ git clone https://github.com/mysteren/nodebb-plugin-katex.git
177
+ cd ../..
178
+ npm install ./node_modules/nodebb-plugin-katex
179
+ ./nodebb restart
180
+ ```
181
+
182
+ ### Использование
183
+
184
+ #### Display режим (отдельной строкой)
185
+
186
+ В настоящее время полностью поддерживается и протестирован только синтаксис `$$...$$`:
187
+
188
+ ```
189
+ $$
190
+ \frac{-b \pm \sqrt{b^2 - 4ac}}{2a}
191
+ $$
192
+ ```
193
+
194
+ Пример с интегралами:
195
+
196
+ ```
197
+ $$
198
+ \int_{0}^{\infty} e^{-x^2} dx = \frac{\sqrt{\pi}}{2}
199
+ $$
200
+ ```
201
+
202
+ **Примечание:** Хотя в коде присутствует поддержка разделителей `\[...\]` (отображаемый режим) и `\(...\)` (встроенный режим), корректная работа этих режимов в настоящее время не гарантируется. Используйте `$$...$$` для надёжного отображения формул.
203
+
204
+ ### Архитектура
205
+
206
+ #### `lib/index.js`
207
+ Основной модуль плагина с хуками:
208
+ - `init()` - Инициализация плагина при загрузке
209
+
210
+ #### `static/js/render.js`
211
+ Клиентский скрипт для:
212
+ - Загрузки KaTeX CSS и JS из встроенных ресурсов
213
+ - Ленивой инициализации (только при наличии выражений)
214
+ - Поддержки динамического контента (AJAX навигация)
215
+ - Очистки HTML-тегов внутри математических выражений
216
+
217
+ #### `static/css/katex.css`
218
+ Дополнительные стили для:
219
+ - Правильного отступа inline выражений
220
+ - Выравнивания display выражений
221
+ - Поддержки тёмной темы
222
+
223
+ ### Технические детали
224
+
225
+ #### Серверная часть
226
+ - Минимальная логика на сервере
227
+ - Только хук инициализации плагина
228
+ - Отсутствие нагрузки на парсинг на сервере
229
+
230
+ #### Загрузка на клиенте
231
+ - KaTeX CSS загружается только при обнаружении математических выражений
232
+ - KaTeX JS загружается из встроенных ресурсов
233
+ - Расширение auto-render для автоматического обнаружения
234
+ - Поддержка динамического контента через AJAX
235
+ - Очистка HTML-тегов внутри математических выражений
236
+
237
+ ### Совместимость
238
+
239
+ - **NodeBB**: 4.0+
240
+ - **Node.js**: 18+
241
+ - **Браузеры**: Все современные браузеры (Chrome, Firefox, Safari, Edge)
242
+
243
+ ### Зависимости
244
+
245
+ - **katex** (^0.16.27) - Библиотека математической вёрстки KaTeX
246
+
247
+ ### Зависимости для разработки
248
+
249
+ - **eslint** (^9.35.0) - Проверка стиля кода
250
+
251
+ ### Производительность
252
+
253
+ - Быстрый рендеринг на клиенте
254
+ - Ленивая загрузка ресурсов
255
+ - Минимальное влияние на загрузку страницы
256
+ - Поддержка кэширования
257
+ - Отсутствие нагрузки на парсинг на сервере
258
+
259
+ ### Скрипты
260
+
261
+ ```bash
262
+ npm run lint # Проверка стиля кода
263
+ npm run fix # Автоматическое исправление ошибок стиля
264
+ ```
265
+
266
+ ### Лицензия
267
+
268
+ MIT
269
+
270
+ ### Автор
271
+
272
+ Timofey (mysteren)
273
+
274
+ ### Благодарности
275
+
276
+ - Оригинальный плагин от [Benjamin Abel](https://github.com/benabel/nodebb-plugin-katex)
277
+ - [KaTeX](https://katex.org/) - Быстрая библиотека математической вёрстки от Khan Academy
package/lib/index.js ADDED
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Серверная часть плагина
3
+ * Минимальная логика - вся работа на клиенте
4
+ */
5
+
6
+ "use strict";
7
+
8
+ /**
9
+ * Инициализация плагина при загрузке приложения
10
+ */
11
+ function init(_params, callback) {
12
+ console.log("[KaTeX] Plugin initialized");
13
+ callback();
14
+ }
15
+
16
+ module.exports = {
17
+ init,
18
+ };
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "nodebb-plugin-katex2",
3
+ "version": "1.0.0",
4
+ "description": "KaTeX math rendering plugin for NodeBB",
5
+ "main": "lib/index.js",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/mysteren/nodebb-plugin-katex"
9
+ },
10
+ "keywords": [
11
+ "nodebb",
12
+ "plugin",
13
+ "katex",
14
+ "latex",
15
+ "math",
16
+ "tex"
17
+ ],
18
+ "author": "Timofey",
19
+ "license": "MIT",
20
+ "bugs": {
21
+ "url": "https://github.com/mysteren/nodebb-plugin-katex/issues"
22
+ },
23
+ "dependencies": {
24
+ "katex": "^0.16.27"
25
+ },
26
+ "devDependencies": {
27
+ "eslint": "^9.35.0"
28
+ },
29
+ "scripts": {
30
+ "lint": "eslint lib/ static/js/",
31
+ "fix": "eslint --fix lib/ static/js/"
32
+ },
33
+ "engines": {
34
+ "node": ">=18"
35
+ },
36
+ "nbbpm": {
37
+ "compatibility": "^4.0.0"
38
+ }
39
+ }
package/plugin.json ADDED
@@ -0,0 +1,18 @@
1
+ {
2
+ "id": "nodebb-plugin-katex2",
3
+ "name": "KaTeX Math Renderer",
4
+ "description": "Render LaTeX math expressions with KaTeX in NodeBB posts",
5
+ "url": "https://github.com/mysteren/nodebb-plugin-katex",
6
+ "library": "lib/index.js",
7
+ "hooks": [
8
+ {
9
+ "hook": "static:app.load",
10
+ "method": "init"
11
+ }
12
+ ],
13
+ "scripts": ["static/js/render.js"],
14
+ "stylesheets": ["static/css/katex.css"],
15
+ "staticDirs": {
16
+ "katex": "./node_modules/katex/dist"
17
+ }
18
+ }
@@ -0,0 +1,312 @@
1
+ /**
2
+ * Ленивая загрузка и рендеринг KaTeX
3
+ * Библиотека загружается только если на странице есть формулы
4
+ */
5
+
6
+ (function () {
7
+ "use strict";
8
+
9
+ console.log("Katex render");
10
+
11
+ // Флаги загрузки
12
+ let katexLoaded = false;
13
+ let katexLoading = false;
14
+ let loadCallbacks = [];
15
+
16
+ /**
17
+ * Регулярное выражение для поиска формул
18
+ * Ищем разделители: $$, \[, \(
19
+ */
20
+ const MATH_PATTERN = /\$\$|\\\[|\\\(/;
21
+
22
+ /**
23
+ * Проверка наличия формул на странице
24
+ * @returns {boolean}
25
+ */
26
+ function hasMathContent() {
27
+ // Проверяем в постах
28
+ const posts = document.querySelectorAll('[component="post/content"]');
29
+
30
+ for (let i = 0; i < posts.length; i++) {
31
+ if (MATH_PATTERN.test(posts[i].textContent)) {
32
+ return true;
33
+ }
34
+ }
35
+
36
+ // Проверяем в превью редактора
37
+ const preview = document.querySelector(".preview-container");
38
+ if (preview && MATH_PATTERN.test(preview.textContent)) {
39
+ return true;
40
+ }
41
+
42
+ // Проверяем в заголовках
43
+ const titles = document.querySelectorAll('[component="topic/title"]');
44
+ for (let i = 0; i < titles.length; i++) {
45
+ if (MATH_PATTERN.test(titles[i].textContent)) {
46
+ return true;
47
+ }
48
+ }
49
+
50
+ return false;
51
+ }
52
+
53
+ /**
54
+ * Динамическая загрузка CSS файла
55
+ * @param {string} href - Путь к CSS файлу
56
+ * @returns {Promise}
57
+ */
58
+ function loadCSS(href) {
59
+ return new Promise(function (resolve, reject) {
60
+ // Проверяем, не загружен ли уже
61
+ const existing = document.querySelector('link[href="' + href + '"]');
62
+ if (existing) {
63
+ resolve();
64
+ return;
65
+ }
66
+
67
+ const link = document.createElement("link");
68
+ link.rel = "stylesheet";
69
+ link.href = href;
70
+ link.onload = resolve;
71
+ link.onerror = reject;
72
+ document.head.appendChild(link);
73
+ });
74
+ }
75
+
76
+ /**
77
+ * Динамическая загрузка JavaScript файла
78
+ * @param {string} src - Путь к JS файлу
79
+ * @returns {Promise}
80
+ */
81
+ function loadScript(src) {
82
+ return new Promise(function (resolve, reject) {
83
+ // Проверяем, не загружен ли уже
84
+ const existing = document.querySelector('script[src="' + src + '"]');
85
+ if (existing) {
86
+ resolve();
87
+ return;
88
+ }
89
+
90
+ const script = document.createElement("script");
91
+ script.src = src;
92
+ script.onload = resolve;
93
+ script.onerror = reject;
94
+ document.body.appendChild(script);
95
+ });
96
+ }
97
+
98
+ /**
99
+ * Загрузка библиотеки KaTeX (с async/await и параллельной загрузкой)
100
+ * @returns {Promise}
101
+ */
102
+ async function loadKaTeX() {
103
+ // Если уже загружено
104
+ if (katexLoaded) {
105
+ return;
106
+ }
107
+
108
+ // Если загружается сейчас - ждем
109
+ if (katexLoading) {
110
+ return new Promise(function (resolve) {
111
+ loadCallbacks.push(resolve);
112
+ });
113
+ }
114
+
115
+ console.log("[KaTeX] Loading library...");
116
+ katexLoading = true;
117
+
118
+ try {
119
+ // Путь к библиотеке в node_modules
120
+ // const basePath = "/plugins/nodebb-plugin-katex/node_modules/katex/dist/";
121
+ // Находим правильный путь
122
+ // Путь к файлам через modules
123
+ const basePath = "/assets/plugins/nodebb-plugin-katex/katex/";
124
+
125
+ // Параллельная загрузка всех файлов
126
+ await Promise.all([
127
+ loadCSS(basePath + "katex.min.css"),
128
+ loadScript(basePath + "katex.min.js"),
129
+ loadScript(basePath + "contrib/auto-render.min.js"),
130
+ ]);
131
+
132
+ katexLoaded = true;
133
+ katexLoading = false;
134
+ console.log("[KaTeX] Library loaded successfully");
135
+
136
+ // Вызываем все ожидающие колбэки
137
+ loadCallbacks.forEach(function (callback) {
138
+ callback();
139
+ });
140
+ loadCallbacks = [];
141
+ } catch (err) {
142
+ katexLoading = false;
143
+ console.error("[KaTeX] Failed to load library:", err);
144
+ throw err;
145
+ }
146
+ }
147
+
148
+ /**
149
+ * Конфигурация KaTeX
150
+ */
151
+ const KATEX_CONFIG = {
152
+ delimiters: [
153
+ { left: "$$", right: "$$", display: true },
154
+ { left: "\\[", right: "\\]", display: true },
155
+ { left: "\\(", right: "\\)", display: false },
156
+ ],
157
+ throwOnError: false,
158
+ errorColor: "#cc0000",
159
+ strict: false,
160
+ trust: false,
161
+ };
162
+
163
+ /**
164
+ * Очистка HTML-тегов внутри формул
165
+ * Markdown может добавить <br>, <p> и другие теги
166
+ * @param {HTMLElement} element
167
+ */
168
+ function cleanMathElements(element) {
169
+ const walker = document.createTreeWalker(
170
+ element,
171
+ NodeFilter.SHOW_TEXT,
172
+ null,
173
+ false,
174
+ );
175
+
176
+ const nodesToProcess = [];
177
+ let node;
178
+
179
+ // Собираем все текстовые узлы с формулами
180
+ while ((node = walker.nextNode())) {
181
+ const text = node.textContent;
182
+ if (MATH_PATTERN.test(text)) {
183
+ nodesToProcess.push(node);
184
+ }
185
+ }
186
+
187
+ // Очищаем найденные узлы от HTML
188
+ nodesToProcess.forEach(function (textNode) {
189
+ let parent = textNode.parentNode;
190
+
191
+ if (
192
+ parent &&
193
+ (parent.innerHTML.includes("<br") || parent.innerHTML.includes("<p"))
194
+ ) {
195
+ const cleanText = parent.textContent;
196
+
197
+ if (MATH_PATTERN.test(cleanText)) {
198
+ parent.textContent = cleanText;
199
+ }
200
+ }
201
+ });
202
+ }
203
+
204
+ /**
205
+ * Рендеринг формул в элементе
206
+ * @param {HTMLElement} element
207
+ */
208
+ function renderMath(element) {
209
+ if (!element) return;
210
+
211
+ try {
212
+ // Сначала очищаем от HTML-тегов
213
+ cleanMathElements(element);
214
+
215
+ // Затем рендерим формулы
216
+ renderMathInElement(element, KATEX_CONFIG);
217
+ } catch (err) {
218
+ console.error("[KaTeX] Render error:", err);
219
+ }
220
+ }
221
+
222
+ /**
223
+ * Рендеринг всех постов (async)
224
+ */
225
+ async function renderAllPosts() {
226
+ // Проверяем наличие формул
227
+ if (!hasMathContent()) {
228
+ console.log("[KaTeX] No math content found, skipping");
229
+ return;
230
+ }
231
+
232
+ try {
233
+ // Загружаем KaTeX если нужно
234
+ await loadKaTeX();
235
+
236
+ // Рендерим посты
237
+ const posts = document.querySelectorAll('[component="post/content"]');
238
+ posts.forEach(function (post) {
239
+ renderMath(post);
240
+ });
241
+
242
+ // Рендерим превью
243
+ const preview = document.querySelector(".preview-container");
244
+ if (preview) {
245
+ renderMath(preview);
246
+ }
247
+
248
+ // Рендерим заголовки
249
+ const titles = document.querySelectorAll('[component="topic/title"]');
250
+ titles.forEach(function (title) {
251
+ renderMath(title);
252
+ });
253
+
254
+ console.log("[KaTeX] Rendered " + posts.length + " posts");
255
+ } catch (err) {
256
+ console.error("[KaTeX] Failed to render:", err);
257
+ }
258
+ }
259
+
260
+ /**
261
+ * Инициализация
262
+ */
263
+ function init() {
264
+ // Первый рендеринг
265
+ renderAllPosts();
266
+
267
+ // События NodeBB для динамического контента
268
+
269
+ // Когда загружаются новые посты (скролл, пагинация)
270
+ window.addEventListener("action:posts.loaded", function () {
271
+ setTimeout(function () {
272
+ renderAllPosts();
273
+ }, 50);
274
+ });
275
+
276
+ // Когда открывается тема
277
+ window.addEventListener("action:topic.loaded", function () {
278
+ setTimeout(function () {
279
+ renderAllPosts();
280
+ }, 50);
281
+ });
282
+
283
+ // Когда происходит навигация
284
+ window.addEventListener("action:ajaxify.end", function () {
285
+ setTimeout(function () {
286
+ renderAllPosts();
287
+ }, 100);
288
+ });
289
+
290
+ // Когда обновляется превью в редакторе
291
+ window.addEventListener("action:composer.preview", async function () {
292
+ if (hasMathContent()) {
293
+ try {
294
+ await loadKaTeX();
295
+ const preview = document.querySelector(".preview-container");
296
+ if (preview) {
297
+ renderMath(preview);
298
+ }
299
+ } catch (err) {
300
+ console.error("[KaTeX] Preview render error:", err);
301
+ }
302
+ }
303
+ });
304
+ }
305
+
306
+ // Запуск после загрузки DOM
307
+ if (document.readyState === "loading") {
308
+ document.addEventListener("DOMContentLoaded", init);
309
+ } else {
310
+ init();
311
+ }
312
+ })();