Using JSONLD to bring data to main page

This commit is contained in:
EpicKiwi 2024-01-05 18:10:23 +01:00
parent 1d851e23a6
commit 026c439f02
Signed by: epickiwi
GPG Key ID: C4B28FD2729941CE
7 changed files with 6112 additions and 4 deletions

120
_src/js/data.js Normal file
View File

@ -0,0 +1,120 @@
import "../lib/jsonld.esm.js.js";
import jsonldRdfaParser from '../lib/jsonld8-rdfa.esm.js';
jsonld.registerRDFParser('text/html', jsonldRdfaParser);
export const GLOBAL_CONTEXT = {
"@vocab": "http://schema.org/",
"@language": "fr"
}
/**
* Fetch remote content data
* @param {String|URL} documentUrl Document URL to fetch
* @param {Object} context Optional context to format JSON
* @returns JSON-LD of data extracted from the provided page
*/
export async function fetchDocumentData(documentUrl, context = GLOBAL_CONTEXT){
let res = await fetch(documentUrl);
if(!res.ok)
throw new Error(`Network error ${res.status} ${res.statusText}`)
if(!res.headers.get("Content-Type").startsWith("text/html"))
throw new Error(`Invalid content type ${res.headers.get("Content-Type")}`)
let content = await res.text()
let remoteDoc = new DOMParser().parseFromString(content, "text/html");
let base = document.createElement("base")
base.href = documentUrl
remoteDoc.head.append(base)
return await getDocumentData(remoteDoc, context);
}
/**
* Extract data from a page document
* @param {Document} sourceDocument Page document to extract data from
* @param {Object} context Optional context to format JSON
* @returns JSON-LD of data extracted from the page
*/
export async function getDocumentData(sourceDocument = document, context = GLOBAL_CONTEXT) {
let resource = sourceDocument.body.getAttribute("resource")
if(resource)
resource = new URL(resource, sourceDocument.baseURI)
return await extractData(
sourceDocument.documentElement,
resource,
context
)
}
/**
* Get JSON data from a RDFa Formatted element
* @param {Element} sourceElement Source element to extract data from
* @param {Object} context Optional context to format JSON
* @returns JSON-LD of data extract from element
*/
export async function getData(sourceElement, context = GLOBAL_CONTEXT){
let resource = sourceElement.getAttribute("resource");
if(resource)
resource = new URL(resource, sourceElement.baseURI)
return await extractData(
sourceElement,
resource,
context
)
}
export function takeFirst(self){
if(!self){
return self
}
if(Array.isArray(self)){
return self[0]
} else {
return self
}
}
export function takeUrl(self){
if(!self){
return self
}
let first = takeFirst(self)
return first["@id"]
}
async function extractData(baseElement, rootId, context){
let sourceData = await jsonld.fromRDF(baseElement, {format: 'text/html'});
if(rootId) {
let frame = {
"@id": rootId
}
if(context) {
frame["@context"] = context
}
return await jsonld.frame(sourceData, frame)
} else if (context) {
return await jsonld.compact(sourceData, context)
} else {
return sourceData
}
}

View File

@ -38,7 +38,7 @@
<header>
<h1><a href="/">
<img src="/_src/img/logo_kiosque.svg" alt="Logo du site" />
<span>Infokiosques du LOL <small>Brochures et autres textes</small></span>
<span>Infokiosque du LOL <small>Brochures et autres textes</small></span>
</a></h1>
</header>

3413
_src/lib/jsonld.esm.js.js Normal file

File diff suppressed because one or more lines are too long

2435
_src/lib/jsonld8-rdfa.esm.js Normal file

File diff suppressed because it is too large Load Diff

View File

