# Сергей Глазов — full-stack веб-разработчик Nuxt, Laravel - Полный экспорт контента > Full-stack веб-разработчик Nuxt, Laravel ## Сайт https://sglazov.ru ## О сайте Этот файл содержит полный контент всех опубликованных заметок блога. Это полезно для AI-моделей и LLM, которые хотят получить полную базу знаний. Для карты сайта без полного контента см.: https://sglazov.ru/llms.txt --- # Полный контент ## Скрипт для вёрстки статей из Google Docs в чистый HTML c картинками для WordPress **URL**: https://sglazov.ru/notes/google-docs-to-html/ **Дата**: 2025-08-02T00:00:00.000Z **Теги**: Google Docs, Apps Script, WordPress, HTML, JS **Описание**: В Google Docs экспорт в HTML через меню «Файл» → «Сохранить как» → «Веб-страница (HTML, ZIP-архив)» — вообще не работает. Точнее работает, но через жопу.
В Google Docs экспорт в HTML через меню «Файл» → «Сохранить как» → «Веб-страница (HTML, ZIP-архив)» — вообще не работает. Точнее работает, но через жопу. Результат импорта практически использовать нельзя. На выходе будет архив, в котором кривой html-файл с десятичным кодом в unicode для всего текста, куча мусорных тегов <span> с инлайн-атрибутами и рандомными CSS-классами, странные общие глобальные стили, и, что самое удивительное, хронологический порядок картинок и их именование будет совершенно случайным.
В июле 2024 в Google Docs добавили экспорт в Markdown и в md-формате хронология и имена картинок — правильные! При этом картинки собираются в этом же файле в base64, что использовать сложно и рождает череду промежуточных этапов при простой казалось бы задаче «сверстать пост в блог на WordPress».
С ростом редакции и блогов захотелось нормальный инструмент для простого экспорта максимально чистого HTML и удобной работы с графикой в документе.
Все доступные найденные инструменты — не работали. Надежда была только на прямую конвертацию средствами Apps Script. Вариант через проект в Apps Script где-то внутри уже Google Drive меня не устраивал: создание, его настройка, раздача прав доступа под каждый документ на исполнение и сложности с правами на расшаренные документы напугали объёмом и передачей знаний по проекту. Инструкцию из двух десятков шагов не каждый пройдёт до финала.
Найден был более простой вариант: запустить отдельный сценарий в Apps Script на одном конкретном документе. Этот сценарий никуда ничего не передаёт и работает только с аккаунтом активного юзера. Минусы сценария: каждый запуск нужно проходить через череду модалок разрешений самого гугла.
Если документ в самом Google Docs разметили правильно (что редакторы делают чаще всего), то его экспорт в HTML будет чистым по структуре: заголовки всех уровней будут заголовками, абзацы — абзацами, нумерованные и маркированные списки — списками. Если ссылка внешняя — взять и сразу добавить ей атрибут rel="nofollow". Ну и поисковые роботы всё это уважают, что для контент-проектов важно.
Как процесс вёрстки поста для WordPress устроен в целом упрощённо без деталей про «а как» для каждого этапа: отдельно верстается разметка, отдельно загружается пачка файлов для этого поста и используется в разметке.
И вот эти промежуточные шаги, особенно если в посте картинок так с десятка два, чтобы каждую правильно именовать, например, добавив префикс после имени, которое повторяет slug поста, занимали кучу фокуса и времени. И это всё с учётом в разметке того, что WordPress будет хранить файлы по пути типа /wp-content/uploads/2025/08/
Ожидаемый результат: при запуске скрипта задаётся базовый набор параметров среди которых slug поста, например, ya-metrika. Алгоритм перебора картинок возьмёт его, добавит суффикс с порядковым номером типа ya-metrika_24 и сложит все картинки в архив.
Из архива все картинки загружаются в медиа библиотеку при создании нового поста в WordPress и все пути правильно работают.
На выходе — один zip-архив с файлом разметки и всеми картинками из документа с правильным именованием. И поскольку мы в пределах Google Docs и Apps Script, то результат выполнения можно отправить на почту, с которой и ведётся работа с документом.
Скорее бонусный, но приятный пункт. В самих Google Docs в панели слева есть структура документа. Почему бы её не вытащить также в кастомную разметку? Вместе с этой задачей добавляется новая задача: всем заголовкам надо добавить id, а чтобы его добавить нужна какая-то штука для транслитерации в slug-формат, чтобы из текста заголовка «Как создать и настроить счетчик посещений» получился id kak-sozdat-i-nastroit-schetchik-poseshchenij. Приставкой можно ещё id поста добавить, например, для уникальности.
Контент-менеджерам, редакциям, верстальщикам — упростить рутину, автоматизировать самое нудное, ускорить процесс вёрстки и выпуска контента.
Смотрите документацию с пошаговой инструкций прохождения этапа подтверждения.
Этот скрипт — настоящая находка для контент-команд, редакторов и верстальщиков! Он значительно ускоряет процесс подготовки статей, автоматизируя рутинные задачи и минимизируя ошибки при переносе текста из Google Docs в HTML.
Этот инструмент серьезно упрощает жизнь контент-специалистам. Если раньше верстка статьи занимала 20-30 минут, то теперь — буквально пару кликов. Рекомендую всем, кто работает с большими объемами текстов и хочет оптимизировать процесс публикации.
slug поста;alt для всех картинок;src для картинок под файловую структуру WordPress;width и height для картинок;rel="nofollow" для внешних ссылок;slug id для заголовков транслитом;utm-forward.js — это простой и эффективный скрипт на ванильном JS без зависимостей для автоматического проброса UTM-меток на внутренние и внешние ссылки вашего сайта. Для любых CMS, конструкторов сайтов или систем обучения (LMS).
Проблема: Когда пользователь переходит по ссылке с UTM-метками на ваш сайт, а затем кликает по внутренней ссылке, все UTM-параметры теряются. Это приводит к неточной аналитике и невозможности понять, какие маркетинговые каналы действительно приносят конверсии.
Решение: utm-forward.js автоматически добавляет UTM-метки ко всем внутренним и внешним ссылкам на вашем сайте, сохраняя информацию об источнике трафика на протяжении всего пути пользователя.
utm-forward.js?Всем: и без программистов и программирования всё можно настроить отслеживание UTM: нужно просто подключить скрипт на сайте или в конструкторе.
utm-forward.js сохраняет эти метки в localStorage браузераСкрипт решил проблему, которая отняла кучу времени. Примеры кода найденные на просторах интернета не работали. Реализовать решение через httacess не получилось. Скрипт легок в настройке и установке.
RSS не умер. Это не мейнстрим, но это все еще процветающий протокол, особенно среди технарей. Многие не знают, что такое RSS-лента и как им пользоваться.
Большинство браузеров отображают RSS как сырые XML-файлы, что не помогает пользователям понять, в чём суть:
В этой статье я расскажу, как оформлять RSS-ленты и одновременно обучать читателей.
Вот как выглядит RSS-лента этого блога:
Чтобы стилизовать XML-файл в браузере, необходимо предоставить информацию о стилях. Это можно сделать, добавив инструкцию обработки xml-stylesheet в RSS-ленту:
<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet href="/rss.xsl" type="text/xsl"?>
<feed xmlns="http://www.w3.org/2005/Atom"
xmlns:media="http://search.yahoo.com/mrss/">
...
</feed>
Атрибут href указывает URL-адрес допустимого XSL-файла. Вы можете ознакомиться с моим примером. Вот суть:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="3.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:atom="http://www.w3.org/2005/Atom">
<xsl:output method="html" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
<head>
<title>
RSS Feed | <xsl:value-of select="/atom:feed/atom:title"/>
</title>
<link rel="stylesheet" href="/assets/styles.css"/>
</head>
<body>
<p>
This is an RSS feed. Visit
<a href="https://aboutfeeds.com">About Feeds</a>
to learn more and get started. It’s free.
</p>
<h1>Recent blog posts</h1>
<xsl:for-each select="/atom:feed/atom:entry">
<a>
<xsl:attribute name="href">
<xsl:value-of select="atom:link/@href"/>
</xsl:attribute>
<xsl:value-of select="atom:title"/>
</a>
<xsl:value-of select="atom:summary"/>
Last updated:
<xsl:value-of select="substring(atom:updated, 0, 11)" />
</xsl:for-each>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
Этот код вдохновлен pretty-feed-v3, но я адаптировал его под спецификацию Atom.
XSL-файл преобразует XML-ленту в валидный HTML-документ, который любой браузер может отобразить. Вы можете использовать любую информацию из XML-файла и поместить её в новую структуру разметки. Вы даже можете отобразить контент, который не является частью XML-ленты. Идеальный пример использования — добавить заметку о том, что такое RSS-ленты и как их использовать (как показано в примере выше).
Вы можете определить встроенные CSS-стили с помощью обычного элемента <style>, но также можно импортировать внешние CSS-файлы. Я импортировал основной CSS-файл своего блога, поэтому мне не пришлось писать новый код, чтобы RSS-лента соответствовала дизайну моего блога.
Кроме того, можно использовать функции XSLT для изменения значений. Вот как я обрезаю временную метку, чтобы отображалась только дата:
<!-- Full timestamp -->
<xsl:value-of select="atom:updated" />
<!-- Date only -->
<xsl:value-of select="substring(atom:updated, 0, 11)" />
Поддержка браузером XSL-трансформаций отличная; неподдерживаемые браузеры вернутся к поведению по умолчанию (прогрессивное улучшение).
Вот несколько стилизованных RSS-лент, которые вы можете посмотреть для вдохновения:
XSL-файлы публичны, так что можно посмотреть, как эти всё реализовано.
XSL-файлы можно применять к любому XML-файлу. Ещё один пример использования стилизованных XML-файлов — это карта сайта. Хотя карты сайта предназначены для обработки роботами (например, краулерами), их можно стилизовать с минимальными усилиями. Посмотрите мою карту сайта и соответствующий XSL-файл в качестве примера. Вот суть:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:sitemap="http://www.sitemaps.org/schemas/sitemap/0.9">
<xsl:output method="html" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
<body>
<h1>Sitemap</h1>
<xsl:for-each select="/sitemap:urlset/sitemap:url">
<a>
<xsl:attribute name="href">
<xsl:value-of select="sitemap:loc"/>
</xsl:attribute>
<xsl:value-of select="sitemap:loc"/>
</a>
Last updated:
<xsl:value-of select="substring(sitemap:lastmod, 0, 11)" />
</xsl:for-each>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
Что произойдёт, если пользователь захочет сохранить стилизованный XML-файл? Интересно, что ответ зависит от браузера. Chrome и Safari сохранят оригинальный XML-файл, а Firefox сохранит стилизованную HTML-страницу. Большинство RSS-ридеров ожидают URL-адрес ленты, поэтому это поведение не имеет значения. Однако я бы был осторожен с XML-файлами, которые предназначены для загрузки, например, OPML-файлами. Спасибо Robb Knight за это наблюдение.
Я люблю RSS, и, согласно моей статистике, многие мои читатели тоже его любят. С учётом всех закрытых экосистем социальных сетей, я надеюсь, что RSS станет более популярным. Создавая собственный XSL-файл, вы можете сделать свою RSS-ленту привлекательной и добавить информацию о том, что такое RSS на самом деле.
Мне понравилась идея. Я захотел сделать не отдельную «красивую страницу» самого RSS, а как бы «вписать» RSS-страницу в общее оформление сайта — ну вот чтобы и шапка и подвал и вообще весь контекст.
Получилось вот так: https://sglazov.ru/notes/feed/
К сожалению, даты локализовать я не смог. И меня несколько смущает вот этот вид «2024-04-18», но это всё же лучше, чем просто xml-файл или отсутствие даты.
Мой файл rss-style.xsl повторяет разметку файла layout.njk всей темы этого сайта.
Единственный момент — понадобилось написать простую функцию, чтобы HTML-разметка там, где надо преобразовалась в xHTML — где-то закрывать теги и всё такое:
export default function html2xhtml(content) {
return content.toString()
.replace(/(<(img|source)[^>]*?) *\/?>/g, '$1 />')
.replace('itemscope', 'itemscope=""')
.replace('©', '©')
.replace(' ', ' ')
}
В неё целиком я оборачиваю какие-то include-конструкции. Карта сайта сделана также.
Ну казалось бы: две маленькие простые странные ссылки «Добавить в календарь» среди кучи интерактивных элементов на странице. При этом я получил ответы на вопросы типа «почему формат календарей устроен именно так?».
На самом-то деле, конечно, с глупости. На каком-то «сером» сайте-помойке, типа «скачать субтитры из видео ютуба», где тонна всякой хреновой рекламы и друных схем обмана наивных посетителей. Попал я на такой сайт с айфона.
Встретил забавную штуку: через какое-то время взаимодействия с сайтом (успел вставить скопированную ссылку на видос), появилось уведомление о предложении добавить чего-то там фид-ленту в мой календарь.
Из любопытства и исследовательского взгляда — согласился. И эта херня подписала меня на ical-feed какого-то «новостного агентства», засрав мне весь календарь, блин. Создав отдельный типа событий в календаре и при этом подтягивая данные из своей ленты из внешнего источника. Забавная же хрень. Я тоже такую захотел.
Понравилось. Как идея того, что можно сделать подписку на ленту мероприятий. Как раз то, что нужно мне в проекте.
Контекст истории в проекте начинается там, где есть вот такой дизайн статус-шапки с информацией о событии:

