primer commit
This commit is contained in:
@@ -0,0 +1,195 @@
|
||||
/* =========================================================
|
||||
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;
|
||||
});
|
||||
}
|
||||
})();
|
||||
Reference in New Issue
Block a user