196 lines
6.5 KiB
JavaScript
196 lines
6.5 KiB
JavaScript
/* =========================================================
|
|
manuales-factorpesca / manual.js
|
|
Lógica de presentación del manual:
|
|
- Genera el índice automático a partir de h1, h2, h3.
|
|
- Dibuja anotaciones numeradas sobre las capturas.
|
|
- Captura el nombre del capítulo actual para la cabecera.
|
|
- Numera figuras y pasos (complementa a los CSS counters).
|
|
- Prepara el contador de página tras la carga de fuentes.
|
|
|
|
Este script NO añade interactividad: solo prepara el DOM
|
|
para que Chromium imprima un PDF bien paginado.
|
|
========================================================= */
|
|
|
|
(function () {
|
|
'use strict';
|
|
|
|
document.addEventListener('DOMContentLoaded', init);
|
|
|
|
function init() {
|
|
if (document.fonts && document.fonts.ready) {
|
|
document.fonts.ready.then(afterFonts);
|
|
} else {
|
|
afterFonts();
|
|
}
|
|
}
|
|
|
|
function afterFonts() {
|
|
try {
|
|
buildTOC();
|
|
renderAnnotations();
|
|
setupChapterString();
|
|
ensureAnchors();
|
|
ensureImagePaths();
|
|
} catch (err) {
|
|
console.error('[manual.js] Error preparando el manual:', err);
|
|
}
|
|
}
|
|
|
|
/* ---------------------------------------------------------
|
|
1. Índice automático
|
|
--------------------------------------------------------- */
|
|
function buildTOC() {
|
|
const out = document.querySelector('[data-toc-output]');
|
|
if (!out) return;
|
|
|
|
const chapters = document.querySelectorAll('[data-chapter]');
|
|
if (!chapters.length) {
|
|
out.innerHTML = '<li class="toc-empty">Sin capítulos definidos.</li>';
|
|
return;
|
|
}
|
|
|
|
const frag = document.createDocumentFragment();
|
|
let chapterIndex = 0;
|
|
|
|
chapters.forEach((chapter) => {
|
|
chapterIndex += 1;
|
|
const title = chapter.querySelector('[data-chapter-title]') || chapter.querySelector('h1');
|
|
if (!title) return;
|
|
|
|
const li = document.createElement('li');
|
|
li.className = 'toc-h1';
|
|
li.appendChild(tocRow(romanize(chapterIndex) + '. ' + title.textContent.trim()));
|
|
frag.appendChild(li);
|
|
|
|
const subsections = chapter.querySelectorAll('h2');
|
|
subsections.forEach((sub) => {
|
|
const row = document.createElement('li');
|
|
row.className = 'toc-h2';
|
|
row.appendChild(tocRow(' ' + (sub.dataset.tocLabel || sub.textContent.trim())));
|
|
frag.appendChild(row);
|
|
});
|
|
|
|
const subsub = chapter.querySelectorAll('h3');
|
|
subsub.forEach((sub) => {
|
|
const row = document.createElement('li');
|
|
row.className = 'toc-h3';
|
|
row.appendChild(tocRow(' ' + (sub.dataset.tocLabel || sub.textContent.trim())));
|
|
frag.appendChild(row);
|
|
});
|
|
});
|
|
|
|
out.innerHTML = '';
|
|
out.appendChild(frag);
|
|
}
|
|
|
|
function tocRow(label) {
|
|
const span = document.createElement('span');
|
|
span.className = 'toc-row';
|
|
const text = document.createElement('span');
|
|
text.className = 'toc-text';
|
|
text.textContent = label;
|
|
const dots = document.createElement('span');
|
|
dots.className = 'toc-dots';
|
|
const num = document.createElement('span');
|
|
num.className = 'toc-page-num';
|
|
num.textContent = '—';
|
|
span.appendChild(text);
|
|
span.appendChild(dots);
|
|
span.appendChild(num);
|
|
return span;
|
|
}
|
|
|
|
function romanize(num) {
|
|
const map = [
|
|
['M', 1000], ['CM', 900], ['D', 500], ['CD', 400],
|
|
['C', 100], ['XC', 90], ['L', 50], ['XL', 40],
|
|
['X', 10], ['IX', 9], ['V', 5], ['IV', 4], ['I', 1],
|
|
];
|
|
let result = '';
|
|
let n = num;
|
|
for (const [letter, value] of map) {
|
|
while (n >= value) {
|
|
result += letter;
|
|
n -= value;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/* ---------------------------------------------------------
|
|
2. Anotaciones sobre capturas
|
|
--------------------------------------------------------- */
|
|
function renderAnnotations() {
|
|
document.querySelectorAll('.annotations[data-annotations]').forEach((list) => {
|
|
let data = [];
|
|
try {
|
|
data = JSON.parse(list.getAttribute('data-annotations') || '[]');
|
|
} catch (err) {
|
|
console.warn('[manual.js] data-annotations inválido:', err);
|
|
return;
|
|
}
|
|
if (!Array.isArray(data) || data.length === 0) {
|
|
list.remove();
|
|
return;
|
|
}
|
|
const sorted = data.slice().sort((a, b) => (a.n || 0) - (b.n || 0));
|
|
sorted.forEach((entry) => {
|
|
const li = document.createElement('li');
|
|
li.className = 'annotation';
|
|
li.textContent = String(entry.n);
|
|
if (typeof entry.x === 'number' && typeof entry.y === 'number') {
|
|
li.style.left = entry.x + '%';
|
|
li.style.top = entry.y + '%';
|
|
}
|
|
if (entry.label) {
|
|
li.setAttribute('aria-label', entry.label);
|
|
li.setAttribute('title', entry.label);
|
|
}
|
|
list.appendChild(li);
|
|
});
|
|
});
|
|
}
|
|
|
|
/* ---------------------------------------------------------
|
|
3. Cabecera dinámica con el nombre del capítulo actual
|
|
--------------------------------------------------------- */
|
|
function setupChapterString() {
|
|
const chapters = document.querySelectorAll('[data-chapter]');
|
|
chapters.forEach((chapter) => {
|
|
const title = chapter.querySelector('[data-chapter-title]') || chapter.querySelector('h1');
|
|
if (!title) return;
|
|
title.setAttribute('data-string', title.textContent.trim());
|
|
});
|
|
}
|
|
|
|
/* ---------------------------------------------------------
|
|
4. Anclas automáticas para headings
|
|
--------------------------------------------------------- */
|
|
function ensureAnchors() {
|
|
const headings = document.querySelectorAll('[data-chapter] h1, [data-chapter] h2, [data-chapter] h3, [data-chapter] h4');
|
|
headings.forEach((h) => {
|
|
if (!h.id) {
|
|
const slug = (h.textContent || '')
|
|
.toLowerCase()
|
|
.normalize('NFD').replace(/[\u0300-\u036f]/g, '')
|
|
.replace(/[^a-z0-9]+/g, '-')
|
|
.replace(/(^-|-$)/g, '');
|
|
h.id = slug || 'h-' + Math.random().toString(36).slice(2, 8);
|
|
}
|
|
});
|
|
}
|
|
|
|
/* ---------------------------------------------------------
|
|
5. Resolver rutas de imágenes a absolutas
|
|
--------------------------------------------------------- */
|
|
function ensureImagePaths() {
|
|
const base = window.location.href.replace(/[^/]*$/, '');
|
|
document.querySelectorAll('img').forEach((img) => {
|
|
const src = img.getAttribute('src');
|
|
if (!src || src.startsWith('http') || src.startsWith('data:') || src.startsWith('file:')) return;
|
|
const absolute = new URL(src, base).href;
|
|
img.src = absolute;
|
|
});
|
|
}
|
|
})();
|