Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions web/css/playground.css
Original file line number Diff line number Diff line change
Expand Up @@ -166,4 +166,9 @@ p{
padding: 8px 0px 0px 25px;
background: none;
border: none;
}

#annotationOutput {
white-space: pre-wrap;
text-align: left !important;
}
6 changes: 6 additions & 0 deletions web/js/toolsCatalog.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@ const ToolsCatalog = [
icon: "./images/uv-logo.png",
view: "https://universalviewer.io/",
description: "A viewer for web objects, allowing users to share their media with the world."
},
{
label: "Web Annotation Tool",
icon: "./images/rerum_logo.png",
view: "./web-annotation.html",
description: "A simple tool for creating and downloading Web Annotations in JSON-LD format."
}
];

Expand Down
166 changes: 166 additions & 0 deletions web/js/webAnnotationTool.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
const RERUM_SANDBOX_CREATE = "https://tinydev.rerum.io/app/create";

import { getStoredManifestLinks } from "./manifestStorage.js";
// ---------- Helpers ----------

// Check if URL is valid
function isValidHttpUrl(url) {
try {
const u = new URL(url);
return u.protocol === "http:" || u.protocol === "https:";
} catch {
return false;
}
}

// ---------- Annotation Builder ----------

function buildAnnotation(target, bodyText, motivation) {
const annotation = {
"@context": "http://www.w3.org/ns/anno.jsonld",
type: "Annotation",
motivation,
target
};

if (bodyText && bodyText.trim()) {
annotation.body = {
type: "TextualBody",
value: bodyText.trim(),
format: "text/plain"
};
}

return annotation;
}

async function saveAnnotationToRerum(annotation) {
const resp = await fetch(RERUM_SANDBOX_CREATE, {
method: "POST",
headers: {
"Content-Type": "application/json;charset=utf-8"
},
body: JSON.stringify(annotation)
});

if (!resp.ok) {
const text = await resp.text().catch(() => "");
throw new Error(`RERUM save failed: ${resp.status} ${resp.statusText} ${text}`);
}

const raw = await resp.json(); // full RERUM response (with new_obj_state etc.)

// This is the actual stored object. If new_obj_state exists, use that; otherwise fall back.
const stored = raw.new_obj_state || raw;

// Try to determine the URI in a robust way
const uri =
stored.id ||
stored['@id'] ||
raw['@id'] ||
resp.headers.get('location') ||
null;

// raw = full response, stored = “clean” object you want to show
return { raw, stored, uri };
}

// ---------- UI Logic ----------

document.addEventListener("DOMContentLoaded", () => {
const generateBtn = document.getElementById("generateBtn");
const output = document.getElementById("annotationOutput");
const downloadBtn = document.getElementById("downloadBtn");
const saveRerumBtn = document.getElementById("saveRerumBtn");
const statusEl = document.getElementById("rerumStatus");

let currentAnnotation = null;

// --- populate recent targets from manifests saved elsewhere ---
const recentTargetsList = document.getElementById("recentTargets");
if (recentTargetsList) {
const manifests = getStoredManifestLinks();
manifests.forEach(uri => {
const opt = document.createElement("option");
opt.value = uri;
recentTargetsList.appendChild(opt);
});
}

generateBtn.addEventListener("click", () => {
const target = document.getElementById("targetUrl").value.trim();
const body = document.getElementById("annotationBody").value.trim();
const motivation = document.getElementById("motivation").value;

if (!isValidHttpUrl(target)) {
alert("Please enter a valid http/https URL.");
return;
}

const annotation = buildAnnotation(target, body, motivation);
currentAnnotation = annotation;

output.textContent = JSON.stringify(annotation, null, 2);
downloadBtn.disabled = false;
if (saveRerumBtn) {
saveRerumBtn.disabled = false;
}
if (statusEl) {
statusEl.textContent = "";
}
});

// --- Download Annotation ---
downloadBtn.addEventListener("click", () => {
if (!currentAnnotation) return;

const blob = new Blob(
[JSON.stringify(currentAnnotation, null, 2)],
{ type: "application/json;charset=utf-8" }
);
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "annotation.json";
document.body.appendChild(a);
a.click();
a.remove();
URL.revokeObjectURL(url);
});

// --- Save Annotation to RERUM sandbox ---
if (saveRerumBtn) {
saveRerumBtn.addEventListener("click", async () => {
if (!currentAnnotation) return;

saveRerumBtn.disabled = true;
if (statusEl) statusEl.textContent = "Saving to RERUM sandbox…";

try {
const { raw, stored, uri } = await saveAnnotationToRerum(currentAnnotation);

// Use the cleaned version for display and download
currentAnnotation = stored;
output.textContent = JSON.stringify(stored, null, 2);

if (statusEl) {
if (uri) {
statusEl.innerHTML = `Saved to RERUM: <a href="${uri}"
target="_blank" rel="noopener noreferrer">${uri}</a>`;
} else {
statusEl.textContent = "Saved to RERUM (no URI returned).";
}
}
// (optional) If you also want to treat annotation URIs as "recent links",
// you could call storeManifestLink(uri) here, or make a parallel storeAnnotationLink().
} catch (err) {
console.error(err);
if (statusEl) {
statusEl.textContent = "Error saving to RERUM. See console for details.";
}
} finally {
saveRerumBtn.disabled = false;
}
});
}
});
87 changes: 87 additions & 0 deletions web/web-annotation.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Web Annotation Tool | Rerum Playground</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">

<!-- JS -->
<script src="./js/webAnnotationTool.js" type="module" defer></script>
<script src="./js/playground.js" type="module"></script>

<!-- CSS -->
<link rel="stylesheet" href="./css/playground.css">
<link rel="stylesheet" href="https://unpkg.com/chota@latest">

<style>
body{
padding-bottom: 90px;
}
</style>

</head>

<body>
<div id="menu-placeholder"></div>

<header>
<div class="header">
<div class="row">
<button onclick="openCloseMenu()" aria-label="Toggle Menu">
&#9776;
</button>
<img src="logo.png" alt="Rerum Playground">
</div>
</div>
</header>

<div class="container">
<div class="placeholder" style="height: 50px;"></div>

<div id="content">
<h2>Web Annotation Generator</h2>
<p>Create simple W3C Web Annotations targeting any URI on the web.</p>

<form id="annotationForm" class="tool-form">

<label>Target URI *</label>
<input list="recentTargets" type="url" id="targetUrl"
placeholder="https://example.org/page" required>

<datalist id="recentTargets"></datalist>

<label>Annotation Body (optional)</label>
<textarea id="annotationBody" placeholder="Write annotation text..."></textarea>

<label>Motivation</label>
<select id="motivation">
<option value="commenting">Commenting</option>
<option value="tagging">Tagging</option>
<option value="highlighting">Highlighting</option>
<option value="linking">Linking</option>
<option value="describing">Describing</option>
</select>

<button type="button" id="generateBtn" class="button primary">
Generate Annotation
</button>
</form>

<h3>Generated Annotation</h3>
<pre id="annotationOutput" class="output-box"></pre>

<button id="downloadBtn" class="button" disabled>
Download JSON
</button>

<button id="saveRerumBtn" class="button secondary" disabled>
Save to RERUM (sandbox)
</button>

<p id="rerumStatus" class="status"></p>
</div>
</div>

<div id="footer-placeholder"></div>
</body>
</html>
Loading