Work with Obsidian vaults (plain Markdown notes) and automate via obsidian-cli.
npx skills add alistaircroll/pagelove --skill "client-libraries"
Install specific skill from multi-skill repository
# Description
Use when writing JavaScript for a Pagelove app β PLDocument, PLElement, DOMSubscriber, events, and the OPTIONS discovery protocol
# SKILL.md
name: client-libraries
description: "Use when writing JavaScript for a Pagelove app β PLDocument, PLElement, DOMSubscriber, events, and the OPTIONS discovery protocol"
Client Libraries
Two ES modules, loaded from the Pagelove CDN:
<script type="module">
import { PLDocument } from "https://cdn.pagelove.net/js/pagelove-primitives/1a5a161/index.mjs";
import { DOMSubscriber } from "https://cdn.pagelove.net/js/dom-subscriber/cde4007/index.mjs";
const doc = new PLDocument();
doc.OPTIONS(); // Fetches capabilities, attaches HTTP methods to DOM elements
</script>
CDN version note: The official docs reference hash
1a5a161for pagelove-primitives. Some existing apps use278bb6d. Both appear to work. When creating new apps, use the official1a5a161hash. Don't update working apps unless a specific fix is needed.
PLDocument
new PLDocument(url?)β represents the page; defaults towindow.location.hrefdoc.OPTIONS()β discovers capabilities from the server, then attaches.PUT(),.POST(),.DELETE()onto matching DOM elements via PLCapability eventsdoc.createElement(domElement)β (async) creates a PLElement wrapping the given DOM element, enabling scoped HTTP methods like.GET()on that element
PLElement (attached to DOM elements automatically by OPTIONS)
After doc.OPTIONS() resolves, matching DOM elements gain these methods:
element.PUT(body?)β replaces the element on the server; defaults to sendingelement.outerHTMLelement.POST(htmlString)β sends an HTML string to the server, which appends it as a child; the server's response HTML is parsed and the single resulting node is appended to the DOMelement.DELETE()β removes the element from both the DOM and the server-stored HTMLelement.selectorβ the auto-generated CSS selector used in theRangeheader
PLElements created via doc.createElement() additionally support:
plElement.GET()β (async) sends a scoped GET request for just this element; returns a new DOM element parsed from the server response. Does NOT modify the local DOM β the caller must diff or apply changes manually
POST Accepts HTML Strings
The official docs clarify that POST() accepts an HTML string (not just a DOM element). The response body is parsed and a single node is appended:
// Both work:
container.POST("<li>New item</li>"); // HTML string
container.POST(document.createElement('li')); // DOM element (serialized to outerHTML)
Selector Generation
The library generates CSS selectors for elements by:
1. Using the element's id attribute if present (e.g., #my-element)
2. Building :nth-child() paths anchored to the nearest parent with an ID
Multipart OPTIONS Protocol
For comprehensive capability discovery, the library sends:
OPTIONS /index.html HTTP/2
Accept: multipart/mixed
Prefer: return=representation
The server responds with 207 Multi-Status and multipart/mixed content type. Each boundary-separated part contains:
- Content-Range header with a CSS selector
- Allow header listing permitted methods for that selector
This enables the library to discover capabilities for all elements in a single round-trip.
Events
Two custom events are dispatched during operation:
PLCapability β emitted during OPTIONS discovery for each capability found:
document.addEventListener('PLCapability', (e) => {
console.log(e.detail.selector); // CSS selector
console.log(e.detail.allow); // ["GET", "PUT", ...]
});
PLCapability events bubble and are composed (cross shadow DOM boundaries).
PLMethodCompleted β emitted after any HTTP method request completes:
document.addEventListener('PLMethodCompleted', (e) => {
console.log(e.detail.method); // "PUT", "POST", "DELETE", etc.
console.log(e.detail.response); // Response object
});
DOMSubscriber
A MutationObserver wrapper that fires a callback whenever elements matching a CSS selector appear in (or are removed from) the DOM. Essential for binding event handlers to dynamically created elements.
DOMSubscriber.subscribe(rootElement, 'css-selector', (matchedElement) => {
// Fires immediately for existing matches
// Fires again whenever a new match appears in the DOM
});
When to use DOMSubscriber:
- Any app that uses POST (new elements arrive via server response and need event handlers)
- Any app that polls and replaces container contents (new DOM nodes need binding)
- Any app where elements can be created by other users (they appear during polling)
If your app uses POST or polls a container that holds interactive elements, import DOMSubscriber alongside PLDocument β it is not optional.
# Supported AI Coding Agents
This skill is compatible with the SKILL.md standard and works with all major AI coding agents:
Learn more about the SKILL.md standard and how to use these skills with your preferred AI coding agent.