feat: descrição arquivística NOBRADE multinível com exportação EAD 3, EAC-CPF e AtoM
- pipeline/nobrade: normalizadores de datas (ISO 8601-2), séries, autoridades, espécie/suporte, direitos, lugar e assuntos; montagem dos níveis 0–5 com código de referência BR RJXYZ.BF.<seção>.<série>.<handle>.<imagem> - validador dos elementos obrigatórios: 0 pendências em 26.454 unidades - EAD 3 e 457 EAC-CPF validados contra os XSD - mural: cartão com código, data normalizada, série e reprodução; filtro por série
This commit is contained in:
+38
-12
@@ -426,6 +426,8 @@
|
||||
#card-footer a { color: #d8b04a; text-decoration: none; }
|
||||
#card-footer a:hover { text-decoration: underline; }
|
||||
#card-rights { margin-top: 6px; font-size: .68rem; color: #777; }
|
||||
#card-ref { font: .7rem/1.3 ui-monospace, SFMono-Regular, Menlo, monospace; color: #8a8a82; margin-top: 4px; }
|
||||
#card-ref .serie { font-family: system-ui, -apple-system, sans-serif; letter-spacing: .02em; }
|
||||
|
||||
/* stepping between pieces without a keyboard */
|
||||
#card-nav { display: flex; gap: 9px; margin-top: 16px; }
|
||||
@@ -635,6 +637,10 @@
|
||||
<label for="f-colecao">Coleção</label>
|
||||
<select id="f-colecao"><option value="">todas</option></select>
|
||||
</div>
|
||||
<div class="f-row">
|
||||
<label for="f-serie">Série (arranjo NOBRADE)</label>
|
||||
<select id="f-serie"><option value="">todas</option></select>
|
||||
</div>
|
||||
<div class="f-row">
|
||||
<label for="f-pessoa">Pessoa</label>
|
||||
<select id="f-pessoa"><option value="">todas</option></select>
|
||||
@@ -660,6 +666,7 @@
|
||||
<div id="card-img-wrap"><img id="card-img" alt=""></div>
|
||||
<div id="card-title">—</div>
|
||||
<div id="card-date">—</div>
|
||||
<div id="card-ref"></div>
|
||||
<div id="card-contrib"></div>
|
||||
<div id="card-tags"></div>
|
||||
<div id="card-related" hidden></div>
|
||||
@@ -1063,6 +1070,7 @@ renderer.domElement.addEventListener('pointermove', e => {
|
||||
// (the "hole" always opens in the middle, wherever you clicked).
|
||||
let atlasSources = []; // { source, size } per atlas page — used to crop the tile into the card canvas
|
||||
let atlasEntries = null, TPR = 1;
|
||||
let seriesTitles = []; // títulos das séries NOBRADE (nível 3); meta.si indexa aqui
|
||||
let selected = null;
|
||||
let selectedAt = 0;
|
||||
|
||||
@@ -1393,11 +1401,20 @@ function tileThumbUrl(handle, px) {
|
||||
|
||||
function showCard(data) {
|
||||
const meta = data.meta || {};
|
||||
document.getElementById('card-type').textContent = meta.type || 'Fotografia';
|
||||
document.getElementById('card-type').textContent = meta.sup || meta.type || 'Fotografia';
|
||||
document.getElementById('card-community').textContent = meta.community || '—';
|
||||
document.getElementById('card-date-tag').textContent = prettyDate(meta.date) || 's.d.';
|
||||
document.getElementById('card-date-tag').textContent = meta.dt || prettyDate(meta.date) || 's.d.';
|
||||
document.getElementById('card-title').textContent = meta.title || '(sem título)';
|
||||
document.getElementById('card-date').textContent = prettyDate(meta.date);
|
||||
document.getElementById('card-date').textContent = meta.dt || prettyDate(meta.date);
|
||||
// código de referência NOBRADE e a série (nível 3) a que a peça pertence
|
||||
const refEl = document.getElementById('card-ref');
|
||||
refEl.textContent = meta.ref || '';
|
||||
if (meta.si != null && seriesTitles[meta.si]) {
|
||||
const sr = document.createElement('div');
|
||||
sr.className = 'serie';
|
||||
sr.textContent = seriesTitles[meta.si];
|
||||
refEl.appendChild(sr);
|
||||
}
|
||||
document.getElementById('card-contrib').textContent = meta.contributor ? meta.contributor : '';
|
||||
// this piece's dc.subject, one chip each (accent/case duplicates folded)
|
||||
const tagsEl = document.getElementById('card-tags');
|
||||
@@ -1406,7 +1423,8 @@ function showCard(data) {
|
||||
renderRelated(data);
|
||||
document.getElementById('card-handle').textContent = data.handle || '';
|
||||
document.getElementById('card-link').href = data.handle ? sourceUrl(data.handle) : '#';
|
||||
document.getElementById('card-rights').textContent = meta.rights ? `Direitos: ${meta.rights}` : '';
|
||||
document.getElementById('card-rights').textContent =
|
||||
[meta.rep && meta.rep[0].toUpperCase() + meta.rep.slice(1), meta.rights && `Direitos: ${meta.rights}`].filter(Boolean).join(' · ');
|
||||
|
||||
// crop this tile out of the atlas image for a (still small, but framed) preview
|
||||
const entry = atlasEntries[data.handle];
|
||||
@@ -1493,6 +1511,7 @@ function flattenMeta(value) {
|
||||
}
|
||||
|
||||
function yearOf(meta) {
|
||||
if (meta && Number.isInteger(meta.ano)) return meta.ano;
|
||||
const m = String((meta || {}).date || '').match(/(1[5-9]\d{2}|20\d{2})/);
|
||||
return m ? parseInt(m[1], 10) : null;
|
||||
}
|
||||
@@ -1961,6 +1980,7 @@ function setupFilters(mapData) {
|
||||
const a = cleanAuthor(meta.contributor);
|
||||
m.userData._author = a ? authorKey(a) : '';
|
||||
m.userData._type = cleanType(meta.type);
|
||||
m.userData._serie = meta.si != null ? seriesTitles[meta.si] || '' : '';
|
||||
Object.assign(m.userData, (({ st, ct, bh, tp, col, pes }) => ({ _st: st, _ct: ct, _bh: bh, _tp: tp, _col: col, _pes: pes }))(subjectFacets(meta)));
|
||||
// one haystack per piece: every metadata field, not a chosen few
|
||||
// the handle is the map's key, not a field of meta — it still has to be
|
||||
@@ -1992,6 +2012,7 @@ function setupFilters(mapData) {
|
||||
const selTopico = document.getElementById('f-topico');
|
||||
const selColecao = document.getElementById('f-colecao');
|
||||
const selPessoa = document.getElementById('f-pessoa');
|
||||
const selSerie = document.getElementById('f-serie');
|
||||
const fillSelect = (sel, rows, first) => {
|
||||
sel.replaceChildren(Object.assign(document.createElement('option'), { value: '', textContent: first }));
|
||||
rows.forEach(([value, n]) => {
|
||||
@@ -2036,7 +2057,7 @@ function setupFilters(mapData) {
|
||||
sel.value = value;
|
||||
}
|
||||
|
||||
const state = { search: '', institution: '', yearLo: decadeMin, yearHi: decadeMax, yearTouched: false, author: '', type: '', estado: '', cidade: '', bairro: '', topico: '', colecao: '', pessoa: '' };
|
||||
const state = { search: '', institution: '', yearLo: decadeMin, yearHi: decadeMax, yearTouched: false, author: '', type: '', estado: '', cidade: '', bairro: '', topico: '', colecao: '', pessoa: '', serie: '' };
|
||||
|
||||
function resetYear() {
|
||||
state.yearTouched = false;
|
||||
@@ -2064,6 +2085,7 @@ function setupFilters(mapData) {
|
||||
(!state.topico || d._tp.has(topicPrefix() + state.topico)) &&
|
||||
(!state.colecao || d._col.has(state.colecao)) &&
|
||||
(!state.pessoa || d._pes.has(state.pessoa)) &&
|
||||
(!state.serie || d._serie === state.serie) &&
|
||||
terms.every(t => d._hay.includes(t));
|
||||
if (okRest && d._year != null) {
|
||||
const i = Math.floor((d._year - decadeMin) / 10);
|
||||
@@ -2131,6 +2153,7 @@ function setupFilters(mapData) {
|
||||
if (state.topico) out.push({ key: 'topico', text: state.topico });
|
||||
if (state.colecao) out.push({ key: 'colecao', text: state.colecao });
|
||||
if (state.pessoa) out.push({ key: 'pessoa', text: state.pessoa });
|
||||
if (state.serie) out.push({ key: 'serie', text: state.serie });
|
||||
if (state.type) out.push({ key: 'type', text: state.type });
|
||||
return out;
|
||||
}
|
||||
@@ -2145,6 +2168,7 @@ function setupFilters(mapData) {
|
||||
if (key === 'topico') { state.topico = ''; selTopico.value = ''; }
|
||||
if (key === 'colecao') { state.colecao = ''; selColecao.value = ''; }
|
||||
if (key === 'pessoa') { state.pessoa = ''; selPessoa.value = ''; }
|
||||
if (key === 'serie') { state.serie = ''; selSerie.value = ''; }
|
||||
if (key === 'author') { state.author = ''; selAuthor.value = ''; }
|
||||
if (key === 'year') resetYear();
|
||||
apply();
|
||||
@@ -2192,6 +2216,7 @@ function setupFilters(mapData) {
|
||||
selTopico.addEventListener('change', () => { state.topico = selTopico.value; apply(); });
|
||||
selColecao.addEventListener('change', () => { state.colecao = selColecao.value; apply(); });
|
||||
selPessoa.addEventListener('change', () => { state.pessoa = selPessoa.value; apply(); });
|
||||
selSerie.addEventListener('change', () => { state.serie = selSerie.value; apply(); });
|
||||
selAuthor.addEventListener('change', () => { state.author = selAuthor.value; apply(); });
|
||||
|
||||
function onYear(e) {
|
||||
@@ -2211,8 +2236,8 @@ function setupFilters(mapData) {
|
||||
|
||||
clearBtn.addEventListener('click', () => {
|
||||
clearTimeout(searchTimer);
|
||||
state.search = ''; state.institution = ''; state.type = ''; state.author = ''; state.estado = state.cidade = state.bairro = state.topico = state.colecao = state.pessoa = '';
|
||||
searchInput.value = ''; selInst.value = ''; selType.value = ''; selAuthor.value = ''; selEstado.value = ''; selColecao.value = ''; selPessoa.value = ''; rebuildPlace();
|
||||
state.search = ''; state.institution = ''; state.type = ''; state.author = ''; state.estado = state.cidade = state.bairro = state.topico = state.colecao = state.pessoa = state.serie = '';
|
||||
searchInput.value = ''; selInst.value = ''; selType.value = ''; selAuthor.value = ''; selEstado.value = ''; selColecao.value = ''; selPessoa.value = ''; selSerie.value = ''; rebuildPlace();
|
||||
resetYear();
|
||||
apply();
|
||||
});
|
||||
@@ -2220,13 +2245,14 @@ function setupFilters(mapData) {
|
||||
fillSelect(selEstado, tally('', d => d._st, 1), 'todos');
|
||||
fillSelect(selColecao, tally('', d => d._col, 1), 'todas');
|
||||
fillSelect(selPessoa, tally('', d => d._pes, 5), 'todas');
|
||||
fillSelect(selSerie, tally('', d => d._serie ? [d._serie] : [], 1), 'todas');
|
||||
|
||||
// a tag on the card applies itself as a filter; the URL carries the filters
|
||||
// (?estado=…&topico=…) so a filtered wall can be shared and survives a reload
|
||||
const URL_KEYS = { q: 'search', inst: 'institution', autor: 'author', tipo: 'type', estado: 'estado', cidade: 'cidade',
|
||||
bairro: 'bairro', topico: 'topico', colecao: 'colecao', pessoa: 'pessoa' };
|
||||
bairro: 'bairro', topico: 'topico', colecao: 'colecao', pessoa: 'pessoa', serie: 'serie' };
|
||||
const SELECTS = { institution: selInst, author: selAuthor, type: selType, estado: selEstado, cidade: selCidade,
|
||||
bairro: selBairro, topico: selTopico, colecao: selColecao, pessoa: selPessoa };
|
||||
bairro: selBairro, topico: selTopico, colecao: selColecao, pessoa: selPessoa, serie: selSerie };
|
||||
function syncUrl() {
|
||||
const p = new URLSearchParams();
|
||||
Object.entries(URL_KEYS).forEach(([k, f]) => { if (state[f]) p.set(k, state[f]); });
|
||||
@@ -2326,6 +2352,7 @@ async function main() {
|
||||
const mapData = JSON.parse(await catalogue.blob.text());
|
||||
TPR = mapData.size / mapData.tile;
|
||||
atlasEntries = mapData.map;
|
||||
seriesTitles = mapData.series || [];
|
||||
const handles = Object.keys(mapData.map);
|
||||
|
||||
// the note only earns its space in placeholder mode — the piece count already
|
||||
@@ -2342,9 +2369,8 @@ async function main() {
|
||||
for (const entry of Object.values(mapData.map)) {
|
||||
const meta = entry.meta || {};
|
||||
if (meta.community) institutions.add(meta.community);
|
||||
const m = String(meta.date || '').match(/(1[5-9]\d{2}|20\d{2})/);
|
||||
if (m) {
|
||||
const y = parseInt(m[1], 10);
|
||||
const y = yearOf(meta);
|
||||
if (y != null) {
|
||||
if (y < minYear) minYear = y;
|
||||
if (y > maxYear) maxYear = y;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user