About Frame Integrations

The Frame plugin offers several hooks for authors interested in extending the behavior of federated wiki. JavaScript programs in the framed content can use the postMessage() protocol to tell the wiki client to follow a collaborative link, create a ghost page, or import pages into the hosting wiki.

Each button in this example demonstrates a different integration. Example code behind the buttons are provided below.

PLUGIN frame/integrations.html

Note: The keepLineup property enables the button inside the frame to treat the shift key the same way wiki readers have come to expect from links and buttons.

# Open an Existing Page

document.querySelector("button") .addEventListener("click", event => { window.parent.postMessage({ action:"doInternalLink", title: "About Frame Plugin", keepLineup: event.shiftKey }, "*"); });

# Open a Remote Page

document.querySelector("button") addEventListener("click", event => { window.parent.postMessage({ action:"doInternalLink", title: "Apparatus", site: "wiki.dbbs.co", keepLineup: event.shiftKey }, "*") })

# Create a Ghost Page

document.querySelector("button") .addEventListener("click", event => { window.parent.postMessage({ action:"showResult", page: { title: "Hello, World!", story: [ { type: "paragraph", text: "Greetings from frame content!", id: Math.abs(Math.random()*1e20|0) .toString(16) } ]}, keepLineup: event.shiftKey }, "*"); });

# Import Many Pages

function itemId() { return Math.abs(Math.random()*1e20|0).toString(16) } document.querySelector("button") .addEventListener("click", event => { window.parent.postMessage({ action:"importer", pages: { "hello-world": { title: "Hello, World!", story: [ { type: "paragraph", text: "Greetings from frame content!", id: itemId() } ]}, "greetings-programs": { title: "Greetings, Programs!", story: [ { type: "paragraph", text: "We're inside the MCP cone!", id: itemId() } ]} }, keepLineup: event.shiftKey }, "*"); });

# Send Frame Context

In some cases, authors construct framed content that needs to know about other items on the hosting page. This requires that the framed content install a message listener to receive the context information, and post a message requesting the frame context.

window.addEventListener("message", handleFrameContext) function handleFrameContext ({data}) { console.log("message received", data) if (data.action == "frameContext") { window.removeEventListener( "message", handleFrameContext) const {pageKey, itemId, origin, site, slug, item, page: {title, story}} = data const items = story.reduce((acc, {type}) => { acc[type] = (acc[type]||0)+1 return acc }, {}) console.log({title, items}) } } window.addEventListener( "DOMContentLoaded", () => window.parent.postMessage({ action: "sendFrameContext" }, "*") )

# Resize To Frame Content

Framed content can ask the wiki client to resize the height on the page to dynamically match the contents.

window.parent.postMessage({ action: "resize", height: document.body.offsetHeight }, "*")

# URL Includes Identifiers

We anticipate framed content needing information to facilitate collaboration with the wiki client. The following code provides a convenient way to discover the identifiers wiki uses for the page and item which host the framed content.

const identifiers = Object.fromEntries( new URLSearchParams( window.location.hash.substr(1) ).entries() ) console.log({ origin: identifiers.origin, title: identifiers.title, site: identifiers.site, slug: identifiers.slug, pageKey: identifiers.pageKey, itemId: identifiers.itemId })

# Request Neighborhood

An HTML script can ask wiki about the neighborhood. (In this example, we also show an idiom of constructing a promise for the reply from wiki.)

function requestNeighborhood() { return new Promise((resolve, reject) => { window.addEventListener("message", neighborhood) function neighborhood({data}) { if (data.action != "neighborhood") return window.removeEventListener( "message", neighborhood) resolve(data.neighborhood) } window.parent.postMessage({ action: "requestNeighborhood" }, "*") }) } document.querySelector("button") .addEventListener("click", async () => { let neighbors = await requestNeighborhood() console.log({neighbors}) })

There are many other resources around the federation about extending wiki in interesting and novel ways.

We define the schema for federated wiki pages to the depth that we know it. We use a BNF-like notation to suggest JSON elements. Any ambiguity will be resolved by examining the example. We close with a brief reflection on the successes of the format.

We built the federation based on the promiscuous sharing of simple things with the hope that this would lead the community to save indefinitely things worth saving. Within a year we had moved from ruby to node without leaving ruby sites behind. Now, in the federation's ninth year, we consider how this process might be further distributed.

We explain federated wiki plugins. We define their role interpreting content, recount our experience writing many, describe principles and strategies for future plugins, and offer a step-by-step guide for new plugin authors. We'll also collect pointers here to good plugins when they emerge.