@ -103,9 +103,13 @@ ul li > a:first-child::before {
font-family: "Open Sans", sans-serif;
margin-left: -1.5ex;
margin-right: 0.5ex;
}
i, ul li > a:first-child::before {
font-size: 1.5em;
vertical-align: middle;
display: inline-block;
font-style: normal;
}
ul li > a:first-child:hover::before {
@ -113,6 +117,59 @@ ul li > a:first-child:hover::before {
transform: translateX(0.25ex);
}
#publications ul {
padding-left: 0;
}
#publications ul li > a:first-child::before {
content: unset;
}
#publications ul li > a {
text-decoration: none;
}
#publications .publication-card {
margin-bottom: 50px;
}
body > section {
margin-top: 10vh;
}
/* publication card */
.publication-card {
display: grid;
grid-template-columns: 100px 1fr;
grid-template-rows: 1fr;
grid-auto-rows: min-content;
column-gap: 15px;
text-decoration: none !important;
width: 100%;
}
.publication-card > img:first-child {
grid-column: 1;
grid-row: 1 / 3;
width: 100%;
}
.publication-card h1 {
font-size: 1.5em;
margin: 0;
}
.publication-card p {
margin: 0;
margin-top: 15px;
grid-column: 1 / 3;
}
.publication-card nav {
text-align: right;
margin-top: 15px;
grid-column: 1 / 3;
}

View File

@ -4,7 +4,7 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Infokiosques LOL</title>
<title>Infokiosque LOL</title>
<link rel="stylesheet" href="/_src/styles/global.css" />
@ -13,9 +13,10 @@
<img src="/_src/img/logo_kiosque.svg" alt="Logo du site" />
<h1 property="name">Inforkiosques du LOL <small>Brochures et autres textes</small></h1>
<h1 property="name">Inforkiosque du LOL <small>Brochures et autres textes</small></h1>
<section id="publications"
property="hasPart"
typeof="Collection"
resource="#publications" >
@ -37,5 +38,87 @@
</ul>
</section>
<template id="publication-card-tmplt">
<article class="publication-card">
<img property="image" src="" alt="Couverture" />
<h1 property="name"></h1>
<div class="meta">
<span>Par <span property="author"></span></span>
</div>
<p property="abstract"></p>
<nav>
<a href="" property="url">Plus d'infos <i>🠺</i></a>
</nav>
</article>
</template>
<script type="module">
import {fetchDocumentData, takeFirst, takeUrl} from "/_src/js/data.js"
init()
async function init() {
for(let it of document.querySelectorAll("#publications ul > li a[href]")) {
let data = null;
try {
data = await fetchDocumentData(it.href)
} catch(e) {
continue;
}
let card = null;
try {
card = makePublicationCard(data)
} catch(e) {
continue;
}
it.innerHTML = ""
it.append(card)
}
}
function makePublicationCard(data){
let template = document.getElementById("publication-card-tmplt").content.cloneNode(true);
let root = template.children[0]
root.setAttribute("resource", takeFirst(data["@id"]))
root.setAttribute("typeof", takeFirst(data["@type"]))
let name = takeFirst(data.name)
if(name){
template.querySelector('[property="name"]').textContent = name
} else {
throw new Error("Provided data doesn't have a \"name\" field");
}
let author = takeFirst(data.author)
if(author){
template.querySelector('[property="author"]').textContent = author
} else {
template.querySelector('[property="author"]').parentElement.remove()
}
let abstract = takeFirst(data.abstract)
if(abstract){
template.querySelector('[property="abstract"]').textContent = abstract
} else {
template.querySelector('[property="abstract"]').remove()
}
let image = takeUrl(data.image)
if(image){
template.querySelector('[property="image"]').src = image
} else {
template.querySelector('[property="image"]').remove()
}
let url = takeUrl(data.url)
template.querySelector('[property="url"]').href = url || takeUrl(data)
return template
}
</script>
</body>
</html>

View File

@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Détruire le capitalisme de surveillance — Cory Doctorow — Infokiosques LOL</title>
<title>Détruire le capitalisme de surveillance — Cory Doctorow — Infokiosque LOL</title>
<link rel="stylesheet" href="/_src/styles/global.css" />
<link rel="prefetch" href="/_src/js/layout.html" />
<script defer src="/_src/js/layout.js"></script>