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('Не удалось скопировать текст.'); + } + }); +}