Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
5a0ff40
Hydrate from seed when fetch fails
cubap Feb 6, 2026
a71968c
runVaulttest
cubap Feb 6, 2026
de6307b
vault all fetches
cubap Feb 6, 2026
36b1e91
Prefetch manifests before resolving canvases
cubap Feb 6, 2026
2953bea
Use vault and prefetch manifests; enhance Vault
cubap Feb 6, 2026
ae6d1ec
Process IIIF resources and simplify ID normalization
cubap Feb 9, 2026
4ec5596
Update interfaces/transcription/index.js
cubap Feb 9, 2026
c1e91b1
Update components/read-only-transcribe/index.js
cubap Feb 9, 2026
973e4cf
Guard against empty URI in vault fetch to prevent unnecessary request…
Copilot Feb 9, 2026
78bd3b0
Add prefetchManifests and prefetchCollections aliases with explicit t…
Copilot Feb 9, 2026
ab4db40
Eliminate N+1 fetches for embedded IIIF resources in vault (#439)
Copilot Feb 9, 2026
db8698c
Add explicit IIIF v2 prefixed types to resource set (#440)
Copilot Feb 9, 2026
b81735c
Update vault.js
cubap Feb 9, 2026
5185901
Merge branch 'vaulting' of https://github.com/CenterForDigitalHumanit…
cubap Feb 9, 2026
6ec5ad1
deprecate these
thehabes Feb 9, 2026
71420f6
strip out TPEN.js and vault.js
cubap Feb 9, 2026
489d059
Import vault in read-only-transcribe component
cubap Feb 9, 2026
de19429
This will work here (#443)
thehabes Feb 9, 2026
32fc6ae
Early guard to avoid NPEs when this.#transcriptions cannot be used (#…
thehabes Feb 9, 2026
01f8684
Address Issues 13-17 from static review: Fix vault consistency, error…
Copilot Feb 9, 2026
2bafa53
Fix vault.js issues 8-11: Hoist constants, fix noCache, clone data, a…
cubap Feb 9, 2026
45d2cb7
Update Conditionals to avoid NPEs (#447)
thehabes Feb 9, 2026
44b58ca
Use vault.getWithFallback for fetching
cubap Feb 9, 2026
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
28 changes: 12 additions & 16 deletions components/annotorious-annotator/line-parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { detectTextLinesCombined } from "./detect-lines.js"
import { v4 as uuidv4 } from "https://cdn.skypack.dev/uuid@9.0.1"
import { CleanupRegistry } from '../../utilities/CleanupRegistry.js'
import { onProjectReady } from '../../utilities/projectReady.js'
import vault from '../../js/vault.js'
import '../page-selector/index.js'

class AnnotoriousAnnotator extends HTMLElement {
Expand Down Expand Up @@ -553,7 +554,7 @@ class AnnotoriousAnnotator extends HTMLElement {

/**
* Fetch a Canvas URI and check that it is a Canvas object. Pass it forward to render the Image into the interface.
* Be prepared to recieve presentation api 2+
* Be prepared to receive presentation api 2+
*
* FIXME
* Give users a path when Canvas URIs do not resolve or resolve to something unexpected.
Expand All @@ -562,20 +563,15 @@ class AnnotoriousAnnotator extends HTMLElement {
*/
async processCanvas(uri) {
if (!uri) return
// TODO Vault me?
const resolvedCanvas = await fetch(uri)
.then(r => {
if (!r.ok) throw r
return r.json()
})
.catch(e => {
this.shadowRoot.innerHTML = `
<h3>Canvas Error</h3>
<p>The Canvas within this Page could not be loaded.</p>
<p> ${e.status ?? e.code}: ${e.statusText ?? e.message} </p>
`
throw e
})
let resolvedCanvas = await vault.getWithFallback(uri, 'canvas', TPEN.activeProject?.manifest)
if (!resolvedCanvas) {
this.shadowRoot.innerHTML = `
<h3>Canvas Error</h3>
<p>The Canvas within this Page could not be loaded.</p>
<p>The Canvas could not be resolved or is invalid.</p>
`
return
}
const context = resolvedCanvas["@context"]
if (!context?.includes("iiif.io/api/presentation/3/context.json")) {
console.warn("The Canvas object did not have the IIIF Presentation API 3 context and may not be parseable.")
Expand Down Expand Up @@ -619,7 +615,7 @@ class AnnotoriousAnnotator extends HTMLElement {
throw new Error("Cannot Resolve Canvas Image", { "cause": "The Image is 404 or unresolvable." })
}
let imgx = resolvedCanvas?.items?.[0]?.items?.[0]?.body?.width
if (!imgx) imgx = resolvedCanvas?.images[0]?.resource?.width
if (!imgx) imgx = resolvedCanvas?.images?.[0]?.resource?.width
let imgy = resolvedCanvas?.items?.[0]?.items?.[0]?.body?.height
if (!imgy) imgy = resolvedCanvas?.images?.[0]?.resource?.height
this.#imageDims = [imgx, imgy]
Expand Down
3 changes: 2 additions & 1 deletion components/column-selector/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,8 @@ export default class ColumnSelector extends HTMLElement {
return { ...col, label: isAuto ? `Unnamed ${i + 1}` : col.label }
})

this.#page = await vault.get(pageId, 'annotationpage', true)
this.#page = await vault.getWithFallback(pageId, 'annotationpage', TPEN.activeProject?.manifest, true)
if (!this.#page) return
const { orderedItems, columnsInPage, allColumnLines } = orderPageItemsByColumns(
{ columns: this.columns, items: page?.items },
this.#page
Expand Down
27 changes: 7 additions & 20 deletions components/continue-working/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import TPEN from '../../api/TPEN.js'
import vault from '../../js/vault.js'
import { stringFromDate } from '/js/utils.js'
import { CleanupRegistry } from '../../utilities/CleanupRegistry.js'

Expand Down Expand Up @@ -177,26 +178,12 @@ class ContinueWorking extends HTMLElement {
if (!canvasId) return this.generateProjectPlaceholder(project)

let canvas, isV3
try {
canvas = await fetch(canvasId).then(r => r.json())
const context = canvas['@context']
isV3 = Array.isArray(context)
? context.some(ctx => typeof ctx === 'string' && ctx.includes('iiif.io/api/presentation/3'))
: typeof context === 'string' && context.includes('iiif.io/api/presentation/3')
} catch {
// Fetch manifest
const manifestUrl = project.manifest?.[0]
if (!manifestUrl) return this.generateProjectPlaceholder(project)

const manifest = await fetch(manifestUrl).then(r => r.json())
const context = manifest['@context']
isV3 = Array.isArray(context)
? context.some(ctx => typeof ctx === 'string' && ctx.includes('iiif.io/api/presentation/3'))
: typeof context === 'string' && context.includes('iiif.io/api/presentation/3')
const canvases = isV3 ? manifest.items : manifest.sequences?.[0]?.canvases
canvas = canvases?.find(c => (isV3 ? c.id : c['@id']) === canvasId)
if (!canvas) return this.generateProjectPlaceholder(project)
}
canvas = await vault.getWithFallback(canvasId, 'canvas', project.manifest)

if (!canvas) return this.generateProjectPlaceholder(project)

// Structure-based detection
isV3 = Array.isArray(canvas.items) || canvas.type === "Canvas"

// Get thumbnail from canvas
let thumbnailUrl = canvas.thumbnail?.id ?? canvas.thumbnail?.['@id'] ?? canvas.thumbnail
Expand Down
30 changes: 11 additions & 19 deletions components/default-transcribe/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@
* TpenTranscriptionElement - Default transcription view component.
* Displays line text and images for an annotation page.
* @element tpen-transcription
*
* @deprecated in favor of tpen-simple-transcription.
*/
import { userMessage, encodeContentState } from "../iiif-tools/index.js"
import vault from "../../js/vault.js"
import "../line-image/index.js"
import "../line-text/index.js"
import { Vault } from 'https://cdn.jsdelivr.net/npm/@iiif/helpers/+esm'

const vault = new Vault()

class TpenTranscriptionElement extends HTMLElement {
#transcriptionContainer
Expand Down Expand Up @@ -71,26 +71,18 @@ class TpenTranscriptionElement extends HTMLElement {
}

async #loadPage(annotationPageID) {
let page = { id: annotationPageID }
try {
page = vault.get({id:annotationPageID,type:"AnnotationPage"}) ?? await vault.load(annotationPageID)
} catch (err) {
switch (err.status ?? err.code) {
case 401:
return userMessage('Unauthorized')
case 403:
return userMessage('Forbidden')
case 404:
return userMessage('Project not found')
default:
return userMessage(err.message ?? err.statusText ?? err.text ?? 'Unknown error')
}
let page = await vault.getWithFallback(annotationPageID, 'annotationpage', TPEN.activeProject?.manifest)
if (!page) {
return userMessage('Failed to load page. Please try again.')
}
let lines = await Promise.all(page.items.flatMap(async l => {
const lineElem = document.createElement('tpen-line-text')
const lineImg = document.createElement('tpen-line-image')
lineElem.line = vault.get({id:l.id,type:"Annotation"}) ?? await vault.load(l.id)
lineElem.line.body[0] = vault.get({id:lineElem.line.body[0].id,type:"ContentResource"})
lineElem.line = await vault.get(l.id, 'annotation')
if (!lineElem.line) {
lineElem.line = await vault.get(l.id, 'annotation', true)
}
lineElem.line.body[0] = await vault.get(lineElem.line.body[0].id, 'contentresource')
lineElem.setAttribute('tpen-line-id', l.id)
lineImg.setAttribute('tpen-line-id', l.id)
lineImg.setAttribute('iiif-canvas', lineElem.line.target.source.id)
Expand Down
27 changes: 11 additions & 16 deletions components/legacy-annotator/plain.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,14 @@
*
* It is exposed to the user through /interfaces/annotator/legacy.html
* @element tpen-legacy-annotator
*
* @deprecated in favor of tpen-plain-annotator.
*/

import { eventDispatcher } from '../../api/events.js'
import TPEN from '../../api/TPEN.js'
import User from '../../api/User.js'
import vault from '../../js/vault.js'
import { CleanupRegistry } from '../../utilities/CleanupRegistry.js'

class LegacyAnnotator extends HTMLElement {
Expand Down Expand Up @@ -311,14 +314,10 @@ class LegacyAnnotator extends HTMLElement {

async processAnnotationPage(page) {
if(!page) return
const resolvedPage = await fetch(page)
.then(r => {
if(!r.ok) throw r
return r.json()
})
.catch(e => {
throw e
})
let resolvedPage = await vault.getWithFallback(page, 'annotationpage', TPEN.activeProject?.manifest)
if (!resolvedPage) {
throw new Error("Cannot Resolve AnnotationPage", {cause: "The AnnotationPage is 404 or unresolvable."})
}
const context = resolvedPage["@context"]
if(!(context.includes("iiif.io/api/presentation/3/context.json") || context.includes("w3.org/ns/anno.jsonld"))){
console.warn("The AnnotationPage object did not have the IIIF Presentation API 3 context and may not be parseable.")
Expand Down Expand Up @@ -396,14 +395,10 @@ class LegacyAnnotator extends HTMLElement {
const ctx = imageCanvas.getContext("2d")
let err
if(!canvas) return
const resolvedCanvas = await fetch(canvas)
.then(r => {
if(!r.ok) throw r
return r.json()
})
.catch(e => {
throw e
})
let resolvedCanvas = await vault.getWithFallback(canvas, 'canvas', TPEN.activeProject?.manifest)
if (!resolvedCanvas) {
throw new Error("Canvas Error", {cause: "The Canvas could not be resolved"})
}
const context = resolvedCanvas["@context"]
if(!context.includes("iiif.io/api/presentation/3/context.json")){
console.warn("The Canvas object did not have the IIIF Presentation API 3 context and may not be parseable.")
Expand Down
27 changes: 15 additions & 12 deletions components/line-image/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -204,18 +204,21 @@ class TpenImageFragment extends HTMLElement {
}

connectedCallback() {
this.cleanup.onDocument('canvas-change', (event) => {
fetch(event.detail.canvasId)
.then(res => res.json())
.then(canvas => {
this.#canvas = canvas
this.setContainerStyle()
const imageResource = canvas?.items?.[0]?.items?.[0]?.body?.id ?? canvas?.images?.[0]?.resource?.id
if (imageResource) {
this.#lineImage.src = imageResource
}
})
.catch(console.error)
this.cleanup.onDocument('canvas-change', async (event) => {
const canvasId = event.detail.canvasId
if (!canvasId) return

this.#canvas = { id: canvasId }
this.setContainerStyle()

// If canvas data is provided, extract image resource
if (event.detail.canvas) {
this.#canvas = event.detail.canvas
const imageResource = event.detail.canvas?.items?.[0]?.items?.[0]?.body?.id ?? event.detail.canvas?.images?.[0]?.resource?.["@id"]
if (imageResource) {
this.#lineImage.src = imageResource
}
}
})
}

Expand Down
13 changes: 6 additions & 7 deletions components/read-only-transcribe/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { checkIfUrlExists } from '../../utilities/checkIfUrlExists.js'
import vault from '../../js/vault.js'
import { CleanupRegistry } from '../../utilities/CleanupRegistry.js'

/**
Expand Down Expand Up @@ -324,12 +325,10 @@ class ReadOnlyViewTranscribe extends HTMLElement {
return
}

const response = await fetch(manifestUrl)
if (!response.ok) {
const errText = await response.text()
throw new Error(`GitHub read failed: ${response.status} - ${errText}`)
const manifest = await vault.get(manifestUrl, 'manifest')
if (!manifest) {
throw new Error(`Manifest could not be resolved for URL: ${manifestUrl}`)
}
const manifest = await response.json()
this.#staticManifest = manifest

this.shadowRoot.querySelector(".transcribe-title").textContent = `Transcription for ${manifest.label.none?.[0]}`
Expand Down Expand Up @@ -411,9 +410,9 @@ class ReadOnlyViewTranscribe extends HTMLElement {

async processCanvas(uri) {
if (!uri) return
let embeddedCanvas = this.#staticManifest?.items.find(c => (c.id ?? c['@id']) === uri)
let embeddedCanvas = this.#staticManifest?.items?.find(c => (c.id ?? c['@id']) === uri)
// Handle both Presentation API v3 (items) and v2 (images) formats
let fullImage = embeddedCanvas?.items?.[0]?.items?.[0]?.body?.id ?? embeddedCanvas?.images?.[0]?.resource?.id
let fullImage = embeddedCanvas?.items?.[0]?.items?.[0]?.body?.id ?? embeddedCanvas?.images?.[0]?.resource?.["@id"]
let imageService = embeddedCanvas?.items?.[0]?.items?.[0]?.body?.service?.id
let imgx = embeddedCanvas?.items?.[0]?.items?.[0]?.body?.width
let imgy = embeddedCanvas?.items?.[0]?.items?.[0]?.body?.height
Expand Down
13 changes: 5 additions & 8 deletions components/simple-transcription/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -481,7 +481,7 @@ export default class SimpleTranscriptionInterface extends HTMLElement {
}

// Use vault.get to fetch the page properly
const fetchedPage = await vault.get(pageID, 'annotationpage', true)
let fetchedPage = await vault.getWithFallback(pageID, 'annotationpage', TPEN.activeProject?.manifest, true)
if (!fetchedPage) {
TPEN.eventDispatcher.dispatch("tpen-toast", {
message: "Failed to load page. Please try again.",
Expand Down Expand Up @@ -515,24 +515,21 @@ export default class SimpleTranscriptionInterface extends HTMLElement {
canvasID = target.source
}

const fetchedCanvas = await vault.get(canvasID, 'canvas')
let fetchedCanvas = await vault.getWithFallback(canvasID, 'canvas', TPEN.activeProject?.manifest)
if (!fetchedCanvas) {
TPEN.eventDispatcher.dispatch("tpen-toast", {
message: "Could not load canvas. Please try again.",
status: "error"
})
return
}

this.#canvas = fetchedCanvas

// Get canvas dimensions (these are the authoritative dimensions for XYWH calculations)
this.#imgTopOriginalHeight = fetchedCanvas.height ?? 1000
this.#imgTopOriginalWidth = fetchedCanvas.width ?? 1000
this.#imgTopOriginalHeight = this.#canvas.height ?? 1000
this.#imgTopOriginalWidth = this.#canvas.width ?? 1000

// Get the image resource from the canvas
// Handle both Presentation API v3 (items) and v2 (images) formats
const imageResource = fetchedCanvas.items?.[0]?.items?.[0]?.body?.id ?? fetchedCanvas.images?.[0]?.resource?.id
const imageResource = fetchedCanvas.items?.[0]?.items?.[0]?.body?.id ?? fetchedCanvas.images?.[0]?.resource?.["@id"]

if (!imageResource) {
TPEN.eventDispatcher.dispatch("tpen-toast", {
Expand Down
3 changes: 2 additions & 1 deletion components/transcription-block/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ export default class TranscriptionBlock extends HTMLElement {
*/
async initializeAsync() {
const pageID = TPEN.screen?.pageInQuery
this.#page = await vault.get(pageID, 'annotationpage', true)
this.#page = await vault.getWithFallback(pageID, 'annotationpage', TPEN.activeProject?.manifest, true)
const projectPage = TPEN.activeProject.layers.flatMap(layer => layer.pages || []).find(p => p.id.split('/').pop() === pageID.split('/').pop())
if (!this.#page || !projectPage) return

Expand Down Expand Up @@ -508,6 +508,7 @@ export default class TranscriptionBlock extends HTMLElement {
}

updateTranscriptionUI() {
if (!this.#transcriptions) return
const previousLineText = this.#transcriptions[TPEN.activeLineIndex - 1] || 'No previous line'
const currentLineText = this.#transcriptions[TPEN.activeLineIndex] || ''
const prevLineElem = this.shadowRoot?.querySelector('.transcription-line')
Expand Down
Loading