Дата: Среда, 22.10.2025, 14:47 | Сообщение # 1 |
|
Написал: Начинающий
Автор темы
Мурчанн
не в сети
Сообщений: 101
Парсер это скрипт, который получает данные с другой страницы (в данном случае с форума), извлекает нужные элементы (заголовок темы, картинку, краткий текст) и отображает их в виде карточек. На Юкозе лучше всего выводить нужную информацию через парсинг или RSS. Все стандартные модули-информеры UCOZ имеют большие ограничения — другими словами, это практически бесполезно.Он работает прямо в браузере пользователя , это называется клиентский парсер. На чём написан: Это JavaScript (ES6+) чистый JS без серверной части, Работает прямо на странице форума, сайта.Для кэширования используется localStorage браузера, чтобы не перегружать форум каждый раз. Особенности моего решения: Клиентский парсер не требует PHP, базы данных или стороннего хостинга.Кэширование через localStorage , данные обновляются раз в 30 минут, что снижает нагрузку. Карточки под дизайн форума , фиксированный размер, название темы, картинка, короткое описание.Ограничение на количество карточек максимум 8 штук, 4 в ряд Вы можете изменить настройки вручную: в скрипте количество загружаемых карточек, в CSS дизайн самих карточек. Вставляйте код на главную страницу вашего каталога файлов или новостей.Если вам нужны какие-то особенные карточки, напишите ниже, какие именно, чтобы мы сделали их под ваш сайт. Все настройки (URL форума, количество карточек, кэш) можно вынести в объект конфигурации, меняйте на свои параметры под ваш форум.Ничего сложного нет , меняйте конфигурацию под свои нужды . Парсер ссылается на конкретную страницу последних новостей форума. в ручную меняйте количество карточек ниже смотрим показываю где менять.Код
const forumListUrl = '/forum/0-0-1-34'; const MAX_CARDS = 8; const CACHE_KEY = 'forum_cards_cache'; const CACHE_TIME = 30 * 60 * 1000; // 30 минут
CSS стили Код
<style> #custom-forum-cards { display: flex; flex-wrap: wrap; gap: 20px; justify-content: flex-start; width: 100%; } .custom-card { flex: 0 0 calc(25% - 15px); /* 4 карточки в ряд */ box-sizing: border-box; border-radius: 12px; overflow: hidden; position: relative; background: #1e1e1e; color: #fff; cursor: pointer; transition: transform 0.3s ease, box-shadow 0.3s ease; text-decoration: none; } .custom-card:hover { transform: translateY(-8px) scale(1.02); box-shadow: 0 15px 25px rgba(0,0,0,0.4); } .custom-card-img { width: 100%; height: 180px; overflow: hidden; position: relative; } .custom-card-img img { width: 100%; height: 100%; object-fit: cover; transition: transform 0.5s ease; } .custom-card:hover .custom-card-img img { transform: scale(1.1); } .custom-card-title { font-weight: bold; font-size: 14px; padding: 8px 10px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .custom-card-desc { font-size: 12px; color: #ccc; padding: 0 10px 10px 10px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .custom-card-badge { position: absolute; top: 10px; left: 10px; background: #853b97; padding: 4px 8px; border-radius: 6px; font-size: 12px; font-weight: bold; text-transform: uppercase; letter-spacing: 0.5px; } </style>
Скрипт Код
<script> (async function() { const container = document.querySelector('#custom-forum-cards'); if (!container) return; const forumListUrl = '/forum/0-0-1-34'; const MAX_CARDS = 8; const CACHE_KEY = 'forum_cards_cache'; const CACHE_TIME = 30 * 60 * 1000; // 30 минут function renderCards(cards) { container.innerHTML = ''; cards.forEach(parsed => { const title = parsed.title || 'Без названия'; const desc = parsed.text || ''; const img = parsed.img || 'https://adm-karpovka.ru/wp-content/uploads/2020/10/novosti-1.jpg'; const card = document.createElement('a'); card.className = 'custom-card'; card.href = parsed.href; card.target = '_blank'; card.innerHTML = ` <div class="custom-card-img"> <img src="${img}" alt="${title}"> <div class="custom-card-badge">Новое</div> </div> <div class="custom-card-title">${title}</div> <div class="custom-card-desc">${desc}</div> `; container.appendChild(card); }); } // Проверяем кэш const cached = localStorage.getItem(CACHE_KEY); if (cached) { const data = JSON.parse(cached); const now = Date.now(); if (now - data.time < CACHE_TIME) { renderCards(data.cards); return; // используем кэшированные данные } } async function fetchHTML(url) { const res = await fetch(url); return await res.text(); } function parseThreadsFromList(html) { const parser = new DOMParser(); const doc = parser.parseFromString(html, 'text/html'); const arr = []; const items = doc.querySelectorAll('.threadNametd .threadLink'); for (let i = 0; i < items.length && arr.length < MAX_CARDS; i++) { const a = items[i]; const titleEl = a.querySelector('.ipsType_pagetitle') || a; const title = (titleEl && titleEl.textContent || '').trim(); let href = a.getAttribute('href') || a.href || ''; if (href && !href.startsWith('http')) { href = window.location.origin + (href.startsWith('/') ? href : '/' + href); } if (href) arr.push({ href, title }); } return arr; } function parseFirstPost(html) { const parser = new DOMParser(); const doc = parser.parseFromString(html, 'text/html'); const post = doc.querySelector('.post_content, .post_body, .post, .ipsType_richText'); let text = ''; let img = ''; if (post) { text = post.textContent.trim().substring(0, 50); const imgEl = post.querySelector('img'); if (imgEl) { img = imgEl.getAttribute('src') || ''; if (img && !img.startsWith('http')) { const base = window.location.origin; img = img.startsWith('/') ? (base + img) : (base + '/' + img); } } } return { text, img }; } try { const listHtml = await fetchHTML(forumListUrl); const threads = parseThreadsFromList(listHtml); const promises = threads.map(t => fetchHTML(t.href).then(html => ({ ...t, ...parseFirstPost(html) })).catch(e => null) ); const results = (await Promise.all(promises)).filter(Boolean); // сохраняем в localStorage localStorage.setItem(CACHE_KEY, JSON.stringify({ time: Date.now(), cards: results })); renderCards(results); } catch (e) { console.error('Ошибка:', e); } })(); </script>
Полностью шаблон парсера , куда хотите туда и устанавливайте Код
<style> #custom-forum-cards { display: flex; flex-wrap: wrap; gap: 20px; justify-content: flex-start; width: 100%; } .custom-card { flex: 0 0 calc(25% - 15px); /* 4 карточки в ряд */ box-sizing: border-box; border-radius: 12px; overflow: hidden; position: relative; background: #1e1e1e; color: #fff; cursor: pointer; transition: transform 0.3s ease, box-shadow 0.3s ease; text-decoration: none; } .custom-card:hover { transform: translateY(-8px) scale(1.02); box-shadow: 0 15px 25px rgba(0,0,0,0.4); } .custom-card-img { width: 100%; height: 180px; overflow: hidden; position: relative; } .custom-card-img img { width: 100%; height: 100%; object-fit: cover; transition: transform 0.5s ease; } .custom-card:hover .custom-card-img img { transform: scale(1.1); } .custom-card-title { font-weight: bold; font-size: 14px; padding: 8px 10px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .custom-card-desc { font-size: 12px; color: #ccc; padding: 0 10px 10px 10px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .custom-card-badge { position: absolute; top: 10px; left: 10px; background: #853b97; padding: 4px 8px; border-radius: 6px; font-size: 12px; font-weight: bold; text-transform: uppercase; letter-spacing: 0.5px; } </style> <div id="custom-forum-cards"></div> <script> (async function() { const container = document.querySelector('#custom-forum-cards'); if (!container) return; const forumListUrl = '/forum/0-0-1-34'; const MAX_CARDS = 8; const CACHE_KEY = 'forum_cards_cache'; const CACHE_TIME = 30 * 60 * 1000; // 30 минут function renderCards(cards) { container.innerHTML = ''; cards.forEach(parsed => { const title = parsed.title || 'Без названия'; const desc = parsed.text || ''; const img = parsed.img || 'https://via.placeholder.com/180x180?text=No+Image'; const card = document.createElement('a'); card.className = 'custom-card'; card.href = parsed.href; card.target = '_blank'; card.innerHTML = ` <div class="custom-card-img"> <img src="${img}" alt="${title}"> <div class="custom-card-badge">Новое</div> </div> <div class="custom-card-title">${title}</div> <div class="custom-card-desc">${desc}</div> `; container.appendChild(card); }); } // Проверяем кэш const cached = localStorage.getItem(CACHE_KEY); if (cached) { const data = JSON.parse(cached); const now = Date.now(); if (now - data.time < CACHE_TIME) { renderCards(data.cards); return; // используем кэшированные данные } } async function fetchHTML(url) { const res = await fetch(url); return await res.text(); } function parseThreadsFromList(html) { const parser = new DOMParser(); const doc = parser.parseFromString(html, 'text/html'); const arr = []; const items = doc.querySelectorAll('.threadNametd .threadLink'); for (let i = 0; i < items.length && arr.length < MAX_CARDS; i++) { const a = items[i]; const titleEl = a.querySelector('.ipsType_pagetitle') || a; const title = (titleEl && titleEl.textContent || '').trim(); let href = a.getAttribute('href') || a.href || ''; if (href && !href.startsWith('http')) { href = window.location.origin + (href.startsWith('/') ? href : '/' + href); } if (href) arr.push({ href, title }); } return arr; } function parseFirstPost(html) { const parser = new DOMParser(); const doc = parser.parseFromString(html, 'text/html'); const post = doc.querySelector('.post_content, .post_body, .post, .ipsType_richText'); let text = ''; let img = ''; if (post) { text = post.textContent.trim().substring(0, 50); const imgEl = post.querySelector('img'); if (imgEl) { img = imgEl.getAttribute('src') || ''; if (img && !img.startsWith('http')) { const base = window.location.origin; img = img.startsWith('/') ? (base + img) : (base + '/' + img); } } } return { text, img }; } try { const listHtml = await fetchHTML(forumListUrl); const threads = parseThreadsFromList(listHtml); const promises = threads.map(t => fetchHTML(t.href).then(html => ({ ...t, ...parseFirstPost(html) })).catch(e => null) ); const results = (await Promise.all(promises)).filter(Boolean); // сохраняем в localStorage localStorage.setItem(CACHE_KEY, JSON.stringify({ time: Date.now(), cards: results })); renderCards(results); } catch (e) { console.error('Ошибка:', e); } })(); </script>
У меня он выглядит так .
Признаюсь, не знаю почему, но глядя на звезды мне всегда хочется мечтать.
Дата: Среда, 22.10.2025, 18:48 | Сообщение # 2 |
|
Написал: Начинающий
Автор темы
Мурчанн
не в сети
Сообщений: 101
Можно вставить любую стандартную (дефолтную) картинку на случай, если скрипт не сможет найти изображение в теме.Код
cards.forEach(parsed => { const title = parsed.title || 'Без названия'; const desc = parsed.text || ''; const img = parsed.img || 'https://via.placeholder.com/180x180?text=No+Image';
Можно такую вставить . Код
function renderCards(cards) { container.innerHTML = ''; cards.forEach(parsed => { const title = parsed.title || 'Без названия'; const desc = parsed.text || ''; const img = parsed.img || 'https://adm-karpovka.ru/wp-content/uploads/2020/10/novosti-1.jpg';
Признаюсь, не знаю почему, но глядя на звезды мне всегда хочется мечтать.
Дата: Четверг, 23.10.2025, 17:43 | Сообщение # 3 |
|
Написал: Начинающий
Автор темы
Мурчанн
не в сети
Сообщений: 101
Немного обновил скрипт: теперь при клике на карточку контент открывается прямо в этой же вкладке, без перезагрузки и перехода на новую страницу. CSS-стили остаются без изменений , внешний вид карточек сохраняется полностью. Теоретически можно добавить в карточку дополнительную информацию, например, имя автора темы или больше деталей из поста, но это может немного замедлить загрузку карточек.Код
<script> (async function() { const container = document.querySelector('#custom-forum-cards'); if (!container) return; const forumListUrl = '/forum/0-0-1-34'; // URL раздела форума, откуда брать темы const MAX_CARDS = 8; const CACHE_KEY = 'forum_cards_cache'; const CACHE_TIME = 30 * 60 * 1000; // 30 минут // Рендер карточек function renderCards(cards) { container.innerHTML = ''; cards.forEach(parsed => { const title = parsed.title || 'Без названия'; const desc = parsed.text || ''; const img = parsed.img || 'https://jordan.moy.su/forumimages/Newsletter-3.png'; // создаем обычную ссылку const card = document.createElement('a'); card.className = 'custom-card'; card.href = parsed.href; card.target = '_self'; // открывать в этой же вкладке card.innerHTML = ` <div class="custom-card-img"> <img src="${img}" alt="${title}"> <div class="custom-card-badge">Новое</div> </div> <div class="custom-card-title">${title}</div> <div class="custom-card-desc">${desc}</div> `; container.appendChild(card); }); } // Получение HTML страницы async function fetchHTML(url) { const res = await fetch(url); return await res.text(); } // Парсим список тем из форума function parseThreadsFromList(html) { const parser = new DOMParser(); const doc = parser.parseFromString(html, 'text/html'); const arr = []; const items = doc.querySelectorAll('.threadNametd .threadLink'); for (let i = 0; i < items.length && arr.length < MAX_CARDS; i++) { const a = items[i]; const titleEl = a.querySelector('.ipsType_pagetitle') || a; const title = (titleEl && titleEl.textContent || '').trim(); let href = a.getAttribute('href') || a.href || ''; if (href && !href.startsWith('http')) { href = window.location.origin + (href.startsWith('/') ? href : '/' + href); } if (href) arr.push({ href, title }); } return arr; } // Парсим первое сообщение темы function parseFirstPost(html) { const parser = new DOMParser(); const doc = parser.parseFromString(html, 'text/html'); const post = doc.querySelector('.post_content, .post_body, .post, .ipsType_richText'); let text = ''; let img = ''; if (post) { text = post.textContent.trim().substring(0, 80); const imgEl = post.querySelector('img'); if (imgEl) { img = imgEl.getAttribute('src') || ''; if (img && !img.startsWith('http')) { const base = window.location.origin; img = img.startsWith('/') ? (base + img) : (base + '/' + img); } } } return { text, img }; } // Проверка кэша const cached = localStorage.getItem(CACHE_KEY); if (cached) { const data = JSON.parse(cached); const now = Date.now(); if (now - data.time < CACHE_TIME) { renderCards(data.cards); return; } } // Основная логика try { const listHtml = await fetchHTML(forumListUrl); const threads = parseThreadsFromList(listHtml); const promises = threads.map(t => fetchHTML(t.href).then(html => ({ ...t, ...parseFirstPost(html) })).catch(() => null) ); const results = (await Promise.all(promises)).filter(Boolean); // сохраняем кэш localStorage.setItem(CACHE_KEY, JSON.stringify({ time: Date.now(), cards: results })); renderCards(results); } catch (e) { console.error('Ошибка:', e); } })(); </script>
Признаюсь, не знаю почему, но глядя на звезды мне всегда хочется мечтать.