PDF viewer: passages, anchors and quotes
The add-on @skaile/forge-editor-pdf-viewer shows a PDF next to the text that cites it. A text selection in the PDF becomes an anchor: the page, the area on the page and the quoted words. The button „Zitat anlegen“ turns the anchor into a quote Typed block that is derived from the PDF. A click on a reference chip with a page locator scrolls the viewer to the passage.
| Piece | What it does | Where |
|---|---|---|
source-anchor | the Block property type of a passage of a PDF | library (@skaile/forge-editor/core) |
page=<n>&anchor=<base64 JSON> | the reference locator that names such a passage | library |
quoteBlockTypes(locale) | the Block type quote with one source-anchor property | editor and server |
anchorOfSelection, quoteBlockOf | pure: selection to anchor, anchor to quote block | anywhere |
pdfViewerFeature() | the viewer in the inspector of a Typed block that keeps a PDF | editor |
PdfViewer | the viewer as a component for any page | editor |
The source-anchor property and the locator
A source-anchor value is { page, rects, quote: { exact, prefix?, suffix? } }. page counts from 1. rects are fractions of the page (x, y, w, h from 0 to 1, origin top left), so the passage stays right at any zoom. quote.exact is the selected text with white space cut to single blanks; prefix and suffix are a little text around it.
The property kind orders and compares by page, reads as the quoted text and groups by page (PROPERTY_KINDS['source-anchor']), so a query can filter quotes by page or by words. The properties form shows page and quote read-only. Only the viewer writes the value.
The same passage as text is the locator of a reference chip: page=12&anchor=<base64url JSON>. anchorLocatorOf(anchor) writes it and parseAnchorLocator(text) reads it back (null when the text is not an anchor or its page differs from the locator's). Chips and backlinks show it as S. 12; the long base64 stays out of sight. A plain locator (S. 12, 12, 3:07) works as before.
When the editor reveals a source at an anchor locator, it sends two DOM events to the node view of the PDF's file block: sk-reveal-page (detail is the page) and sk-reveal-anchor (detail is the SourceAnchor). The viewer listens to both.
Install and wire
import { defaultBlockTypes } from '@skaile/forge-editor/composites'
import { quoteBlockTypes } from '@skaile/forge-editor-pdf-viewer'
import { pdfViewerFeature } from '@skaile/forge-editor-pdf-viewer/vue' // or '/react'
import '@skaile/forge-editor-pdf-viewer/styles/pdf-viewer.css'
const editor = composeEditor({
content: 'rich',
interaction: 'full',
// The Block types of the editor: the built-in ones and `quote`.
blockTypes: [...defaultBlockTypes(t), ...quoteBlockTypes('de')],
add: [pdfViewerFeature({ workerSrc: '/pdf.worker.min.mjs' })],
})
Give the Block type to the server too, so the schema, the AI chat and the query validation know it:
import { quoteBlockTypes } from '@skaile/forge-editor-pdf-viewer/server'
// installEditorServer({ blockTypes })
export const blockTypes = [...serverBlockTypes, ...quoteBlockTypes('de')]
pdfjs needs a worker. By default the viewer loads pdf.worker.min.mjs of the installed pdfjs version from jsDelivr. A host that cannot reach the CDN serves the file from pdfjs-dist/build/ itself and passes its URL as workerSrc (feature option, or prop of PdfViewer).
The viewer
pdfViewerFeature adds a section „PDF-Ansicht“ to the Typed block inspector. It shows for every block whose source (the source capability: paper, book, document) is a PDF file block. The viewer draws only the pages near the visible part, each as a canvas with a transparent text layer on top, so the text can be selected and searched with the browser.
- The toolbar has previous and next page, a page field, zoom out and in (50 % to 300 %).
- A reveal of the PDF's block scrolls to the page, or to the first line of the anchor, and marks the anchor's rects for a few seconds. Reveals of other blocks do not move this viewer.
PdfViewertakessrc(the URL of the PDF),editor(to create quotes and follow reveals) andblockId(the Typed block that keeps the PDF). Withouteditorit is a plain viewer.
Create a quote
Select text in one page and press Zitat anlegen. The button works when the editor is editable and the PDF is a stored asset of a Document. The quote goes right behind the PDF's Typed block and is revealed.
{
"type": "typedBlock",
"attrs": {
"blockType": "quote",
"meta": {
"anchor": { "page": 12, "rects": [{ "x": 0.15, "y": 0.1, "w": 0.5, "h": 0.025 }], "quote": { "exact": "Der Hund bellt laut." } },
"derivation": { "from": { "asset": "<name>.pdf" }, "method": "highlight", "inputHash": "<sha256 of the PDF>", "outputHash": "<sha256 of the body>", "createdAt": "…" }
}
}
}
The title is the first words of the quote. The body is the quoted text as a block quote and, when the PDF belongs to a Typed block, a reference chip to it whose locator is the anchor: a click on the chip in the quote opens the PDF at the passage. Because the quote has a Derivation, the block shows its origin, the Explore trail lists it, and an edit of the body shows as „bearbeitet“.
quoteBlockOf(asset, anchor, options) builds the same JSON without a viewer: use it in an importer or a server tool.
import { anchorOfSelection, quoteBlockOf } from '@skaile/forge-editor-pdf-viewer/server'
const anchor = anchorOfSelection({ page: 3, box: pageRect, rects: selectionRects, text, pageText })
if (anchor) editor.commands.insertContent(quoteBlockOf({ name, hash }, anchor, { sourceBlockId: paperId }))
Graph: the Edges around a Block
The graph route that walks the Edges of the Block index in both directions, the walk's limits, the client call, and the @skaile/forge-editor-graph add-on that draws the answer with sigma in Vue and React.
Obsidian: import and export a vault
The add-on @skaile/forge-editor-obsidian: importVault plans one Document per note with frontmatter as properties, wikilinks as reference chips and ^id block ids kept; exportVault writes them back; a command uploads a vault to an editor server.