Ремонт: техника — авто-группировка карточек по типу + перенос строк в названии
All checks were successful
Deploy Remont Site / deploy (push) Successful in 19s

- inline() поддерживает <br> в тексте заметки (было видно литеральным)
- карточки: «Модель<br>описание» → модель жирным + приглушённое описание
- колонка «Тип/Категория/Техника» → карточки группируются по типу с подзаголовками
  (пустая ячейка типа наследует значение сверху)
- таблица «Варианты техники» переведена в формат Тип|Название|Цена|Ссылка|Фото
- README: описан формат таблицы техники

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KjyzuPoH9T5k3oCbKEEazy
This commit is contained in:
Cosmo
2026-07-16 10:08:35 +00:00
parent b355119e8e
commit c2f65b2ea1
4 changed files with 58 additions and 15 deletions

View File

@@ -42,6 +42,7 @@ function inline(t) {
t = esc(t);
t = t.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>');
t = t.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank" rel="noopener">$1</a>');
t = t.replace(/&lt;br\s*\/?&gt;/gi, '<br>'); // разрешаем перенос строки <br> в тексте заметки
return t;
}
@@ -179,28 +180,54 @@ function renderCards(tbl) {
const iPrice = colIndex(H, /цена|price|₽|руб/i);
const iLink = colIndex(H, /ссылк|link|url/i);
const iPhoto = colIndex(H, /фото|photo|картин|image/i);
const iName = H.findIndex((h, idx) => idx !== iNo && idx !== iPrice && idx !== iLink && idx !== iPhoto && h);
const iCat = colIndex(H, /^(тип|категор|группа|техника|раздел)$/i);
const iName = H.findIndex((h, idx) => idx !== iNo && idx !== iPrice && idx !== iLink && idx !== iPhoto && idx !== iCat && h);
const cards = tbl.rows.map((r, idx) => {
const no = iNo >= 0 && r[iNo] ? r[iNo] : String(idx + 1);
const name = iName >= 0 ? r[iName] : '';
// одна карточка. no — подпись «Вариант N» (в пределах группы)
const card = (r, no) => {
const nameRaw = iName >= 0 ? (r[iName] || '') : '';
// «Модель<br>описание» → жирная модель + приглушённое описание
const [head, ...rest] = nameRaw.split(/<br\s*\/?>/i);
const desc = rest.join('<br>').trim();
const priceRaw = iPrice >= 0 ? r[iPrice] : '';
const priceNum = (priceRaw.match(/[\d\s .]+/) || [''])[0].trim();
const unit = /₽|руб/i.test(priceRaw) ? '₽' : '';
const photo = iPhoto >= 0 ? parseWiki(r[iPhoto]) : null;
const url = iLink >= 0 ? extractUrl(r[iLink]) : '';
const media = photo
? `<div class="opt__media" data-zoom-wrap><img src="${imgSrc(photo)}" alt="${esc(name)}" data-zoom loading="lazy"></div>` : '';
? `<div class="opt__media" data-zoom-wrap><img src="${imgSrc(photo)}" alt="${esc(head)}" data-zoom loading="lazy"></div>` : '';
const price = priceNum
? `<span class="opt__price">${esc(priceNum)}${unit ? ` <span>${unit}</span>` : ''}</span>` : '';
const link = url
? `<a class="opt__link" href="${esc(url)}" target="_blank" rel="noopener">Открыть на ${esc(domainLabel(url))} <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M7 17 17 7M8 7h9v9"/></svg></a>` : '';
return `<article class="opt reveal">${media}<div class="opt__body">
<span class="opt__no">Вариант ${esc(no)}</span>
<span class="opt__name">${inline(name)}</span>
<span class="opt__name">${inline(head)}</span>
${desc ? `<span class="opt__desc">${inline(desc)}</span>` : ''}
${price}${link}</div></article>`;
};
// без колонки типа — простая сетка карточек
if (iCat < 0) {
const cards = tbl.rows.map((r, idx) => card(r, iNo >= 0 && r[iNo] ? r[iNo] : idx + 1)).join('\n');
return `<div class="options">${cards}</div>`;
}
// с колонкой типа — группируем по типу (пустая ячейка наследует тип сверху)
const groups = [];
let cur = null, lastCat = '';
tbl.rows.forEach((r) => {
const cat = (r[iCat] && r[iCat].trim()) || lastCat;
lastCat = cat;
if (!cur || cur.cat !== cat) { cur = { cat, rows: [] }; groups.push(cur); }
cur.rows.push(r);
});
return groups.map((g) => {
const cards = g.rows.map((r, i) => card(r, i + 1)).join('\n');
return `<div class="opt-group reveal">
<h4 class="opt-group__title">${inline(g.cat)}<span>${g.rows.length} ${plural(g.rows.length, 'вариант', 'варианта', 'вариантов')}</span></h4>
<div class="options">${cards}</div></div>`;
}).join('\n');
return `<div class="options">${cards}</div>`;
}
function tableCell(c) {