Skip to content

Commit 6e048da

Browse files
committed
collection.html
1 parent b9baf9c commit 6e048da

File tree

79 files changed

+686
-654
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

79 files changed

+686
-654
lines changed

JS/metadata.js

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ function addManuscriptForm(data = {}) {
3535
</div>
3636
3737
<div class="col-md-6">
38-
<label class="form-label">Publication details</label>
38+
<label class="form-label">License</label>
3939
<select class="form-select" name="publicationStmt">
4040
<option value="">Please select</option>
4141
${['CC BY','CC BY-SA','CC BY-ND','CC BY-NC','CC BY-NC-SA','CC BY-NC-ND','CC0']
@@ -314,6 +314,8 @@ function addManuscriptForm(data = {}) {
314314
</div>
315315
`;
316316
container.appendChild(form);
317+
318+
form.scrollIntoView({ behavior: 'smooth', block: 'start' });
317319

318320
return form
319321
}
@@ -872,7 +874,7 @@ function downloadXML(formId) {
872874

873875

874876
// Download JSON, CSV, RDF
875-
document.getElementById('downloadAllJSON').addEventListener('click', () => {
877+
/*document.getElementById('downloadAllJSON').addEventListener('click', () => {
876878
const all = [...document.querySelectorAll('.msForm')].map(f => getFormData(f.id));
877879
downloadFile('all-manuscripts.json', JSON.stringify(all, null, 2), 'application/json');
878880
});
@@ -905,8 +907,51 @@ document.getElementById('downloadAllRDF').addEventListener('click', () => {
905907
});
906908
rdf += '</rdf:RDF>';
907909
downloadFile('all-manuscripts.rdf', rdf, 'application/rdf+xml');
910+
});*/
911+
912+
function buildCombinedFileName(all) {
913+
const titles = all.map(row => row.msTitle?.trim() || 'Untitled');
914+
return titles.join('_').replace(/[\\/:*?"<>|]/g, '_');
915+
}
916+
917+
document.getElementById('downloadAllJSON').addEventListener('click', () => {
918+
const all = [...document.querySelectorAll('.msForm')].map(f => getFormData(f.id));
919+
const fileBase = buildCombinedFileName(all);
920+
downloadFile(`${fileBase}.json`, JSON.stringify(all, null, 2), 'application/json');
921+
});
922+
923+
document.getElementById('downloadAllCSV').addEventListener('click', () => {
924+
const all = [...document.querySelectorAll('.msForm')].map(f => getFormData(f.id));
925+
const headers = Object.keys(all[0] || {});
926+
const csv = [headers.join(',')].concat(
927+
all.map(row =>
928+
headers.map(h => {
929+
const val = row[h];
930+
const str = typeof val === 'string' ? val : JSON.stringify(val);
931+
return `"${(str || '').replace(/"/g, '""')}"`;
932+
}).join(',')
933+
)
934+
).join('\n');
935+
const fileBase = buildCombinedFileName(all);
936+
downloadFile(`${fileBase}.csv`, csv, 'text/csv');
908937
});
909938

