From 92711f57c2c420ba6e54b38e67cb1241c6a3f332 Mon Sep 17 00:00:00 2001
From: erjemin
Date: Tue, 20 Jan 2026 00:56:23 +0300
Subject: [PATCH] =?UTF-8?q?add:=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?=
=?UTF-8?q?=D0=B5=D0=BD=D0=B0=20=D0=BA=D0=BD=D0=BE=D0=BF=D0=BA=D0=B0=20"?=
=?UTF-8?q?=D1=81=D0=BA=D0=BE=D0=BF=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D1=82?=
=?UTF-8?q?=D1=8C=20=D0=B2=20=D0=B1=D1=83=D1=84=D0=B5=D1=80=20=D0=BE=D0=B1?=
=?UTF-8?q?=D0=BC=D0=B5=D0=BD=D0=B0"=20=D0=B8=20=D0=BE=D1=82=D1=81=D0=BB?=
=?UTF-8?q?=D0=B5=D0=B6=D0=B8=D0=B2=D0=B0=D0=BD=D0=B8=D0=B5=20=D1=86=D0=B5?=
=?UTF-8?q?=D0=BB=D0=B5=D0=B9=20=D0=B2=20Yandex-Metrica=20=D0=B8=20Top-Mai?=
=?UTF-8?q?l.ru?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../typograph/templates/typograph/base.html | 4 +-
.../typograph/templates/typograph/index.html | 15 +-
public/static/js/base.js | 224 ++++++++++--------
public/static/js/index.js | 69 +++++-
4 files changed, 200 insertions(+), 112 deletions(-)
diff --git a/etpgrf_site/typograph/templates/typograph/base.html b/etpgrf_site/typograph/templates/typograph/base.html
index eee5517..a29f13c 100644
--- a/etpgrf_site/typograph/templates/typograph/base.html
+++ b/etpgrf_site/typograph/templates/typograph/base.html
@@ -64,8 +64,8 @@
{# Футер #}
diff --git a/etpgrf_site/typograph/templates/typograph/index.html b/etpgrf_site/typograph/templates/typograph/index.html
index c4d3b80..dd281fb 100644
--- a/etpgrf_site/typograph/templates/typograph/index.html
+++ b/etpgrf_site/typograph/templates/typograph/index.html
@@ -14,7 +14,7 @@
на «ёлочки» (или “лапки” для англоязычного текста), отбивка и компоновка тире,
инициалов, единиц измерения, переносы в словах, обработка псевдографики и преобразование
их в спецсимволы, висячая пунктуация. Получите готовый и валидный HTML-код для вставки
- на ваш сайт или публикации в блог.
+ на ваш сайт или публикацию в блог.
Исходный код etpgrf-типографа доступен в нескольких репозиториях
@@ -265,14 +265,19 @@
-
+
-
+
+
+
+
diff --git a/public/static/js/base.js b/public/static/js/base.js
index c496443..d62400d 100644
--- a/public/static/js/base.js
+++ b/public/static/js/base.js
@@ -1,115 +1,133 @@
(function () {
- const darkModeMediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
- const logoImg = document.getElementById('logo-img');
- const navbar = document.getElementById('main-navbar');
+ "use strict";
- // --- АВТОМАТИЧЕСКОЕ ПЕРЕКЛЮЧЕНИЕ ТЕМЫ (Dark/Light) ---
- function updateTheme(e) {
- const theme = e.matches ? 'dark' : 'light';
- document.documentElement.setAttribute('data-bs-theme', theme);
- }
+ // --- АВТОМАТИЧЕСКОЕ ПЕРЕКЛЮЧЕНИЕ ТЕМЫ (Dark/Light) ---
+ const darkModeMediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
- // --- ОБНОВЛЕНИЕ ЛОГОТИПА ПРИ СКРОЛЛЕ И СМЕНЕ ТЕМЫ ---
- function updateLogo() {
- const isDark = darkModeMediaQuery.matches;
- // Используем getBoundingClientRect для определения позиции контента
- if (document.getElementById('content-container').getBoundingClientRect().top < 78) {
- navbar.classList.add('navbar-scrolled');
- logoImg.src = isDark ? logoImg.dataset.srcDarkCompact : logoImg.dataset.srcLightCompact;
- } else {
- navbar.classList.remove('navbar-scrolled');
- logoImg.src = isDark ? logoImg.dataset.srcDark : logoImg.dataset.srcLight;
+ function updateTheme(e) {
+ const theme = e.matches ? 'dark' : 'light';
+ document.documentElement.setAttribute('data-bs-theme', theme);
+ // При смене темы обновляем и логотип
+ updateLogo();
}
- }
- // Инициализация
- updateTheme(darkModeMediaQuery);
- updateLogo();
- document.addEventListener('DOMContentLoaded', updateLogo);
+ // --- ЛОГОТИП И СКРОЛЛ ---
+ function updateLogo() {
+ const logoImg = document.getElementById('logo-img');
+ const navbar = document.getElementById('main-navbar');
+
+ if (!logoImg || !navbar) return;
- // Слушаем скролл
- window.addEventListener('scroll', updateLogo);
+ const isDark = darkModeMediaQuery.matches;
+ // Используем window.scrollY для определения прокрутки
+ // Если прокрутили больше 50px, уменьшаем шапку
+ const isScrolled = window.scrollY > 50;
- // Слушаем смену темы
- window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', updateLogo);
+ if (isScrolled) {
+ navbar.classList.add('navbar-scrolled');
+ logoImg.src = isDark ? logoImg.dataset.srcDarkCompact : logoImg.dataset.srcLightCompact;
+ } else {
+ navbar.classList.remove('navbar-scrolled');
+ logoImg.src = isDark ? logoImg.dataset.srcDark : logoImg.dataset.srcLight;
+ }
+ }
- // --- КУКИ ---
- const COOKIE_KEY = 'cookie_consent';
- const TTL_MS = 90 * 24 * 60 * 60 * 1000; // 90 дней: 90 * 24 * 60 * 60 * 1000 = 7776000000)
+ // Инициализация темы и логотипа
+ updateTheme(darkModeMediaQuery);
+ darkModeMediaQuery.addEventListener('change', updateTheme);
+
+ // Инициализация логотипа при загрузке и скролле
+ document.addEventListener('DOMContentLoaded', updateLogo);
+ window.addEventListener('scroll', updateLogo);
- const banner = document.getElementById('cookie-banner');
- const acceptButton = document.getElementById('cookie-accept');
- // Функция загрузки счетчиков аналитики
- function loadCounters() {
- // console.log("Загрузка счетчиков (Яндекс, Google)...");
- // Код Яндекс.Метрики
- (function (m, e, t, r, i, k, a) {
- m[i] = m[i] || function () { (m[i].a = m[i].a || []).push(arguments) };
- m[i].l = 1 * new Date();
- for (var j = 0; j < document.scripts.length; j++) { if (document.scripts[j].src === r) { return; } }
- k = e.createElement(t), a = e.getElementsByTagName(t)[0], k.async = 1, k.src = r, a.parentNode.insertBefore(k, a)
- })(window, document, 'script', 'https://mc.yandex.ru/metrika/tag.js?id=106310834', 'ym');
- ym(106310834, 'init', {
- ssr: true, webvisor: true, clickmap: true, ecommerce: "dataLayer",
- accurateTrackBounce: true, trackLinks: true
+ // --- КУКИ И СЧЕТЧИКИ ---
+ const COOKIE_KEY = 'cookie_consent';
+ const TTL_MS = 90 * 24 * 60 * 60 * 1000; // 90 дней
+ const MAILRU_ID = "3734603";
+ const YANDEX_ID = "106310834";
+
+ function loadCounters() {
+ // console.log("Загрузка счетчиков...");
+ try {
+ // Mail.ru
+ var _tmr = window._tmr || (window._tmr = []);
+ _tmr.push({id: MAILRU_ID, type: "pageView", start: (new Date()).getTime()});
+ (function (d, w, id) {
+ if (d.getElementById(id)) return;
+ var ts = d.createElement("script"); ts.type = "text/javascript"; ts.async = true; ts.id = id;
+ ts.src = "https://top-fwz1.mail.ru/js/code.js";
+ var f = d.getElementsByTagName("script")[0]; f.parentNode.insertBefore(ts, f);
+ })(document, window, "topmailru-code");
+
+ // Яндекс.Метрика
+ (function(m,e,t,r,i,k,a){m[i]=m[i]||function(){(m[i].a=m[i].a||[]).push(arguments)};
+ m[i].l=1*new Date();
+ for (var j = 0; j < document.scripts.length; j++) {if (document.scripts[j].src === r) { return; }}
+ k=e.createElement(t),a=e.getElementsByTagName(t)[0],k.async=1,k.src=r,a.parentNode.insertBefore(k,a)})
+ (window, document, "script", "https://mc.yandex.ru/metrika/tag.js", "ym");
+
+ window.ym(YANDEX_ID, "init", {
+ clickmap:true,
+ trackLinks:true,
+ accurateTrackBounce:true
+ });
+ } catch (e) {
+ console.error("Ошибка загрузки счетчиков:", e);
+ }
+ }
+
+ function checkConsent() {
+ try {
+ const stored = localStorage.getItem(COOKIE_KEY);
+ if (!stored) return false;
+ const data = JSON.parse(stored);
+ const now = Date.now();
+ if (now - data.timestamp > TTL_MS) {
+ localStorage.removeItem(COOKIE_KEY);
+ return false;
+ }
+ return true;
+ } catch (e) {
+ return false;
+ }
+ }
+
+ // Инициализация куки-баннера
+ document.addEventListener('DOMContentLoaded', function() {
+ const banner = document.getElementById('cookie-banner');
+ const acceptButton = document.getElementById('cookie-accept');
+
+ if (banner && acceptButton) {
+ if (checkConsent()) {
+ loadCounters();
+ } else {
+ banner.style.display = 'block';
+ }
+
+ acceptButton.addEventListener('click', function () {
+ const data = { value: true, timestamp: Date.now() };
+ localStorage.setItem(COOKIE_KEY, JSON.stringify(data));
+ banner.style.display = 'none';
+ loadCounters();
+ });
+ }
});
-
- // Код Google Analytics
- // window.dataLayer = window.dataLayer || [];
- // function gtag(){dataLayer.push(arguments);}
- // gtag('js', new Date());
- // gtag('config', 'G-XXXXXXXXXX');
-
- // Код Top.Mail.Ru
- var _tmr = window._tmr || (window._tmr = []);
- _tmr.push({id: "3734603", type: "pageView", start: (new Date()).getTime()});
- (function (d, w, id) {
- if (d.getElementById(id)) return;
- var ts = d.createElement("script");
- ts.type = "text/javascript"; ts.async = true; ts.id = id; ts.src = "https://top-fwz1.mail.ru/js/code.js";
- var f = function () {
- var s = d.getElementsByTagName("script")[0]; s.parentNode.insertBefore(ts, s);
- };
- if (w.opera == "[object Opera]") {
- d.addEventListener("DOMContentLoaded", f, false);
- } else { f(); }
- })(document, window, "tmr-code");
- //
- // alert("Отладка. Счетчики загружены (здесь должен быть реальный код счетчиков).");
- }
-
- function checkConsent() {
- const stored = localStorage.getItem(COOKIE_KEY);
- if (!stored) return false;
-
- try {
- const data = JSON.parse(stored);
- const now = Date.now();
- // Проверяем, не истек ли срок
- if (now - data.timestamp > TTL_MS) {
- localStorage.removeItem(COOKIE_KEY);
- return false;
- }
- return true;
- } catch (e) {
- return false;
- }
- }
-
- if (checkConsent()) {
- loadCounters();
- } else {
- banner.style.display = 'block';
- }
-
- acceptButton.addEventListener('click', function () {
- const data = {
- value: true,
- timestamp: Date.now()
+
+ // Глобальная функция для отправки целей
+ window.sendGoal = function(goalName) {
+ if (!checkConsent()) return;
+ // console.log("Sending goal:", goalName);
+
+ try {
+ if (window._tmr) {
+ window._tmr.push({ id: MAILRU_ID, type: "reachGoal", goal: goalName, value: 1 });
+ }
+ if (typeof window.ym === 'function') {
+ window.ym(YANDEX_ID, 'reachGoal', goalName);
+ }
+ } catch (e) {
+ console.error("Ошибка отправки цели:", e);
+ }
};
- localStorage.setItem(COOKIE_KEY, JSON.stringify(data));
- banner.style.display = 'none';
- loadCounters();
- });
-})();
\ No newline at end of file
+})();
diff --git a/public/static/js/index.js b/public/static/js/index.js
index 2b8b5d9..b5ed443 100644
--- a/public/static/js/index.js
+++ b/public/static/js/index.js
@@ -19,9 +19,12 @@ import {
} from "../codemirror/editor.js";
const resultWrapper = document.getElementById('cm-result-wrapper');
+const btnCopy = document.getElementById('btn-copy');
+const sourceTextarea = document.querySelector('textarea[name="text"]');
+
+// console.log("Index.js loaded. btnCopy:", !!btnCopy, "sourceTextarea:", !!sourceTextarea); // DEBUG
const themeCompartment = new Compartment();
-
function getTheme() {
return window.matchMedia('(prefers-color-scheme: dark)').matches ? oneDark : [];
}
@@ -47,8 +50,10 @@ const charNames = {
0x2063: "Invisible Comma (невидимая запятая для семантической разметки математических выражений — ⁣)",
};
+const PLACEHOLDER_TEXT = "Здесь появится результат...";
+
const resultState = EditorState.create({
- doc: "Здесь появится результат...",
+ doc: PLACEHOLDER_TEXT,
extensions: [
lineNumbers(),
highlightActiveLineGutter(),
@@ -83,17 +88,77 @@ const resultView = new EditorView({
parent: resultWrapper
});
+// Обработка ответа от сервера (HTMX)
document.body.addEventListener('htmx:afterSwap', function (evt) {
+ // console.log("HTMX afterSwap event:", evt.detail.target.id); // DEBUG
if (evt.detail.target.id === 'result-area') {
const newContent = evt.detail.xhr.response;
+
+ // Обновляем редактор
resultView.dispatch({
changes: {from: 0, to: resultView.state.doc.length, insert: newContent}
});
+
+ // Показываем кнопку копирования
+ if (btnCopy) {
+ // console.log("Showing copy button"); // DEBUG
+ btnCopy.classList.remove('d-none');
+ }
}
});
+// Скрываем кнопку и сбрасываем редактор при изменении исходного текста
+if (sourceTextarea) {
+ sourceTextarea.addEventListener('input', () => {
+ if (btnCopy) {
+ btnCopy.classList.add('d-none');
+ }
+ // Сбрасываем редактор на плейсхолдер
+ if (resultView.state.doc.toString() !== PLACEHOLDER_TEXT) {
+ resultView.dispatch({
+ changes: {from: 0, to: resultView.state.doc.length, insert: PLACEHOLDER_TEXT}
+ });
+ }
+ });
+}
+
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {
resultView.dispatch({
effects: themeCompartment.reconfigure(getTheme())
});
});
+
+// --- КОПИРОВАНИЕ В БУФЕР ---
+if (btnCopy) {
+ btnCopy.addEventListener('click', async () => {
+ const text = resultView.state.doc.toString();
+ // console.log("Copying text:", text.substring(0, 20) + "..."); // DEBUG
+
+ // Отправляем цель в метрику
+ if (typeof window.sendGoal === 'function') {
+ window.sendGoal('etpgrf-copy-pressed');
+ }
+
+ try {
+ await navigator.clipboard.writeText(text);
+
+ // Визуальное подтверждение
+ const originalHtml = btnCopy.innerHTML;
+ const originalClass = btnCopy.className;
+
+ btnCopy.innerHTML = ' Скопировано!';
+ btnCopy.classList.remove('btn-outline-secondary', 'btn-outline-primary');
+ btnCopy.classList.add('btn-success');
+
+ setTimeout(() => {
+ btnCopy.innerHTML = originalHtml;
+ btnCopy.className = originalClass;
+ btnCopy.classList.remove('d-none');
+ }, 2000);
+
+ } catch (err) {
+ console.error('Ошибка копирования:', err);
+ alert('Не удалось скопировать текст.');
+ }
+ });
+}