Note: If you know how to improve the script, please edit this post!
To get started, simply clone the wiki, add your plugins, and enable the “Allow site to be loaded inside an iframe” option in the advanced settings:
To install the library, drag and drop the link in the “About” tiddler in the tiddlywiki of your choice.
You can test it here: plugin-library.tiddlyhost.com/
And here’s a wiki making use of that library: https://test-plugin-library.tiddlyhost.com/
Here’s the script that make it possible:
`<script>
/*\
title: $:/plugins/tiddlywiki/pluginlibrary/libraryserver.js
type: application/javascript
module-type: library
A simple HTTP-over-window.postMessage implementation of a standard TiddlyWeb-compatible server.
It uses real HTTP to load the individual tiddler JSON files.
\*/
(function () {
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
var assetList = [`{{assetList}}`];
// Listen for window messages
window.addEventListener(
"message",
function listener(event) {
console.log("plugin library: Received message from", event.origin);
console.log("plugin library: Message content", event.data);
switch (event.data.verb) {
case "GET":
if (event.data.url === "recipes/library/tiddlers.json") {
// Route for recipes/library/tiddlers.json
event.source.postMessage(
{
verb: "GET-RESPONSE",
status: "200",
cookies: event.data.cookies,
url: event.data.url,
type: "application/json",
body: JSON.stringify(assetList, null, 4),
},
"*"
);
} else if (event.data.url.indexOf("recipes/library/tiddlers/") === 0) {
var pluginTitle = removePrefix(
event.data.url,
"recipes/library/tiddlers/"
);
// Strip the `.json` suffix if present
if (pluginTitle.endsWith(".json")) {
pluginTitle = pluginTitle.slice(0, -5); // Remove the last 5 characters (".json")
}
var url =
"`{{$:/SiteUrl}}`/tiddlers.json?title=" +
pluginTitle +
"&pretty=1&include_system=1";
// Route for recipes/library/tiddlers/<uri-encoded-tiddler-title>.json
httpGet(url, function (err, responseText) {
if (err) {
event.source.postMessage(
{
verb: "GET-RESPONSE",
status: "404",
cookies: event.data.cookies,
url: event.data.url,
type: "text/plain",
body: "Not found",
},
"*"
);
} else {
var cleanedResponse = stripOuterBrackets(responseText);
event.source.postMessage(
{
verb: "GET-RESPONSE",
status: "200",
cookies: event.data.cookies,
url: event.data.url,
type: "application/json",
body: cleanedResponse,
},
"*"
);
}
});
} else {
event.source.postMessage(
{
verb: "GET-RESPONSE",
status: "404",
cookies: event.data.cookies,
url: event.data.url,
type: "text/plain",
body: "Not found",
},
"*"
);
}
break;
}
},
false
);
// Helper to remove string prefixes
function removePrefix(string, prefix) {
if (string.indexOf(prefix) === 0) {
return string.substr(prefix.length);
} else {
return string;
}
}
// Helper to strip outer brackets `[]` from JSON response
function stripOuterBrackets(jsonString) {
try {
var parsed = JSON.parse(jsonString);
if (Array.isArray(parsed) && parsed.length === 1) {
return JSON.stringify(parsed[0], null, 4); // Return the first (and only) object in the array
}
return jsonString; // Return as-is if not an array or has multiple items
} catch (error) {
console.error("Error parsing JSON:", error);
return jsonString; // Return original string if parsing fails
}
}
// Helper for HTTP GET
function httpGet(url, callback) {
var http = new XMLHttpRequest();
http.open("GET", url, true);
http.onreadystatechange = function () {
if (http.readyState == 4) {
if (http.status == 200) {
callback(null, http.responseText);
} else {
callback(new Error("HTTP error: " + http.status), null);
}
}
};
http.send();
}
})();
</script>`