939+
document.getElementById('downloadAllRDF').addEventListener('click', () => {
940+
const all = [...document.querySelectorAll('.msForm')].map(f => getFormData(f.id));
941+
let rdf = `<?xml version="1.0"?>\n<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">\n`;
942+
all.forEach((row, i) => {
943+
rdf += ` <rdf:Description rdf:about="http://example.org/manuscript/${i + 1}">\n`;
944+
for (const key in row) {
945+
rdf += ` <${key}>${escapeXml(row[key])}</${key}>\n`;
946+
}
947+
rdf += ` </rdf:Description>\n`;
948+
});
949+
rdf += '</rdf:RDF>';
950+
const fileBase = buildCombinedFileName(all);
951+
downloadFile(`${fileBase}.rdf`, rdf, 'application/rdf+xml');
952+
});
953+
954+
910955
function downloadFile(filename, content, type) {
911956
const blob = new Blob([content], { type });
912957
const a = document.createElement('a');

collection.html

Lines changed: 280 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,280 @@
1+
<!DOCTYPE html>
2+
<html xmlns="http://www.w3.org/1999/xhtml" translate="no" class="notranslate">
3+
<head>
4+
<title>MANO - Metadata Collection</title>
5+
<meta name="keywords" content="MANO, Manuscripts Online, Metadata Collection">
6+
<meta name="description" content="">
7+
<meta charset="UTF-8"/>
8+
<meta name="viewport" content="width=device-width, initial-scale=1.0 shrink-to-fit=no"/>
9+
<meta name="google" content="notranslate"/>
10+
<link rel="canonical" href="https://burchards-dekret-digital.de"/>
11+
<link rel="icon" type="image/png" href="images/MANO.png">
12+
13+
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
14+
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
15+
16+
<link rel="stylesheet" type="text/css" href="css/style.css" media="screen"/>
17+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css">
18+
</head>
19+
<body>
20+
<nav class="navbar bg-body-tertiary ">
21+
<div class="container-fluid d-flex justify-content-between align-items-center position-relative py-2">
22+
23+
<!-- Invisible spacer to balance layout -->
24+
<div style="width: 80px;"></div>
25+
26+
<!-- Centered logo -->
27+
<div class="position-absolute start-50 translate-middle-x text-center">
28+
<a class="navbar-brand" href="index.html">
29+
<img src="images/MANO.png" alt="Logo" width="80" class="d-inline-block align-text-top">
30+
</a>
31+
</div>
32+
33+
<!-- Offcanvas toggle aligned right -->
34+
<button class="navbar-toggler" type="button" data-bs-toggle="offcanvas" data-bs-target="#offcanvasNavbar"
35+
aria-controls="offcanvasNavbar" aria-label="Toggle navigation">
36+
<span class="navbar-toggler-icon"></span>
37+
</button>
38+
39+
<div class="offcanvas offcanvas-end" tabindex="-1" id="offcanvasNavbar" aria-labelledby="offcanvasNavbarLabel">
40+
<div class="offcanvas-header">
41+
<h5 class="offcanvas-title" id="offcanvasNavbarLabel">&lt;MANO&gt;</h5>
42+
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" aria-label="Close"></button>
43+
</div>
44+
<div class="offcanvas-body">
45+
<ul class="navbar-nav justify-content-end flex-grow-1 pe-3">
46+
<li class="nav-item"><a class="nav-link" href="index.html">Home</a></li>
47+
<li class="nav-item"><a class="nav-link" href="resources.html">Resources</a></li>
48+
<li class="nav-item"><a class="nav-link" href="editor.html">Metadata Editor</a></li>
49+
<li class="nav-item"><a class="nav-link active" href="collection.html">Metadata Collection</a></li>
50+
<li class="nav-item"><a class="nav-link" href="viewer.html">Transcription Viewer</a></li>
51+
<li class="nav-item"><a class="nav-link" href="contacts.html">Contacts</a></li>
52+
</ul>
53+
</div>
54+
</div>
55+
</div>
56+
</nav>
57+
<div class="d-flex flex-column min-vh-100">
58+
<div class="container container-page mt-4 mb-5 flex-grow-1">
59+
60+
<div class="container">
61+
<button onclick="history.back()" class="btn btn-sm btn-outline-secondary">&larr; Back</button>
62+
</div>
63+
<h1 class="page-title">Metadata Collection</h1>
64+
65+
<div class="row my-4 align-items-end">
66+
<div class="col-md-6 d-flex justify-content-start">
67+
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#infoMetadataCollection">
68+
About Metadata Collection
69+
</button>
70+
</div>
71+
<div class="col-md-6 d-flex justify-content-end">
72+
<a href="https://github.com/mano-project/mano-metadata/tree/main/data" class="btn btn-primary" target="_blank" rel="noopener noreferrer">Contribute to the Collection
73+
<i class="fab fa-github fa-xl ms-2"></i></a>
74+
</div>
75+
</div>
76+
77+
<!-- About Metadata Editor Modal -->
78+
<div class="modal fade" id="infoMetadataCollection" tabindex="-1" aria-labelledby="infoMetadataCollectionLabel" aria-hidden="true">
79+
<div class="modal-dialog modal-lg">
80+
<div class="modal-content">
81+
<div class="modal-header">
82+
<h5 class="modal-title" id="infoMetadataCollectionLabel">About Metadata Collection</h5>
83+
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
84+
</div>
85+
<div class="modal-body">
86+
<p>The Metadata Collection provides an overview of all manuscript descriptions that have been contributed by users into the MANO project repository. Each row in the table represents a single manuscript, with metadata such as title, repository, location, material, and links to digital copies. The filtering interface allows the metadata to be refined by country, century, responsible person, or by searching titles and repositories.</p>
87+
<p>To contribute new metadata, manuscripts must first be described in the <a href="editor.html">Metadata Editor</a> and downloaded in JSON format. The resulting JSON file can then be uploaded directly to the <a href="https://github.com/mano-project/mano-metadata/tree/main/data" target="_blank">MANO GitHub repository</a>, or by clicking the "Contribute to the Collection" button on this page. After the submission is reviewed and accepted by the project team, the new records will automatically appear in this collection.
88+
</p>
89+
<p>Contributors must have a GitHub account in order to submit files directly. Alternatively, metadata files may be sent via email to the project team, who will handle the upload process. Every contribution helps expand the shared digital resource, and participation in enriching the Metadata Collection is greatly appreciated.
90+
</p>
91+
</div>
92+
<div class="modal-footer">
93+
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
94+
</div>
95+
</div>
96+
</div>
97+
</div>
98+
99+
<!-- Filters -->
100+
<div class="row mb-4 mt-5">
101+
<div class="col-md-3">
102+
<input id="searchInput" class="form-control" type="text" placeholder="Search title or repository..." oninput="filterTable()">
103+
</div>
104+
<div class="col-md-3">
105+
<select id="countryFilter" class="form-select" onchange="filterTable()">
106+
<option value="">All Countries</option>
107+
</select>
108+
</div>
109+
<div class="col-md-3">
110+
<select id="centuryFilter" class="form-select" onchange="filterTable()">
111+
<option value="">All Centuries</option>
112+
</select>
113+
</div>
114+
<div class="col-md-3">
115+
<select id="personFilter" class="form-select" onchange="filterTable()">
116+
<option value="">All Responsible Persons</option>
117+
</select>
118+
</div>
119+
</div>
120+
121+
<!-- Table -->
122+
<table class="table table-striped" id="metadataTable">
123+
<thead>
124+
<tr>
125+
<th>Title</th>
126+
<th>Repository</th>
127+
<th>Location</th>
128+
<th>Material</th>
129+
<th>Digital Copy</th>
130+
</tr>
131+
</thead>
132+
<tbody id="tableBody">
133+
<!-- Rows added by JS -->
134+
</tbody>
135+
</table>
136+
</div>
137+
138+
<footer class="footer bg-body-tertiary text-center py-4">
139+
<div class="container">
140+
141+
<!-- Logo centered -->
142+
<div class="mb-3">
143+
<a class="navbar-brand" href="index.html">
144+
<img src="images/MANO.png" alt="Logo" width="80">
145+
</a>
146+
</div>
147+
148+
<!-- Links centered in one line -->
149+
<div class="mb-3">
150+
<a class="footer-link mx-2" href="index.html">Home</a>
151+
<a class="footer-link mx-2" href="resources.html">Resources</a>
152+
<a class="footer-link mx-2" href="editor.html">Metadata Editor</a>
153+
<a class="footer-link mx-2" href="collection.html">Metadata Collection</a>
154+
<a class="footer-link mx-2" href="viewer.html">Transcription Viewer</a>
155+
<a class="footer-link mx-2" href="contacts.html">Contacts</a>
156+
</div>
157+
158+
<!-- Copyright centered -->
159+
<div class="text-center mt-2">
160+
<span>© 2025 <span class="mano">&lt;MANO&gt;</span></span>
161+
</div>
162+
163+
</div>
164+
</footer>
165+
</div>
166+
<script>
167+
const repoOwner = "mano-project";
168+
const repoName = "mano-metadata";
169+
const folderPath = "data";
170+
171+
let allEntries = [];
172+
173+
async function fetchFileList() {
174+
const apiUrl = `https://api.github.com/repos/${repoOwner}/${repoName}/contents/${folderPath}`;
175+
const res = await fetch(apiUrl);
176+
const files = await res.json();
177+
return files.filter(file => file.name.endsWith(".json"));
178+
}
179+
180+
async function fetchJSONFile(url) {
181+
const res = await fetch(url);
182+
return await res.json();
183+
}
184+
185+
function getCentury(yearStr) {
186+
const year = parseInt(yearStr);
187+
if (isNaN(year)) return null;
188+
return Math.floor((year - 1) / 100) + 1;
189+
}
190+
191+
function createTableRow(entry) {
192+
const row = document.createElement("tr");
193+
row.innerHTML = `
194+
<td>${entry.msTitle || ""}</td>
195+
<td>${entry.repository || ""}</td>
196+
<td>${entry.settlementIdent || ""}, ${entry.countryIdent || ""}</td>
197+
<td>${entry.material || ""}</td>
198+
<td>${entry.digi ? `<a href="${entry.digi}" target="_blank">View</a>` : ""}</td>
199+
`;
200+
row.dataset.country = entry.countryIdent || "";
201+
row.dataset.century = getCentury(entry.dateOrigin) || "";
202+
row.dataset.persons = (entry.responsiblePersons || [])
203+
.map(p => `${p.name} ${p.surname}`.toLowerCase())
204+
.join(";");
205+
row.dataset.text = `${entry.msTitle || ""} ${entry.repository || ""}`.toLowerCase();
206+
return row;
207+
}
208+
209+
function populateFilters(entries) {
210+
const countries = new Set();
211+
const centuries = new Set();
212+
const persons = new Set();
213+
214+
entries.forEach(entry => {
215+
if (entry.countryIdent) countries.add(entry.countryIdent);
216+
const c = getCentury(entry.dateOrigin);
217+
if (c) centuries.add(`C.${c}`);
218+
(entry.responsiblePersons || []).forEach(p => {
219+
persons.add(`${p.surname}, ${p.name}`);
220+
});
221+
});
222+
223+
const addOptions = (selectId, values) => {
224+
const select = document.getElementById(selectId);
225+
values = Array.from(values).sort();
226+
values.forEach(v => {
227+
const option = document.createElement("option");
228+
option.value = v;
229+
option.textContent = v;
230+
select.appendChild(option);
231+
});
232+
};
233+
234+
addOptions("countryFilter", countries);
235+
addOptions("centuryFilter", centuries);
236+
addOptions("personFilter", persons);
237+
}
238+
239+
function filterTable() {
240+
const textFilter = document.getElementById("searchInput").value.toLowerCase();
241+
const country = document.getElementById("countryFilter").value;
242+
const century = document.getElementById("centuryFilter").value.replace("C.", "");
243+
const person = document.getElementById("personFilter").value.toLowerCase();
244+
245+
document.querySelectorAll("#tableBody tr").forEach(row => {
246+
const matchesText = row.dataset.text.includes(textFilter);
247+
const matchesCountry = !country || row.dataset.country === country;
248+
const matchesCentury = !century || row.dataset.century === century;
249+
const matchesPerson = !person || row.dataset.persons.includes(person);
250+
251+
row.style.display = matchesText && matchesCountry && matchesCentury && matchesPerson ? "" : "none";
252+
});
253+
}
254+
255+
async function loadMetadata() {
256+
const tableBody = document.getElementById("tableBody");
257+
const fileList = await fetchFileList();
258+
259+
for (const file of fileList) {
260+
try {
261+
const record = await fetchJSONFile(file.download_url);
262+
const entries = Array.isArray(record) ? record : [record];
263+
entries.forEach(entry => {
264+
allEntries.push(entry);
265+
const row = createTableRow(entry);
266+
tableBody.appendChild(row);
267+
});
268+
} catch (err) {
269+
console.error("Error loading file:", file.name, err);
270+
}
271+
}
272+
273+
populateFilters(allEntries);
274+
}
275+
276+
loadMetadata();
277+
</script>
278+
</body>
279+
280+
</html>

0 commit comments

Comments
 (0)