Даты проведения и длительность, место проведения и/или формат (очное или онлайн) ну и кнопка-действие. В первом экране, отвечает сразу на все базовые вопросы
Аккуратно свёрстано и работает. Во время вёрстки придумал, что для очного формата элемент с адресом будет кликабельным и открывать Яндекс.Карты с прокинутым в поиск, адресом. Урлы Яндекс.Карты умеют получать адрес и искать просто из ссылки с параметром ?text=:
<!--
Параметр `z=16` — это масштаб карты (zoom),
z=20, например покажет отдельное здание
-->
<a href="https://maps.yandex.ru/?text=Санкт-Петербург, Тверская 10&z=16"
target="_blank"
title="Подскажем адрес на Яндекс.Картах">
Санкт-Петербург, ...
</a>
Включил это для тач-устройств в первую очередь: опыт дружелюбнее. В большинстве случаев после тапа, сразу откроется приложение Яндекс.Карт на телефоне. Если приложения на устройстве нет, то откроется браузер с сайтом карт сразу с адресом.
Захотелось также сделать что-то приятно-незаметное (пока не кликнешь, не знаешь, что о тебе позаботились и сценарий продумали уже) и для элемента с датой. Самый очевидный вариант — добавлялка напоминания в календарь.
На самых популярных порталах добавление в календарь не встречается в интерфейсе вовсе. Скорее там везде один паттерн: призыв к добавлению будет после действия — записи или покупки.
Также, как и с билетами на самолёт, например. В письме с маршрутной квитанцией обязательно будут ссылки, типа «Добавить в Apple Wallet» и добавлялки в календари.
Так что актуальных примеров для вдохновения я там не нашёл. Поискал скрины и в своих письмах по истории покупок билетов, понятны стали самые популярные варианты календарей:
Подглядел в TimePad. Если смотреть их стандартную «Афишу» — клик по блоку с датой скроллит к этажу выбора билетов. А вот идея с оформлением даты, как календарика — понравилась. Кнопок-действий для добавления в календарь на странице отдельного события нет (апрель, 2024). Но есть в письмах после регистрации или оплаты:

