Как это работает:
Определение типа страницы: Скрипт проверяет, является ли текущая страница статьей, новостью или другим основным контентом (например, ищет теги article, .post и т.д.). Если это не такая страница, скрипт бездействует.
Запоминание визита:
Когда пользователь заходит на подходящую страницу, скрипт записывает текущую дату и время в локальное хранилище браузера (это как небольшая память, которая хранится только в вашем браузере).
Отображение уведомления:
При следующем визите на ту же страницу, скрипт:
Проверяет, есть ли запись о предыдущем посещении.
Если запись есть и она не слишком старая (по умолчанию, не старше 180 дней), то он показывает небольшое, стильное уведомление сверху контента: «Вы уже читали эту страницу.
Последний визит: [дата и время]».
Кнопка «Скрыть» позволяет убрать это уведомление, если оно мешает.
Очистка старых записей: Записи о посещениях, которым более 180 дней, автоматически удаляются, чтобы не засорять память браузера.
Главные плюсы:Улучшение пользовательского опыта: Помогает посетителям быстро понять, новый ли это контент для них.
Автоматизация: Не требует от пользователя никаких действий для отслеживания.
Легкость интеграции: Просто добавьте скрипт на сайт, и он начнет работать.
Настраиваемость: Можно легко изменить срок хранения информации о посещениях (180 дней).
Код
<style>.uc-read-flag {
position: relative;
margin: 14px 0;
padding: 12px 14px;
border-radius: 12px;
background: rgba(0, 0, 0, 0.06);
border: 1px solid rgba(0, 0, 0, 0.10);
line-height: 1.35;
font-size: 14px;
}
@media (prefers-color-scheme: dark) {
.uc-read-flag {
background: rgba(255, 255, 255, 0.06);
border: 1px solid rgba(255, 255, 255, 0.12);
}
}
.uc-read-flag__title {
font-weight: 700;
margin: 0 0 4px 0;
font-size: 15px;
}
.uc-read-flag__meta {
opacity: 0.85;
margin: 0;
}
.uc-read-flag__btn {
position: absolute;
top: 10px;
right: 10px;
border: 0;
border-radius: 10px;
padding: 6px 10px;
cursor: pointer;
background: rgba(0, 0, 0, 0.10);
font-size: 13px;
}
@media (prefers-color-scheme: dark) {
.uc-read-flag__btn {
background: rgba(255, 255, 255, 0.12);
}
}</style>
position: relative;
margin: 14px 0;
padding: 12px 14px;
border-radius: 12px;
background: rgba(0, 0, 0, 0.06);
border: 1px solid rgba(0, 0, 0, 0.10);
line-height: 1.35;
font-size: 14px;
}
@media (prefers-color-scheme: dark) {
.uc-read-flag {
background: rgba(255, 255, 255, 0.06);
border: 1px solid rgba(255, 255, 255, 0.12);
}
}
.uc-read-flag__title {
font-weight: 700;
margin: 0 0 4px 0;
font-size: 15px;
}
.uc-read-flag__meta {
opacity: 0.85;
margin: 0;
}
.uc-read-flag__btn {
position: absolute;
top: 10px;
right: 10px;
border: 0;
border-radius: 10px;
padding: 6px 10px;
cursor: pointer;
background: rgba(0, 0, 0, 0.10);
font-size: 13px;
}
@media (prefers-color-scheme: dark) {
.uc-read-flag__btn {
background: rgba(255, 255, 255, 0.12);
}
}</style>
Код
<script>
(function () {
'use strict';
var KEEP_DAYS = 180;
var SAVE_AFTER_MS = 2500;
function nowMs() { return Date.now(); }
function pad2(n) { return (n < 10 ? '0' : '') + n; }
function formatDate(ts) {
var d = new Date(ts);
return pad2(d.getDate()) + '.' + pad2(d.getMonth() + 1) + '.' + d.getFullYear() +
' ' + pad2(d.getHours()) + ':' + pad2(d.getMinutes());
}
function safeJsonParse(s) {
try { return JSON.parse(s); } catch (e) { return null; }
}
function normUrl(u) {
try {
var x = new URL(u, location.href);
return x.origin + x.pathname.replace(/\/+$/, '');
} catch (e) {
return (u || '').split('#')[0].split('?')[0].replace(/\/+$/, '');
}
}
function getPageKey() {
var canonical = document.querySelector('link[rel="canonical"]');
var base = canonical && canonical.href ? canonical.href : location.href;
return normUrl(base);
}
function isEntryPage() {
if (document.querySelector('.eMessage')) return true;
if (document.querySelector('article, .entry, .post, .article-post')) return true;
var og = document.querySelector('meta[property="og:type"][content="article"]');
return !!og;
}
function findEntryNode() {
return document.querySelector('article.article-post') ||
document.querySelector('.article-post') ||
document.querySelector('.post') ||
document.querySelector('.entry') ||
document.querySelector('.eMessage') ||
document.querySelector('article');
}
if (!isEntryPage()) return;
var entryNode = findEntryNode();
if (!entryNode) return;
var STORAGE_KEY = 'uc_read_flag:' + getPageKey();
var prevRaw = localStorage.getItem(STORAGE_KEY);
var prev = prevRaw ? safeJsonParse(prevRaw) : null;
if (prev && prev.last && (nowMs() - prev.last) > KEEP_DAYS * 86400000) {
localStorage.removeItem(STORAGE_KEY);
prev = null;
}
if (prev && prev.last) {
var el = document.createElement('div');
el.className = 'uc-read-flag';
el.innerHTML =
'<div class="uc-read-flag__title">Вы уже читали эту страницу.</div>' +
'<p class="uc-read-flag__meta">Последний визит: <b>' + formatDate(prev.last) + '</b>.</p>' +
'<button class="uc-read-flag__btn" type="button">Скрыть</button>';
el.querySelector('.uc-read-flag__btn').onclick = function () {
el.remove();
};
entryNode.parentNode.insertBefore(el, entryNode);
}
setTimeout(function () {
try {
localStorage.setItem(STORAGE_KEY, JSON.stringify({ last: nowMs() }));
} catch (e) {}
}, SAVE_AFTER_MS);
})();
</script>
(function () {
'use strict';
var KEEP_DAYS = 180;
var SAVE_AFTER_MS = 2500;
function nowMs() { return Date.now(); }
function pad2(n) { return (n < 10 ? '0' : '') + n; }
function formatDate(ts) {
var d = new Date(ts);
return pad2(d.getDate()) + '.' + pad2(d.getMonth() + 1) + '.' + d.getFullYear() +
' ' + pad2(d.getHours()) + ':' + pad2(d.getMinutes());
}
function safeJsonParse(s) {
try { return JSON.parse(s); } catch (e) { return null; }
}
function normUrl(u) {
try {
var x = new URL(u, location.href);
return x.origin + x.pathname.replace(/\/+$/, '');
} catch (e) {
return (u || '').split('#')[0].split('?')[0].replace(/\/+$/, '');
}
}
function getPageKey() {
var canonical = document.querySelector('link[rel="canonical"]');
var base = canonical && canonical.href ? canonical.href : location.href;
return normUrl(base);
}
function isEntryPage() {
if (document.querySelector('.eMessage')) return true;
if (document.querySelector('article, .entry, .post, .article-post')) return true;
var og = document.querySelector('meta[property="og:type"][content="article"]');
return !!og;
}
function findEntryNode() {
return document.querySelector('article.article-post') ||
document.querySelector('.article-post') ||
document.querySelector('.post') ||
document.querySelector('.entry') ||
document.querySelector('.eMessage') ||
document.querySelector('article');
}
if (!isEntryPage()) return;
var entryNode = findEntryNode();
if (!entryNode) return;
var STORAGE_KEY = 'uc_read_flag:' + getPageKey();
var prevRaw = localStorage.getItem(STORAGE_KEY);
var prev = prevRaw ? safeJsonParse(prevRaw) : null;
if (prev && prev.last && (nowMs() - prev.last) > KEEP_DAYS * 86400000) {
localStorage.removeItem(STORAGE_KEY);
prev = null;
}
if (prev && prev.last) {
var el = document.createElement('div');
el.className = 'uc-read-flag';
el.innerHTML =
'<div class="uc-read-flag__title">Вы уже читали эту страницу.</div>' +
'<p class="uc-read-flag__meta">Последний визит: <b>' + formatDate(prev.last) + '</b>.</p>' +
'<button class="uc-read-flag__btn" type="button">Скрыть</button>';
el.querySelector('.uc-read-flag__btn').onclick = function () {
el.remove();
};
entryNode.parentNode.insertBefore(el, entryNode);
}
setTimeout(function () {
try {
localStorage.setItem(STORAGE_KEY, JSON.stringify({ last: nowMs() }));
} catch (e) {}
}, SAVE_AFTER_MS);
})();
</script>
Если у тебя другой контейнер статьи, просто добавь его в функцию findEntryNode().
Например, если статья лежит в .article-post-content, добавь первой строкой:
Код
return document.querySelector('.article-post-content') || ...
Логика скрипта от этого не ломается.