Initial Commit
This commit is contained in:
parent
d2d00ffa4d
commit
12d84c43c9
|
@ -14,7 +14,8 @@
|
||||||
"tiddlywiki/dynannotate",
|
"tiddlywiki/dynannotate",
|
||||||
"tiddlywiki/codemirror",
|
"tiddlywiki/codemirror",
|
||||||
"tiddlywiki/menubar",
|
"tiddlywiki/menubar",
|
||||||
"tiddlywiki/jszip"
|
"tiddlywiki/jszip",
|
||||||
|
"tiddlywiki/multiwikiserver"
|
||||||
],
|
],
|
||||||
"themes": [
|
"themes": [
|
||||||
"tiddlywiki/vanilla",
|
"tiddlywiki/vanilla",
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
{
|
{
|
||||||
"description": "TiddlyWiki core tests",
|
"description": "TiddlyWiki core tests",
|
||||||
"plugins": [
|
"plugins": [
|
||||||
"tiddlywiki/jasmine"
|
"tiddlywiki/jasmine",
|
||||||
|
"tiddlywiki/multiwikiserver"
|
||||||
],
|
],
|
||||||
"themes": [
|
"themes": [
|
||||||
"tiddlywiki/vanilla",
|
"tiddlywiki/vanilla",
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
title: $:/plugins/tiddlywiki/multiwikiserver/readme
|
||||||
|
|
||||||
|
This plugin extends the TiddlyWiki 5 server running on Node.js to be able to host multiple wikis, which can share content or be independent.
|
||||||
|
|
||||||
|
Before using the plugin, it is necessary to install dependencies by running the following command in the root of the ~TiddlyWiki5 repository:
|
||||||
|
|
||||||
|
```
|
||||||
|
npm install better-sqlite3
|
||||||
|
```
|
|
@ -0,0 +1,53 @@
|
||||||
|
/*\
|
||||||
|
title: $:/plugins/tiddlywiki/multiwikiserver/init.js
|
||||||
|
type: application/javascript
|
||||||
|
module-type: startup
|
||||||
|
|
||||||
|
Multi wiki server initialisation
|
||||||
|
|
||||||
|
\*/
|
||||||
|
(function(){
|
||||||
|
|
||||||
|
/*jslint node: true, browser: true */
|
||||||
|
/*global $tw: false */
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
// Export name and synchronous status
|
||||||
|
exports.name = "multiwikiserver";
|
||||||
|
exports.platforms = ["node"];
|
||||||
|
exports.before = ["story"];
|
||||||
|
exports.synchronous = true;
|
||||||
|
|
||||||
|
exports.startup = function() {
|
||||||
|
// Install the sqlite3 global namespace
|
||||||
|
$tw.sqlite3 = {
|
||||||
|
Database: null
|
||||||
|
};
|
||||||
|
// Check that better-sqlite3 is installed
|
||||||
|
var logger = new $tw.utils.Logger("multiwikiserver");
|
||||||
|
try {
|
||||||
|
$tw.sqlite3.Database = require("better-sqlite3");
|
||||||
|
} catch(e) {
|
||||||
|
}
|
||||||
|
if(!$tw.sqlite3.Database) {
|
||||||
|
logger.alert("The plugin 'tiddlywiki/multiwikiserver' requires the better-sqlite3 npm package to be installed. Run 'npm install better-sqlite3' in the root of the TiddlyWiki repository");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Create and initialise the tiddler store
|
||||||
|
var SqlTiddlerStore = require("$:/plugins/tiddlywiki/multiwikiserver/sql-tiddler-store.js").SqlTiddlerStore;
|
||||||
|
$tw.sqlTiddlerStore = new SqlTiddlerStore({});
|
||||||
|
$tw.sqlTiddlerStore.createTables();
|
||||||
|
// Create bags and recipes
|
||||||
|
$tw.sqlTiddlerStore.saveBag("bag-alpha");
|
||||||
|
$tw.sqlTiddlerStore.saveBag("bag-beta");
|
||||||
|
$tw.sqlTiddlerStore.saveBag("bag-gamma");
|
||||||
|
$tw.sqlTiddlerStore.saveRecipe("recipe-rho",["bag-alpha","bag-beta"]);
|
||||||
|
$tw.sqlTiddlerStore.saveRecipe("recipe-sigma",["bag-alpha","bag-gamma"]);
|
||||||
|
// Save tiddlers
|
||||||
|
$tw.sqlTiddlerStore.saveTiddler({title: "Another Tiddler",text: "I'm in alpha",tags: "one two three"},"bag-alpha");
|
||||||
|
$tw.sqlTiddlerStore.saveTiddler({title: "Hello There",text: "I'm in alpha as well",tags: "one two three"},"bag-alpha");
|
||||||
|
$tw.sqlTiddlerStore.saveTiddler({title: "Hello There",text: "I'm in beta",tags: "four five six"},"bag-beta");
|
||||||
|
$tw.sqlTiddlerStore.saveTiddler({title: "Hello There",text: "I'm in gamma",tags: "seven eight nine"},"bag-gamma");
|
||||||
|
};
|
||||||
|
|
||||||
|
})();
|
|
@ -0,0 +1,60 @@
|
||||||
|
/*\
|
||||||
|
title: $:/plugins/tiddlywiki/multiwikiserver/route-wiki.js
|
||||||
|
type: application/javascript
|
||||||
|
module-type: route
|
||||||
|
|
||||||
|
GET /wikis/:recipe_name
|
||||||
|
|
||||||
|
\*/
|
||||||
|
(function() {
|
||||||
|
|
||||||
|
/*jslint node: true, browser: true */
|
||||||
|
/*global $tw: false */
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
exports.method = "GET";
|
||||||
|
|
||||||
|
exports.path = /^\/wiki\/(.+)$/;
|
||||||
|
|
||||||
|
exports.handler = function(request,response,state) {
|
||||||
|
// Get the recipe name from the parameters
|
||||||
|
var recipe_name = $tw.utils.decodeURIComponentSafe(state.params[0]);
|
||||||
|
// Get the tiddlers in the recipe
|
||||||
|
var titles = $tw.sqlTiddlerStore.getRecipeTiddlers(recipe_name);
|
||||||
|
// Render the template
|
||||||
|
var template = $tw.wiki.renderTiddler("text/plain","$:/core/templates/tiddlywiki5.html",{
|
||||||
|
variables: {
|
||||||
|
saveTiddlerFilter: `
|
||||||
|
$:/boot/boot.css
|
||||||
|
$:/boot/boot.js
|
||||||
|
$:/boot/bootprefix.js
|
||||||
|
$:/core
|
||||||
|
$:/library/sjcl.js
|
||||||
|
$:/themes/tiddlywiki/snowwhite
|
||||||
|
$:/themes/tiddlywiki/vanilla
|
||||||
|
`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// Splice in our tiddlers
|
||||||
|
var marker = `<` + `script class="tiddlywiki-tiddler-store" type="application/json">[`,
|
||||||
|
markerPos = template.indexOf(marker);
|
||||||
|
if(markerPos === -1) {
|
||||||
|
throw new Error("Cannot find tiddler store in template");
|
||||||
|
}
|
||||||
|
var htmlParts = [];
|
||||||
|
htmlParts.push(template.substring(0,markerPos + marker.length));
|
||||||
|
$tw.utils.each(titles,function(title) {
|
||||||
|
htmlParts.push(JSON.stringify($tw.sqlTiddlerStore.getTiddler(title,recipe_name)));
|
||||||
|
htmlParts.push(",")
|
||||||
|
});
|
||||||
|
htmlParts.push(template.substring(markerPos + marker.length))
|
||||||
|
// Send response
|
||||||
|
if(htmlParts) {
|
||||||
|
state.sendResponse(200,{"Content-Type": "text/html"},htmlParts.join("\n"),"utf8");
|
||||||
|
} else {
|
||||||
|
response.writeHead(404);
|
||||||
|
response.end();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}());
|
|
@ -0,0 +1,263 @@
|
||||||
|
/*\
|
||||||
|
title: $:/plugins/tiddlywiki/multiwikiserver/sql-tiddler-store.js
|
||||||
|
type: application/javascript
|
||||||
|
module-type: library
|
||||||
|
|
||||||
|
Functions to perform basic tiddler operations with a sqlite3 database
|
||||||
|
|
||||||
|
\*/
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
|
||||||
|
function SqlTiddlerStore(options) {
|
||||||
|
// Create our database
|
||||||
|
this.db = new $tw.sqlite3.Database(":memory:",{verbose: undefined && console.log});
|
||||||
|
}
|
||||||
|
|
||||||
|
SqlTiddlerStore.prototype.close = function() {
|
||||||
|
this.db.close();
|
||||||
|
this.db = undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
SqlTiddlerStore.prototype.runStatement = function(sql,params) {
|
||||||
|
params = params || {};
|
||||||
|
const statement = this.db.prepare(sql);
|
||||||
|
statement.run(params);
|
||||||
|
};
|
||||||
|
|
||||||
|
SqlTiddlerStore.prototype.runStatementGet = function(sql,params) {
|
||||||
|
params = params || {};
|
||||||
|
const statement = this.db.prepare(sql);
|
||||||
|
return statement.get(params);
|
||||||
|
};
|
||||||
|
|
||||||
|
SqlTiddlerStore.prototype.runStatementGetAll = function(sql,params) {
|
||||||
|
params = params || {};
|
||||||
|
const statement = this.db.prepare(sql);
|
||||||
|
return statement.all(params);
|
||||||
|
};
|
||||||
|
|
||||||
|
SqlTiddlerStore.prototype.runStatements = function(sqlArray) {
|
||||||
|
for(const sql of sqlArray) {
|
||||||
|
this.runStatement(sql);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
SqlTiddlerStore.prototype.createTables = function() {
|
||||||
|
this.runStatements([`
|
||||||
|
-- Bags have names and access control settings
|
||||||
|
CREATE TABLE IF NOT EXISTS bags (
|
||||||
|
bag_id INTEGER PRIMARY KEY,
|
||||||
|
bag_name TEXT UNIQUE,
|
||||||
|
accesscontrol TEXT
|
||||||
|
)
|
||||||
|
`,`
|
||||||
|
-- Recipes have names...
|
||||||
|
CREATE TABLE IF NOT EXISTS recipes (
|
||||||
|
recipe_id INTEGER PRIMARY KEY,
|
||||||
|
recipe_name TEXT UNIQUE
|
||||||
|
)
|
||||||
|
`,`
|
||||||
|
-- ...and recipes also have an ordered list of bags
|
||||||
|
CREATE TABLE IF NOT EXISTS recipe_bags (
|
||||||
|
recipe_id INTEGER,
|
||||||
|
bag_id INTEGER,
|
||||||
|
position INTEGER,
|
||||||
|
FOREIGN KEY (recipe_id) REFERENCES recipes(recipe_id),
|
||||||
|
FOREIGN KEY (bag_id) REFERENCES bags(bag_id),
|
||||||
|
UNIQUE (recipe_id, bag_id)
|
||||||
|
)
|
||||||
|
`,`
|
||||||
|
-- Tiddlers are contained in bags and have titles
|
||||||
|
CREATE TABLE IF NOT EXISTS tiddlers (
|
||||||
|
tiddler_id INTEGER PRIMARY KEY,
|
||||||
|
bag_id INTEGER,
|
||||||
|
title TEXT,
|
||||||
|
FOREIGN KEY (bag_id) REFERENCES bags(bag_id),
|
||||||
|
UNIQUE (bag_id, title)
|
||||||
|
)
|
||||||
|
`,`
|
||||||
|
-- Tiddlers also have unordered lists of fields, each of which has a name and associated value
|
||||||
|
CREATE TABLE IF NOT EXISTS fields (
|
||||||
|
tiddler_id INTEGER,
|
||||||
|
field_name TEXT,
|
||||||
|
field_value TEXT,
|
||||||
|
FOREIGN KEY (tiddler_id) REFERENCES tiddlers(tiddler_id),
|
||||||
|
UNIQUE (tiddler_id, field_name)
|
||||||
|
)
|
||||||
|
`]);
|
||||||
|
};
|
||||||
|
|
||||||
|
SqlTiddlerStore.prototype.logTables = function() {
|
||||||
|
var self = this;
|
||||||
|
function sqlLogTable(table) {
|
||||||
|
console.log(`TABLE ${table}:`);
|
||||||
|
let statement = self.db.prepare(`select * from ${table}`);
|
||||||
|
for(const row of statement.all()) {
|
||||||
|
console.log(row);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const tables = ["recipes","bags","recipe_bags","tiddlers","fields"];
|
||||||
|
for(const table of tables) {
|
||||||
|
sqlLogTable(table);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
SqlTiddlerStore.prototype.saveBag = function(bagname) {
|
||||||
|
// Run the queries
|
||||||
|
this.runStatement(`
|
||||||
|
INSERT OR REPLACE INTO bags (bag_name, accesscontrol) VALUES ($bag_name, $accesscontrol)
|
||||||
|
`,{
|
||||||
|
bag_name: bagname,
|
||||||
|
accesscontrol: "[some access control stuff]"
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
SqlTiddlerStore.prototype.saveRecipe = function(recipename,bagnames) {
|
||||||
|
// Run the queries
|
||||||
|
this.runStatement(`
|
||||||
|
-- Insert or replace the recipe with the given name
|
||||||
|
INSERT OR REPLACE INTO recipes (recipe_name)
|
||||||
|
VALUES ($recipe_name)
|
||||||
|
`,{
|
||||||
|
recipe_name: recipename
|
||||||
|
});
|
||||||
|
this.runStatement(`
|
||||||
|
-- Insert bag names into recipe_bags for the given recipe name
|
||||||
|
INSERT INTO recipe_bags (recipe_id, bag_id, position)
|
||||||
|
SELECT r.recipe_id, b.bag_id, j.key
|
||||||
|
FROM (
|
||||||
|
SELECT * FROM json_each($bag_names)
|
||||||
|
) AS j
|
||||||
|
JOIN bags AS b ON b.bag_name = j.value
|
||||||
|
JOIN recipes AS r ON r.recipe_name = $recipe_name;
|
||||||
|
`,{
|
||||||
|
recipe_name: recipename,
|
||||||
|
bag_names: JSON.stringify(bagnames)
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
SqlTiddlerStore.prototype.saveTiddler = function(tiddlerFields,bagname) {
|
||||||
|
// Run the queries
|
||||||
|
this.runStatement(`
|
||||||
|
INSERT OR REPLACE INTO tiddlers (bag_id, title)
|
||||||
|
VALUES (
|
||||||
|
(SELECT bag_id FROM bags WHERE bag_name = $bag_name),
|
||||||
|
$title
|
||||||
|
)
|
||||||
|
`,{
|
||||||
|
title: tiddlerFields.title,
|
||||||
|
bag_name: bagname
|
||||||
|
});
|
||||||
|
this.runStatement(`
|
||||||
|
INSERT OR REPLACE INTO fields (tiddler_id, field_name, field_value)
|
||||||
|
SELECT
|
||||||
|
t.tiddler_id,
|
||||||
|
json_each.key AS field_name,
|
||||||
|
json_each.value AS field_value
|
||||||
|
FROM (
|
||||||
|
SELECT tiddler_id
|
||||||
|
FROM tiddlers
|
||||||
|
WHERE bag_id = (
|
||||||
|
SELECT bag_id
|
||||||
|
FROM bags
|
||||||
|
WHERE bag_name = $bag_name
|
||||||
|
) AND title = $title
|
||||||
|
) AS t
|
||||||
|
JOIN json_each($field_values) AS json_each
|
||||||
|
`,{
|
||||||
|
title: tiddlerFields.title,
|
||||||
|
bag_name: bagname,
|
||||||
|
field_values: JSON.stringify(Object.assign({},tiddlerFields,{title: undefined}))
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
SqlTiddlerStore.prototype.deleteTiddler = function(title,bagname) {
|
||||||
|
// Run the queries
|
||||||
|
this.runStatement(`
|
||||||
|
DELETE FROM fields
|
||||||
|
WHERE tiddler_id IN (
|
||||||
|
SELECT t.tiddler_id
|
||||||
|
FROM tiddlers AS t
|
||||||
|
INNER JOIN bags AS b ON t.bag_id = b.bag_id
|
||||||
|
WHERE b.bag_name = $bag_name AND t.title = $title
|
||||||
|
)
|
||||||
|
`,{
|
||||||
|
title: title,
|
||||||
|
bag_name: bagname
|
||||||
|
});
|
||||||
|
this.runStatement(`
|
||||||
|
DELETE FROM tiddlers
|
||||||
|
WHERE bag_id = (
|
||||||
|
SELECT bag_id
|
||||||
|
FROM bags
|
||||||
|
WHERE bag_name = $bag_name
|
||||||
|
) AND title = $title
|
||||||
|
`,{
|
||||||
|
title: title,
|
||||||
|
bag_name: bagname
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
SqlTiddlerStore.prototype.getTiddler = function(title,recipename) {
|
||||||
|
const rows = this.runStatementGetAll(`
|
||||||
|
SELECT field_name, field_value
|
||||||
|
FROM fields
|
||||||
|
WHERE tiddler_id = (
|
||||||
|
SELECT tt.tiddler_id
|
||||||
|
FROM (
|
||||||
|
SELECT bb.bag_id, t.tiddler_id
|
||||||
|
FROM (
|
||||||
|
SELECT b.bag_id
|
||||||
|
FROM bags AS b
|
||||||
|
INNER JOIN recipe_bags AS rb ON b.bag_id = rb.bag_id
|
||||||
|
INNER JOIN recipes AS r ON rb.recipe_id = r.recipe_id
|
||||||
|
WHERE r.recipe_name = $recipe_name
|
||||||
|
ORDER BY rb.position
|
||||||
|
) AS bb
|
||||||
|
INNER JOIN tiddlers AS t ON bb.bag_id = t.bag_id
|
||||||
|
WHERE t.title = $title
|
||||||
|
) AS tt
|
||||||
|
ORDER BY tt.tiddler_id DESC
|
||||||
|
LIMIT 1
|
||||||
|
)
|
||||||
|
`,{
|
||||||
|
title: title,
|
||||||
|
recipe_name: recipename
|
||||||
|
});
|
||||||
|
if(rows.length === 0) {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
return rows.reduce((accumulator,value) => {
|
||||||
|
accumulator[value["field_name"]] = value.field_value;
|
||||||
|
return accumulator;
|
||||||
|
},{title: title});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
Get the titles of the tiddlers in a recipe. Returns an empty array for recipes that do not exist
|
||||||
|
*/
|
||||||
|
SqlTiddlerStore.prototype.getRecipeTiddlers = function(recipename) {
|
||||||
|
const rows = this.runStatementGetAll(`
|
||||||
|
SELECT DISTINCT title
|
||||||
|
FROM tiddlers
|
||||||
|
WHERE bag_id IN (
|
||||||
|
SELECT bag_id
|
||||||
|
FROM recipe_bags
|
||||||
|
WHERE recipe_id = (
|
||||||
|
SELECT recipe_id
|
||||||
|
FROM recipes
|
||||||
|
WHERE recipe_name = $recipe_name
|
||||||
|
)
|
||||||
|
)
|
||||||
|
ORDER BY title ASC
|
||||||
|
`,{
|
||||||
|
recipe_name: recipename
|
||||||
|
});
|
||||||
|
return rows.map(value => value.title);
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.SqlTiddlerStore = SqlTiddlerStore;
|
||||||
|
|
||||||
|
})();
|
|
@ -0,0 +1,61 @@
|
||||||
|
/*\
|
||||||
|
title: tests-sql-tiddler-store.js
|
||||||
|
type: application/javascript
|
||||||
|
tags: [[$:/tags/test-spec]]
|
||||||
|
|
||||||
|
Tests the SQL tiddler store
|
||||||
|
|
||||||
|
\*/
|
||||||
|
(function(){
|
||||||
|
|
||||||
|
/*jslint node: true, browser: true */
|
||||||
|
/*global $tw: false */
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
if($tw.node) {
|
||||||
|
|
||||||
|
describe("SQL tiddler store", function() {
|
||||||
|
// Create and initialise the tiddler store
|
||||||
|
var SqlTiddlerStore = require("$:/plugins/tiddlywiki/multiwikiserver/sql-tiddler-store.js").SqlTiddlerStore;
|
||||||
|
const sqlTiddlerStore = new SqlTiddlerStore({});
|
||||||
|
sqlTiddlerStore.createTables();
|
||||||
|
// Create bags and recipes
|
||||||
|
sqlTiddlerStore.saveBag("bag-alpha");
|
||||||
|
sqlTiddlerStore.saveBag("bag-beta");
|
||||||
|
sqlTiddlerStore.saveBag("bag-gamma");
|
||||||
|
sqlTiddlerStore.saveRecipe("recipe-rho",["bag-alpha","bag-beta"]);
|
||||||
|
sqlTiddlerStore.saveRecipe("recipe-sigma",["bag-alpha","bag-gamma"]);
|
||||||
|
// Tear down
|
||||||
|
afterAll(function() {
|
||||||
|
// Close the database
|
||||||
|
sqlTiddlerStore.close();
|
||||||
|
});
|
||||||
|
// Run tests
|
||||||
|
it("should save and retrieve tiddlers", function() {
|
||||||
|
// Save tiddlers
|
||||||
|
sqlTiddlerStore.saveTiddler({title: "Another Tiddler",text: "I'm in alpha",tags: "one two three"},"bag-alpha");
|
||||||
|
sqlTiddlerStore.saveTiddler({title: "Hello There",text: "I'm in alpha as well",tags: "one two three"},"bag-alpha");
|
||||||
|
sqlTiddlerStore.saveTiddler({title: "Hello There",text: "I'm in beta",tags: "four five six"},"bag-beta");
|
||||||
|
sqlTiddlerStore.saveTiddler({title: "Hello There",text: "I'm in gamma",tags: "seven eight nine"},"bag-gamma");
|
||||||
|
// Verify what we've got
|
||||||
|
expect(sqlTiddlerStore.getRecipeTiddlers("recipe-rho")).toEqual([ "Another Tiddler", "Hello There"]);
|
||||||
|
expect(sqlTiddlerStore.getRecipeTiddlers("recipe-sigma")).toEqual([ "Another Tiddler", "Hello There"]);
|
||||||
|
expect(sqlTiddlerStore.getTiddler("Hello There","recipe-rho")).toEqual({ title: "Hello There", text: "I'm in beta", tags: "four five six" });
|
||||||
|
expect(sqlTiddlerStore.getTiddler("Missing Tiddler","recipe-rho")).toEqual(null);
|
||||||
|
expect(sqlTiddlerStore.getTiddler("Another Tiddler","recipe-rho")).toEqual({ title: "Another Tiddler", text: "I'm in alpha", tags: "one two three" });
|
||||||
|
expect(sqlTiddlerStore.getTiddler("Hello There","recipe-sigma")).toEqual({ title: "Hello There", text: "I'm in gamma", tags: "seven eight nine" });
|
||||||
|
expect(sqlTiddlerStore.getTiddler("Another Tiddler","recipe-sigma")).toEqual({ title: "Another Tiddler", text: "I'm in alpha", tags: "one two three" });
|
||||||
|
// Delete a tiddler to ensure the underlying tiddler in the recipe shows through
|
||||||
|
sqlTiddlerStore.deleteTiddler("Hello There","bag-beta");
|
||||||
|
expect(sqlTiddlerStore.getRecipeTiddlers("recipe-rho")).toEqual([ "Another Tiddler", "Hello There"]);
|
||||||
|
expect(sqlTiddlerStore.getRecipeTiddlers("recipe-sigma")).toEqual([ "Another Tiddler", "Hello There"]);
|
||||||
|
expect(sqlTiddlerStore.getTiddler("Hello There","recipe-beta")).toEqual(null);
|
||||||
|
sqlTiddlerStore.deleteTiddler("Another Tiddler","bag-alpha");
|
||||||
|
expect(sqlTiddlerStore.getRecipeTiddlers("recipe-rho")).toEqual([ "Hello There"]);
|
||||||
|
expect(sqlTiddlerStore.getRecipeTiddlers("recipe-sigma")).toEqual([ "Hello There"]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
})();
|
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"title": "$:/plugins/tiddlywiki/multiwikiserver",
|
||||||
|
"name": "Multi Wiki Server",
|
||||||
|
"description": "Multiple Wiki Server Extension",
|
||||||
|
"list": "readme",
|
||||||
|
"dependents": []
|
||||||
|
}
|
Loading…
Reference in New Issue