Ссылки в письме TimePad. С отдельной явной сноской о необходимости авторизации в Google-календаре.
А если смотреть на страницу отдельного организатора мероприятий, то в блоке даты есть ссылка «Добавить в календарь» с выпадайкой и выбором вариантов. Любопытно, что такого нет на других отдельных страницах.

Отдельная страница организатора TimePad и контрол на стрнице в сайтбаре с выпадайкой
Технические вводные у задачи: WordPress, PHP, свой шаблон и полный контроль над кодом, часть данных из админки доставать надо через поля Advanced Custom Fields (ACF).
У продукта Google Calendar квадратная по пропорциям иконка и внутри текстом указывается актуальный день месяца. В favicon calendar.google.com — тоже подставляется актуальный день, например, такое:
<link
rel="icon"
id="favicon"
type="image/x-icon"
href="https://calendar.google.com/googlecalendar/images/favicons_2020q4/calendar_27.ico"
>
Где «27» в имени файла calendar_27.ico — день месяца. Ещё очень интересна структура УРЛа. Что ж там такое ещё есть в favicons_2020q4.
Чтобы добавить какое-то событие в календарь — была (!) ссылка http://www.google.com/calendar/render? с обширным списком параметров для события. Почему-то из официальной документации хоть какая-то полезная информация была удалена и все всегда во всех вопросах ссылаются на вебархивную ссылку из 2012-го (!): Share your events with an individual, a group, or the whole world.
Интернет очень странная штука, конечно. На этой же архивной странице есть генератор ссылок. Работает и спустя 12 лет.
А современные официальные ссылки документации и раздела помощи гугла показывают что-то бесполезное.
Параметры описаны на странице Instructions for making Google Calendar event reminder buttons, тоже вебархивная ссылка. Там параметры обозначают термином «CGI parameters».
Базовый набор:
# тут всегда только TEMPLATE, так сказано в доке
action=TEMPLATE
# название события
text="Публикация поста и ссылках на календари"
# местоположение
location="https://sglazov.ru"
# описание, можно простые HTML-теги
details="Я публикую пост, это торжественно!"
# дата, в Ymd\THis + `Z` после времени
dates=20240517T190000Z/20240517T200000Z
И всё это можно превратить в набор параметров для ссылки, правда ссылка-пример из этой самой [документации] — не работает, почему-то в параметрах запроса есть лишние символы точки с запятой: text;=, &details;= ну и т.п. А конструктор кнопок — работает, но выбор года там только до 2020.
Ну и актуальный УРЛ для добавления мероприятий и формирования полной ссылки — https://calendar.google.com/calendar/r/eventedit, параметры передаются гетом.
SVG-иконку для фона взял случайную, сам в фигме удалил фон. При вёрстке поставил блок с текстом по центру размеров иконки и подогнал визуально размер шрифта, чтобы две цифры даты не поломали габариты блока. Ещё какое-то время понадобилось уделить тому, чтобы выяснить, параметр &ctz=Europe/Moscow для часового пояса обязателен.
<?php
// переменные $dateStart, $dateEnd, $speakerName, $title, $formatOffline, $city объявлены выше, там ничего интересного
// форматирует даты
$calendarDataStart = date('Ymd\THis', strtotime( $dateStart ));
$calendarDataEnd = date('Ymd\THis', strtotime( $dateEnd ));
$calendarDetails = "Преподаватель: {$speakerName}. Описание и программа: https://site.tld/events/" . get_the_id() . "/";
$calendarUrl = 'https://calendar.google.com/calendar/r/eventedit';
$calendarUrl .= '?text=' . htmlspecialchars( $title ); // название мероприятия
$calendarUrl .= '&ctz=Europe/Moscow'; // часовой пояс обязательный
$calendarUrl .= '&dates=' . $calendarDataStart . '/' . $calendarDataEnd;
$calendarUrl .= '&details=' . htmlspecialchars($calendarDetails);
// если событие в оффлайне и в админке указан город
if ( $formatOffline && $city )
{
$calendarAddress = $city;
// добавляем параметр `location` с адресом к урлу календаря
$calendarUrl .= '&location=' . htmlspecialchars($calendarAddress);
}
?>
<li class="dropdown-menu__item">
<a class="add-to-calendar-link"
href="<?= $calendarUrl; ?>"
title="Календарь откроется в новом окне"
target="_blank">
<div class="add-to-calendar-link__symbol">
<svg role="img"
width="22"
height="22">
<use xlink:href="#i_google-calendar-blank"></use>
</svg><!-- /.w-icon__symbol -->
<div class="add-to-calendar-link__day">
<?= date('j', strtotime( $dateStart ) ); ?><!-- день месяца в стилизованной иконке календаря -->
</div><!-- /.add-to-calendar-link__day -->
</div><!-- /.add-to-calendar-link__symbol -->
<div class="add-to-calendar-link__text">
Google Календарь
</div><!-- /.add-to-calendar-link__text -->
</a><!-- /.add-to-calendar-link -->
</li><!-- /.dropdown-menu__item -->
И всё прекрасно заработало.
Стоит упомянуть, что «i» в названии формата iCalendar, созданного в 1998/2009, не имеет отношение к манере Apple называть свои продукты. В случае с iCalendar — это Internet Calendaring and Scheduling Core Object Specification (iCalendar).
Формат представляет собой набор параметров вида ключ-значение. В этой реализации все параметры орут и написаны прописными. В 199* так было модно? Файл на десятка два событий, визуально превращается в мешанину из капса, цифр и неожиданных двоеточий.
Пример структуры для отдельного события:
BEGIN:VCALENDAR
PRODID:-//Timepad Ltd.//NONSGML Timepad//RU
VERSION:2.0
METHOD:PUBLISH
BEGIN:VEVENT
UID:3414132849538
DTSTART:20240517T190000
DTEND:20240517T200000
DTSTAMP:20240415T000102
SUMMARY:SPB Frontend Митап #36
DESCRIPTION:[...] традиционная встреча веб-разработчиков в Санкт-Петербурге [...]
LOCATION:Санкт-Петербург, Цветочная улица, дом 19
END:VEVENT
END:VCALENDAR
Формат состоит из компонент-секций BEGIN:-END:. Версия является обязательной и всегда указывается вторая. Сомневаюсь, что она будет когда-то меняться, но поскольку есть совсем старинная первая, то указывается вторая.
Вместо TITLE использовали вариант SUMMARY, ну ок. METHOD:PUBLISH — это про событие, которое явно случится.
Отдельных VEVENT внутри *.ics может быть произвольное количество, не только одно событие.
id.ics, где id — уникальный ID мероприятия, ну можно префикс ещё добавить;*.ics;Ещё я хотел отдельную ленту всех мероприятий в формате iCal. Придумал, что можно сделать кастомную RSS-ленту в WordPress, которая может принимать на вход один ID параметром, типа ?event=<?= get_the_id(); ?> и формировать файл для отдельного мероприятия. Ну и без параметра урл вида /feed/ical/ будет отдавать всегда актуальный общий список мероприятий в формате iCalendar. Два в одном типа.
Кастомную RSS-ленту можно собрать через WordPress-хук add_feed(). Писал про этот хук на Хабре в 2019-м: Кастомная авторассылка в MailChimp из RSS-ленты.
Скачать файл сразу поможет и сам браузер, но лучше ему помочь и указать заголовок Content-Disposition: attachment; при ответе.
Файл feed-ical.php:
// Добавляет новую ленту `/feed/ical`
add_action('init', 'iCalendarFeed');
function iCalendarFeed()
{
add_feed('ical', 'iCalendarFeedTemplate');
}
// Шаблон ленты
function iCalendarFeedTemplate()
{
get_template_part('feed', 'ical');
}
Подключить в файле функций темы functions.php:
// лента для Apple Календаря
require get_template_directory() . '/core/custom/feed-ical.php';
Ну, у меня оно так разложено по своей структуре и собирается через require под полным моим контролем.
<li class="dropdown-menu__item">
<a class="add-to-calendar-link"
href="<?= get_feed_link('ical'); ?>?event=<?= get_the_id(); ?>"
title="Файл события скачается"
target="_blank"
download="event-<?= get_the_id(); ?>.ics">
<div class="add-to-calendar-link__symbol">
<div class="apple-calendar">
<div class="apple-calendar__week">
<?= date_i18n('D', strtotime( $dateStart ) ); ?>
</div><!-- /.apple-calendar__week -->
<div class="apple-calendar__date">
<?= date('j', strtotime( $dateStart ) ); ?>
</div><!-- /.apple-calendar__date -->
</div><!-- /.apple-calendar -->
</div><!-- /.add-to-calendar-link__symbol -->
<div class="add-to-calendar-link__text">
iCal (Календарь Apple)
</div><!-- /.add-to-google-calendar__text -->
</a><!-- /.add-to-calendar-link -->
</li><!-- /.dropdown-menu__item -->
WordPress-функция date_i18n() позволяет из коробки работать с локализацией дат без магии. Ну и формат D выведет сокращённый двухбуквенный вариант дня недели. Как в иконке календаря на айфоне. А на макос иконка позывает месяц + день.
<?php
// имя файла
$file = urlencode( "event-" . date('Y-m-d') . ".ics" );
// это — календарь, в UTF-8
header('Content-type: text/calendar; charset=utf-8');
// и файл надо скачать
header("Content-Description: File Transfer");
// имя этому файлу объявлено
header("Content-Disposition: attachment; filename={$file}");
// помогает всегда отдавать только свежее, игнорируя кэш
header("Expires: 0");
// если `event=ID`, то карточку собираем для него
if ( isset($_GET['event']) && $_GET['event'] )
{
$getEvent = $_GET['event'];
}
$args = array(
'p' => $getEvent, // ID мероприятия из запроса
'post_type' => 'events', // мероприятия
'posts_per_page' => -1, // все
);
$getEvents = new WP_Query($args);
// в стандартном цикле WP перебираем всё, что нужно
if ( $getEvents->have_posts() ) :
?>
BEGIN:VCALENDAR
VERSION:2.0
CALSCALE:GREGORIAN
PRODID:-//RU
NAME:БРЕНД
X-WR-CALDESC:Краткое описание календаря
TZID:Europe/Moscow
<?php
// в цикле собирает отдельный компонент `BEGIN:VEVENT-END:VEVENT`
while ( $getEvents->have_posts() ) :
// ... тут из acf/wp достаются нужные поля
ob_start(); ?>
BEGIN:VEVENT
SUMMARY:<?= $title; ?>
UID:<?= uniqid(); ?>
DTSTAMP:<?= $timestamp; ?>
DTSTART:<?= $dateStart; ?>
DTEND:<?= $dateEnd; ?>
LOCATION:<?= escapeString($location); ?>
DESCRIPTION:<?= escapeString($description); ?>
END:VEVENT
<?php endwhile; ?>
END:VCALENDAR
<?php
ob_end_clean();
return ob_get_contents();
endif;
?>
В 1996-м году написали спеку vCalendar, где «v» — virtual. vCalendar — формат данных для представления и обмена информацией календаря. В той же спеке было и первое упоминание формата vCard — формата данных для карточки контакта. vCard частично работает до сих и формат 1996 поддерживается в рамках обратной совместимости.
Формат vCalendar позже превратился в iCalendar — vCalendar: The Basis for Cross-Platform Scheduling. В 1998 году появилась спека RFC 2445.
В 2009-м, через два года после выхода первого iPhone, первую спеку прокачали для применения уже в разросшемся вебе — RFC 5545, она и является актуальной. Ещё её правили в 2016-м и в целом, спека — живая.
В 2014 году сделали мапинг формата iCalendar в json-формат в спеке RFC 7265 — jCal. Перед ним в период популярности XML была спека RFC 6321 — xCal, но формат уж слишком многословный получился.
В 1971 году было написано и отправлено первое электронное письмо. Общедоступный WWW и то, во что он превратился создан в 1991-1996 годах. В Россию Рунет пришёл в апреле 1994 года.
Черновик HTML 3.0 опубликован в марте 1995 года. Ну и вот начинается то, куда мы пришли. Есть активное развитие каждой из ключевых технологий и веб пополз даже в другие стороны, захватывая железные части: можно получать доступ к микрофону или геопозиции, акселерометру, сканеру отпечатка пальцев для подтверждения операций и много чего ещё интересного. Тут всё хорошо, и пока не об этом хочется поговорить.
Первые письма с минимальными возможностями технологий тех лет (HTML 4.01 и CSS2) начали использовать только в конце 90-х. В 1997 году появляется почтовый веб-клиент с использованием актуальных веб-технологий. Ну, Hotmail вы точно должны знать — он был первым. Через год после запуска его купит Microsoft и вслед за этим запустит свой почтовый клиент — Outlook. Outlook жил в Windows. Это очень повлияло на его популярность. Но доставило много боли разработчикам. Всё как с Internet Explorer. Кстати. В июне 1999 Microsoft выпускает Outlook 2000 в котором письма рендерит движок их собственного браузера Internet Explorer 6 (эх). В январе 2007 Outlook перешёл с движка Internet Explorer на движок своего другого продукта — MS Word. Сразу после перехода много CSS-свойств в письмах перестали нормально поддерживаться. Совпадение?
Среди первой критики HTML-писем были такие пункты: «съедают» пропускную способность интернет-канала, долго рендерятся. Пост с этой критикой — целая история. И актуальные для тех лет проблемы.
Разработчикам оставалось только бороться и изобретать костыли для решения задач: функциональных, оформительных или кросс-платформенных. Вот самые интересные вариант: встраивание ленты твиттера прямо в письмо, работающая форма заявки, чекбоксы (!) и на их основе слайдеры и блоки-спойлеры.
Например, Gmail (и веб-клиент и приложение) обрежут письмо, превышающее 102 Кб. Отдельно сразу нужно заметить, что делает он это также тогда, когда несколько писем были отправлены с одной и той же темой. 102 Кб — это только про разметку и HTML-код.
Человечество уже столкнулось с каждой проблемой и нашло какое-то решение.
Ситуация такая: есть сайт. Внутри — WordPress и свой мир. У сайта есть команда разработки. Для каждого разработчика придумано своё окружение и работы ведутся на dev-домене. dev0.*.tld, dev2.*.tld, ну и пока не закончатся разработчики или цифры.
Ещё есть обычные зеркала сайта — маркетинг придумал домен в другой доменной зоне, глобальные, региональные и какие угодно. И при этом всё это живёт на одной БД, ну, да. Не спрашивайте. Так бывает. Реальность достаточна сурова и объясняется контекстом (который до конца никому не понятен) или фразой типа: «так устроена жизнь».
И вот я совсем не понимаю, зачем в WordPress в базе данных в пугающе многих местах, захардкожен домен сайта. При варианте разработки «сделал локально → перенёс на удалённый сервер», человечество изобрело для этого костыля другие костыли или просто в лоб заменяют SQL-запросом все совпадения домена. Потрясающе.
WP_SITEURL и WP_HOMEРешение (трюк?) с переопределением констант WP_SITEURL и WP_HOME работают не так, как нужно: они переопределяются, но не изменяются. И чаще всего вы получите при обращении к dev0.*.tld запрос ресурсов, статики и всего, с абсолютным урлом основного домена. На dev-окружениях такое и бесит и недопустимо.
// wp-config.php
define( 'WP_HOME', 'https://' . $_SERVER['HTTP_HOST'] );
define( 'WP_SITEURL', 'https://' . $_SERVER['HTTP_HOST'] );
«Умная переопределялка» для хоста через $_SERVER['HTTP_HOST'] будет вести себя точно также из-за абсолютных ссылок в БД.
/**
* Вторая попытка определения хоста для дев- и прод-доменов
*/
switch ( strtolower( $_SERVER['HTTP_HOST'] ) )
{
case 'dev.7site.tld':
define('WP_SITEURL', 'https://dev.7site.tld');
define('WP_HOME', 'https://dev.7site.tld');
break;
case '7site.tld':
define('WP_SITEURL', 'https://7site.tld');
define('WP_HOME', 'https://7site.tld');
break;
default:
define('WP_SITEURL', 'https://site.tld');
define('WP_HOME', 'https://site.tld');
break;
}
Разумеется, при этом менять что-то в самой БД — нельзя. При этом хочется. А нельзя.
Кажется, логичным: заменять то, что нужно и там, где надо (дорогостоящая операция по производительности). Так уже работает, например, для медиафайлов на dev-доменах: медиа загружены на основной домен, менеджер при тестировании смотрит окружение разработчика, но урл для картинки будет заменён на урл боевого домена:
<img
class="gallery__img"
alt="<?= $item['alt']; ?>"
draggable="false"
src="<?= str_replace( 'dev1.', '', $item['src'] ); ?>">
Выкинуть из кода все переопределения констант.
Создать файл wp-domains.php в корне. Внутри описать магию с ob_start + str_replace.
<?php
/**
* https://github.com/MihanEntalpo/WordpressGitTools/blob/master/wp-domains.php
*/
//Проверим, не запускался ли данный скрипт ранее:
if (defined("WP_DOMAINS_INITIALIZED")) return;
const WP_DOMAINS_INITIALIZED = 1;
//Домены на котором может работать сайт, локальные и удалённые,
$domains_enabled = array(
"site.tld",
"7site.tld",
"dev.7site.tld"
);
//Добавим в массив $_SERVER все необходимые для проверки ключи,
//которых там может не быть
$_SERVER = array_replace(
array(
"HTTPS" => "", "HTTP_HTTPS" => "", "REQUEST_SCHEME" => "", "SERVER_PORT" => ""
),
$_SERVER
);
//Проверим, используется ли https?
$https =
(!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off')
|| (!empty($_SERVER['HTTP_HTTPS']) && $_SERVER['HTTP_HTTPS'] != 'off')
|| $_SERVER['REQUEST_SCHEME'] == 'https'
|| $_SERVER['SERVER_PORT'] == 443;
//Составим адрес хоста
$site_addr = ($https ? "https://" : "http://") . $_SERVER['HTTP_HOST'] . "/";
//Заменим адрес хоста на тот, что мы вычислили
if (!defined("WP_HOME")) define("WP_HOME", $site_addr);
if (!defined("WP_SITEURL")) define("WP_SITEURL", $site_addr);
//Добавим обработчик выдачи сайта, который будет заменять во всех ссылках
//домены на текущий домен
ob_start(function ($data) use ($domains_enabled, $https)
{
$current_host = $_SERVER['HTTP_HOST'];
$replace = array();
$scheme = $https ? "https" : "http";
foreach ($domains_enabled as $domain) {
if ($current_host == $domain) continue;
$replace["http://$domain/"] = "$scheme://$current_host/";
$replace["https://$domain"] = "$scheme://$current_host";
$replace["//$domain/"] = "//$current_host/";
$replace["//$domain"] = "//$current_host";
}
return str_replace(array_keys($replace), array_values($replace), $data);
});
Подключить файл wp-domains.php в файлы wp-config.php и wp-load.php.
require_once __DIR__ . '/wp-domains.php';
Проверено на хостинге timeweb.ru, четырёх доменах-синонимов и трёх поддоменах.
--- ## Хочешь чего-то узнать — включи любопытство **URL**: https://sglazov.ru/notes/curious/ **Дата**: 2022-06-30T00:00:00.000Z **Описание**: Расковырять, узнать побольше, понять, как устроено всёИ не выключай никогда, слыш.
Вот детям такое объяснять даже не надо. А взрослым — почему-то это всё вообще не очевидно. Они всего боятся вокруг. Даже дерево в парке потрогать. Даже себя иногда боятся.
Когда появляется какая-то новая штука — хочется расковырять, узнать побольше, понять, как устроена. И как вообще чего оно там.
Даёшь людям законченную единицу смысла в работе. Изолировано и так, чтобы они ничего не сломали, а если и сломали — это даже плюс. Можно что-то сделать лучше, чем было. А случившаяся поломка бы не задела другие части системы. От админки и до оплат — критичное всё, в общем. А остальное — ломай, изучай, понимай.
А через три месяца узнаёшь, что люди используют три очевидные кнопки и переключатель, а на пункт «развернуть», где ещё есть шесть кнопок даже курсор наводить бояться. Объективного ответа на «почему?» — на находится.
Вспомнил историю на контрасте. Моя тётя. Много годиков. Любопытство к этому возрасту даже проветриваться должно и заменяться поучительной мудростью (неуместной). Учитель математики у кучи избалованных непоседливых детей. И тут вот пандемия, онлайн-уроки, Zoom и его аналоги, вся вот эта херня.
Она попросила мне ей позвонить, сама создала комнаты-конференции, сама изучила, как кикнуть из звонка кого-либо, как стереть с демонстрации экрана нарисованную письку или если кто-то в крестики-нолики играет, или слово «хуй» написал сердечками-символами. Я оставил звонок без звука и её с любопытством. За час она перезванивала трижды и за полтора часа знала, как устроено всё со стороны организатора и как вести себя (куда нажать) в нужной ей ситуации.
Уважуха за любопытство.
И почему так не у всех — я так и не понимаю.
--- ## Исполнитель (сотрудник) — не ксерокс **URL**: https://sglazov.ru/notes/your-opinion/ **Дата**: 2022-06-09T00:00:00.000Z **Описание**: У человека должно быть своё мнение. Обязано бытьВ каком-либо конвейере процесса при передаче штуки в работу человек не является инструментом клонирования результата прошлого звена в цепочке.
У человека должно быть своё мнение. Обязано быть.
И свой взгляд на то, что он делает. Даже если всё ТЗ описали (так не бывает) прям педантично.
Предусмотреть все детали и все ситуации во всех мультивалентных и позаботится о нескольких шагах наперёд. Менеджер или кто-то угодно в цепочке дел может что-то проебать или просто не знать. Ну а ты-то знаешь. Добавь своего видения.
Всегда был убеждён, кто каждый человек заинтересован делать что-то хорошее в первую очередь для себя.
Разгрузить своё время, позаботиться о себе так, чтобы результат был прекрасным.
Круто работать с теми, кому не всё равно.
--- ## Laravel на виртуальном хостинге (shared hosting) TimeWeb **URL**: https://sglazov.ru/notes/laravel-in-shared-timeweb/ **Дата**: 2021-11-25T00:00:00.000Z **Теги**: Laravel, PHP **Описание**: Поставить Composer, указать правильную версию PHP, создать симлинк на public_html и добавить конфиг для GitHub ActionsТак случилось, что хостинг клиента — виртуальный (shared) на TimeWeb. Тариф ничем не выделяется.
Внутри есть SSH, БД, вообще всё, что нужно и задача в целом, реальная и решаемая.
В терминале версия PHP5.6:
client@vh2**:~/site.tld$ php -v
PHP 5.6.37 (cli) (built: Jul 24 2018 00:54:11)
А нужна минимум PHP7.3. В настройках каждого сайта в личном кабинете хостера можно указать требуемую версию PHP, но вот терминалу на это плевать.
В терминале можно указать путь к исполняемому пакету с нужной версией PHP. Laravel ставится через Composer, его надо скачать и положить рядом.
php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
...
php -r "unlink('composer-setup.php');"
В корне появился composer.phar. Ставим Laravel, выбирая нужную версию PHP:
/opt/php73/bin/php composer.phar create-project laravel/laravel
Устанавливается без каких-либо проблем. Важно знать, что к artisan обращаться надо также:
/opt/php73/bin/php artisan optimize
У проектов в таймвебе паблик-директория — ./public_html. А Laravel торчит наружу из ./public в корне файловой структуры. Чтобы это просто завести на таймвебе — можно обойтись симлинком:
ln -s ~/site.tld/public ~/site.tld/public_html
В Marketplace GitHub Actions есть уже куча пакетов для Laravel. Моя задача была простой и я обошёлся rsync-deployments:
# .github/workflows/timeweb-deploy.yml
name: Deployment site.tld
on:
push:
branches:
- main
jobs:
deploy:
runs-on: ubuntu-latest
if: "!contains(github.event.head_commit.message, 'skip ci')"
steps:
- name: Загрузка актуальной кодовой базы
uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: "14"
- name: Создаёт .env-файл
run: echo "$" > ".env"
# Install Сomposer
- name: Установка Сomposer-зависимостей
run: composer install --optimize-autoloader --no-dev
# Artisan cli commands
- name: Трудится, artisan
run: |
php artisan clear-compiled
php artisan optimize
php artisan migrate
# Install NPM
- name: Установка NPM-зависимостей и сборка статики
run: |
npm ci
npm run prod
# Deploy
- name: Публикация проекта site.tld
uses: burnett01/rsync-deployments@4.1
with:
switches: -az --delete --exclude-from="rsync-excludes.txt"
path: .
remote_path: $
remote_host: $
remote_user: $
remote_key: $
Все параметры, как и .env-конфиг для production-окружения, будут взяты из Actions secrets.
Вот представим. Вы верстаете. И есть штука, которая должна показаться/скрываться по клику на какой-либо элемент. Не модалка, а клик-подсказки или кнопка-меню.
Конструкция, упрощённо (но обязательно с aria-атрибутами!), такая:
<!-- контрол -->
<button class="ui-button"
type="button"
id="ui-menu"
aria-haspopup="true"
aria-expanded="false">
Добавить в...
</button>
<!--
нечто, что реагирует на действие
с контролом. и оно скрыто
-->
<ul class="ui-menu is-hidden"
aria-labelledby="ui-menu">
Меню с пунктами...
</ul>
И вот самое важное как раз в классе is-hidden.
Исходим всегда из того, что в начальном состоянии элемент всегда скрыт. А действие с контролом — его показывает.
Нужно удалять модификатор скрытия, а не добавлять модификатор показа.
$(document).ready(function()
{
$('.ui-button')
.on('click', function ()
{
$('.ui-menu')
.removeClass('is-hidden');
});
});
Разметка — чище. Из разметки сразу понятно базовое состояние «всё скрыто». Проще дебажить.
Стили также пишутся исходя из мысли, что элемент всегда виден, а модификатор — скрывает. С анимацией там или ещё с чем угодно.
.ui-menu {
opacity: 1;
visibility: visible;
// скрывалка
&.is-hidden {
opacity: 0;
visibility: hidden;
}
}
Есть прекрасный CloudPayments для приёма платежей. Он умеет в выгрузку списка транзакций из интерфейса. В разделе «Транзакции» (в меню слева) над списком есть кнопка «Экспорт транзакций», по клику вам отправят на почту *.csv-файл за месяц.
Это такой ручной вариант: надо авторизоваться в личном кабинете, запросить файл, забрать его из почты, куда-то там загрузить. Долго и сложно. И это нужно делать бухгалтерии, им итак несладко, а тут ещё это.
Хочется автоматического варианта. Чаще для автоматизации 1С (или любого интегратора) + CloudPayments.
csv-файл (он прост, с ним легко работать и его понимают все), надо его как-то создаватьЧерез API от CloudPayments, конечно. Документация прекрасна. То, что нам нужно описано тут: https://developers.cloudpayments.ru/#vygruzka-spiska-tranzaktsiy
/**
* Выгрузка списка транзакций
*
* https://developers.cloudpayments.ru/#vygruzka-spiska-tranzaktsiy
*
* API Lib: https://github.com/flowwow/cloudpayments-php-client
*/
$today = date('Y-m-d');
$publicId = '';
$pass = '';
$apiClient = new \Flowwow\Cloudpayments\Library($publicId, $pass);
$response = $apiClient->getListPayment(
new \Flowwow\Cloudpayments\Request\PaymentsList(
'2021-06-05',
'MSK'
)
);
$list = $response->model;
$publicId и $pass — ваши «секреты» из настроек сайта в личном кабинете.
В общем-то, это всё. Спасибо за лёгкость библиотеке для работы с API. Люблю прям опенсорс за такое.
Вы получите json со списком транзакций за сутки.
Дальше нужно из этого ответа собрать csv-файл. По пути можно чего-то там отфильтровать (например, собрать формат только сумма + название платежа, всё остальное неинтересно). Так вот, собрать файл и его куда-то положить. Ещё настроить автозапуск скрипта, чтобы файл создавался каждый день.
Чаше всего я использую в проектах стандартный набор системных шрифтов:
font-family: -apple-system, BlinkMacSystemFont,
'Segoe UI', Roboto,
'Helvetica Neue', Helvetica,
Ubuntu,
Cantarell,
Arial, sans-serif,
'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
Один из них точно «попадёт» и текст на сайте будет выглядеть привычно для посетителя, как в его операционной системе.
А нестандартные шрифты чаще всего рутина и боль. Я стараюсь организовать свой процесс, рассказываю, как.
Для начала нужно скачать нужные начертания и положить к себе так, чтобы потом ими было удобно манипулировать.
Google Fonts позволяет скачивать шрифты со страницы описания и отдаёт *.ttf-вариант. Вверху справа есть кнопка «Download family».
Далее каждую гарнитуру размещаю в папке с названием, соответствующим имени гарнитуры. Внутри папки — файлы с именем по начертанию. Стандартные имена файлов шрифтов с начертаниям сложны и многословны.
├── fonts
│ └── alegreya-sans
│ ├── ...
│ ├── AlegreyaSans-Regular.ttf # → normal.ttf
│ ├── AlegreyaSans-MediumItalic.ttf # → 500-italic.ttf
│ ├── AlegreyaSans-Medium.ttf # → 500.ttf
│ ├── AlegreyaSans-Bold.ttf # → bold.ttf
│ ├── AlegreyaSans-BlackItalic.ttf # → 900-italic.ttf
│ ├── AlegreyaSans-Black.ttf # → 900.ttf
Обычно bold и normal оставляю в буквенном варианте. И в CSS использую также буквенный вариант.
После переименования нужно сконвертировал «тяжёлый» *.ttf в лёгкие *.woff и *.woff2, которые являются архивами.
В редких случаях лучше использовать google-webfonts-helper — там есть все нужные и правильные варианты.
*.woff/2 из *.ttf-файловРаньше я пользовался разными онлайн-инструментами, но увидев консольный способ в докладе Вадима Макеева «_ ___ ______?», стал использовать только его:
# поставить пакет
brew tap bramstein/webfonttools
brew install woff2 sfnt2woff-zopfli
# перейти в папку со шрифтами
cd app/src/fonts/
# сконвертировать ttf
woff2_compress 500.ttf # → 500.woff2
sfnt2woff-zopfli 500.ttf # → 500.woff
Увы, инструменты @bramstein/webfonttools для генерации не умеют работать пакетно или по маске типа/имени файла. Каждый *.ttf-файл нужно указать в ручном режиме. Дважды.
Всё, что нужно для подключения шрифта в стилях — это имя шрифта и набор его начертаний. Именно это я и указываю в карте шрифтов.
Из имени шрифта получается имя директории со шрифтом — пробелы заменяются на дефисы и регистр меняется на строчные. Всё это происходит в функции slug(string).
$global-font-path : '/fonts/'; // где вообще все шрифты в проекте
// имя шрифтов и их начертания
$fonts: (
"Alegreya Sans" : ( 300, normal, 500, bold ),
"Fira Code" : ( 100, normal, bold )
);
@mixin font-face($font, $weight) {
// каждый стиль: обычный и курсив
@each $style in (normal, italic) {
$path : slug($font);
// если курсив, то добавить суффикс
$font-file: if( $style == italic, $weight + '-italic', $weight);
// повторить этот блок
@font-face {
font-family: $font;
font-display: swap; // это важно для отрисовки
font-weight: $weight;
font-style: $style;
src:
url('#{$global-font-path}#{$path}/#{$font-file}.woff2') format('woff2'),
url('#{$global-font-path}#{$path}/#{$font-file}.woff') format('woff');
}
}
}
// для каждого шрифта и начертаний..
@each $font, $weight in $fonts {
// при разном количестве указания начертаний меняется тип переменной в SCSS o_O
// проверяем это и если > 1, list — проходим по всем в цикле...
@if type-of($weight) == list {
@each $weight in $weight {
@include font-face($font, $weight);
}
// если =1, используем это значение
} @else {
@include font-face($font, $weight);
}
}
Разумеется, подключаются только современные форматы. И используется font-display: swap;, чтобы шрифт отображался сразу.
Как только возникает необходимость подключить какой-то шрифт:
*.woff2- и *.woff,./app/src/fonts/,$fonts добавляем имя шрифта и необходимые начертания.Я предзагружаю файлы базовых начертаний только на главной странице через preload:
<link rel="preload"
href="/fonts/alegreya-sans/normal.woff2"
as="font"
type="font/woff2"
crossorigin
/>
<link rel="preload"
href="/fonts/alegreya-sans/bold.woff2"
as="font"
type="font/woff2"
crossorigin
/>
Не забывайте про атрибут crossorigin.
Подробнее о предзагрузке любых ресурсов — в посте Ивана Акулова: «Preload, prefetch and other <link> tags» (английский, или на русском на Хабре).
@mixin и @each для генерации нужных правил @font-face;glyphhanger для конвертации и сабсеттинга веб-шрифтов из командной строкиОбновлено 22.09.2023: добавлена ссылка на WOFF Has Left the Building.
Всего событий сейчас 12 (актуально на октябрь 2018 года). Полный список можно найти по ссылкам-первоисточникам в разделе чуть ниже. У людей много вопросов появляется в духе, что именно за код, как его добавить, что делать, причём тут пример для Google Tag Manager и т.п.
Всё проще, чем кажется.
Вам важно знать id товара из прайс-листа. Его видно в меню «Ретаргетинг» рекламных кампаний в ВК.
В примерах ниже цифры 15901 — это и есть id прайс-листа. Замените на свой.
view_home<script>
window.vkAsyncInit = function() {
VK.Retargeting.ProductEvent(15901, "view_home", {});
console.log('VK pixel event send: view_home');
}
</script>
view_search<script>
window.vkAsyncInit = function() {
VK.Retargeting.ProductEvent(15901, "view_search", {"search_string": "ПОИСКОВЫЙ ЗАПРОС"});
console.log('VK pixel event send: view_search');
}
</script>
view_category<script>
window.vkAsyncInit = function() {
VK.Retargeting.ProductEvent(15901, "view_category", "");
console.log('VK pixel event send: view_category');
}
</script>
view_other<script>
window.vkAsyncInit = function() {
VK.Retargeting.ProductEvent(15901, "view_other", {});
console.log('VK pixel event send: view_other');
}
</script>
add_payment_info<script>
window.vkAsyncInit = function() {
VK.Retargeting.ProductEvent(15901, "add_payment_info", {});
console.log('VK pixel event send: add_payment_info');
}
</script>
purchase<script>
window.vkAsyncInit = function() {
VK.Retargeting.ProductEvent(15901, "purchase", {});
console.log('VK pixel event send: purchase');
}
</script>
add_payment_info<script>
window.vkAsyncInit = function() {
VK.Retargeting.ProductEvent(15901, "add_payment_info", {});
console.log('VK pixel event send: add_payment_info');
}
</script>
init_checkout<script>
window.vkAsyncInit = function() {
VK.Retargeting.ProductEvent(15901, "init_checkout", {});
console.log('VK pixel event send: init_checkout');
}
</script>
view_product<script>
window.vkAsyncInit = function() {
VK.Retargeting.ProductEvent(15901, "view_product", {"products": [{"id": "ID ИЛИ АРТИКУЛ ПРОДУКТА"}]} );
console.log('VK pixel event send: view_product');
}
</script>
add_to_cartonclick="VK.Retargeting.ProductEvent(15901, "add_to_cart", {"products": [{"id": "ID ИЛИ АРТИКУЛ ПРОДУКТА"}]});return false;"
remove_from_cartonclick="VK.Retargeting.ProductEvent(15901, "remove_from_cart", {"products": [{"id": "ID ИЛИ АРТИКУЛ ПРОДУКТА"}]});return false;"
Smarty, то не забывайте оборачивать JS-код в шаблонах в теги {literal}...{/literal}Куча сайтов в своих статьях, по сути, ссылается на эти материалы и/или пересказываю их, но не отвечают на вопрос «как?».
--- ## Дополнительные ресурсы - **RSS Feed**: https://sglazov.ru/notes/feed/ - **Sitemap**: https://sglazov.ru/sitemap.xml - **Robots.txt**: https://sglazov.ru/robots.txt - **Карта сайта для LLM**: https://sglazov.ru/llms.txt --- *Сгенерировано автоматически с сайта sglazov.ru. Последнее обновление: 2026-07-14T11:59:52.000Z*