mirror of
https://github.com/Jermolene/TiddlyWiki5
synced 2025-02-09 07:30:01 +00:00
Add support for the plugin library
We create a system bag to contain each plugin/theme/language. It seems wasteful because it results in lots of bags, but the semantics are exactly right and so it seems like the right approach
This commit is contained in:
parent
131a5abeb8
commit
9ba4556250
@ -3,7 +3,7 @@ title: $:/plugins/tiddlywiki/multiwikiserver/routes/handlers/get-index.js
|
||||
type: application/javascript
|
||||
module-type: mws-route
|
||||
|
||||
GET /
|
||||
GET /?show_system=true
|
||||
|
||||
\*/
|
||||
(function() {
|
||||
@ -31,6 +31,7 @@ exports.handler = function(request,response,state) {
|
||||
// Render the html
|
||||
var html = $tw.mws.store.adminWiki.renderTiddler("text/plain","$:/plugins/tiddlywiki/multiwikiserver/templates/page",{
|
||||
variables: {
|
||||
"show-system": state.queryParameters.show_system || "off",
|
||||
"page-content": "$:/plugins/tiddlywiki/multiwikiserver/templates/get-index",
|
||||
"bag-list": JSON.stringify(bagList),
|
||||
"recipe-list": JSON.stringify(recipeList)
|
||||
|
@ -46,30 +46,112 @@ function setupStore() {
|
||||
}
|
||||
|
||||
function loadStore(store) {
|
||||
const path = require("path");
|
||||
const path = require("path"),
|
||||
fs = require("fs");
|
||||
// Performance timing
|
||||
console.time("mws-initial-load");
|
||||
// Copy plugins
|
||||
var makePluginBagName = function(type,publisher,name) {
|
||||
return "$:/" + type + "/" + (publisher ? publisher + "/" : "") + name;
|
||||
},
|
||||
savePlugin = function(pluginFields,type,publisher,name) {
|
||||
const bagName = makePluginBagName(type,publisher,name);
|
||||
const result = store.createBag(bagName,pluginFields.description || "(no description)",{allowPrivilegedCharacters: true});
|
||||
store.saveBagTiddler(pluginFields,bagName);
|
||||
},
|
||||
collectPlugins = function(folder,type,publisher) {
|
||||
var pluginFolders = $tw.utils.getSubdirectories(folder) || [];
|
||||
for(var p=0; p<pluginFolders.length; p++) {
|
||||
const pluginFolderName = pluginFolders[p];
|
||||
if(!$tw.boot.excludeRegExp.test(pluginFolderName)) {
|
||||
var pluginFields = $tw.loadPluginFolder(path.resolve(folder,"./" + pluginFolderName));
|
||||
if(pluginFields && pluginFields.title) {
|
||||
savePlugin(pluginFields,type,publisher,pluginFolderName);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
collectPublisherPlugins = function(folder,type) {
|
||||
var publisherFolders = $tw.utils.getSubdirectories(folder) || [];
|
||||
for(var t=0; t<publisherFolders.length; t++) {
|
||||
const publisherFolderName = publisherFolders[t];
|
||||
if(!$tw.boot.excludeRegExp.test(publisherFolderName)) {
|
||||
collectPlugins(path.resolve(folder,"./" + publisherFolderName),type,publisherFolderName);
|
||||
}
|
||||
}
|
||||
};
|
||||
$tw.utils.each($tw.getLibraryItemSearchPaths($tw.config.pluginsPath,$tw.config.pluginsEnvVar),function(folder) {
|
||||
collectPublisherPlugins(folder,"plugin");
|
||||
});
|
||||
$tw.utils.each($tw.getLibraryItemSearchPaths($tw.config.themesPath,$tw.config.themesEnvVar),function(folder) {
|
||||
collectPublisherPlugins(folder,"theme");
|
||||
});
|
||||
$tw.utils.each($tw.getLibraryItemSearchPaths($tw.config.languagesPath,$tw.config.languagesEnvVar),function(folder) {
|
||||
collectPlugins(folder,"language");
|
||||
});
|
||||
// Copy TiddlyWiki core editions
|
||||
function copyEdition(options) {
|
||||
console.log(`Copying edition ${options.tiddlersPath}`);
|
||||
store.createBag(options.bagName,options.bagDescription);
|
||||
store.createRecipe(options.recipeName,[options.bagName],options.recipeDescription);
|
||||
store.saveTiddlersFromPath(path.resolve($tw.boot.corePath,$tw.config.editionsPath,options.tiddlersPath),options.bagName);
|
||||
// Read the tiddlywiki.info file
|
||||
const wikiInfoPath = path.resolve($tw.boot.corePath,$tw.config.editionsPath,options.wikiPath,$tw.config.wikiInfo);
|
||||
let wikiInfo;
|
||||
if(fs.existsSync(wikiInfoPath)) {
|
||||
wikiInfo = $tw.utils.parseJSONSafe(fs.readFileSync(wikiInfoPath,"utf8"),function() {return null;});
|
||||
}
|
||||
if(wikiInfo) {
|
||||
// Create the bag
|
||||
store.createBag(options.bagName,options.bagDescription);
|
||||
// Add plugins to the recipe list
|
||||
const recipeList = [];
|
||||
const processPlugins = function(type,plugins) {
|
||||
$tw.utils.each(plugins,function(pluginName) {
|
||||
const parts = pluginName.split("/");
|
||||
let publisher, name;
|
||||
if(parts.length === 2) {
|
||||
publisher = parts[0];
|
||||
name = parts[1];
|
||||
} else {
|
||||
name = parts[0];
|
||||
}
|
||||
recipeList.push(makePluginBagName(type,publisher,name));
|
||||
});
|
||||
};
|
||||
processPlugins("plugin",wikiInfo.plugins);
|
||||
processPlugins("theme",wikiInfo.themes);
|
||||
processPlugins("language",wikiInfo.languages);
|
||||
// Create the recipe
|
||||
recipeList.push(options.bagName);
|
||||
store.createRecipe(options.recipeName,recipeList,options.recipeDescription);
|
||||
store.saveTiddlersFromPath(path.resolve($tw.boot.corePath,$tw.config.editionsPath,options.wikiPath,$tw.config.wikiTiddlersSubDir),options.bagName);
|
||||
}
|
||||
}
|
||||
copyEdition({
|
||||
bagName: "docs",
|
||||
bagDescription: "TiddlyWiki Documentation from https://tiddlywiki.com",
|
||||
recipeName: "docs",
|
||||
recipeDescription: "TiddlyWiki Documentation from https://tiddlywiki.com",
|
||||
tiddlersPath: "tw5.com/tiddlers"
|
||||
wikiPath: "tw5.com"
|
||||
});
|
||||
copyEdition({
|
||||
bagName: "dev-docs",
|
||||
bagDescription: "TiddlyWiki Developer Documentation from https://tiddlywiki.com/dev",
|
||||
recipeName: "dev-docs",
|
||||
recipeDescription: "TiddlyWiki Developer Documentation from https://tiddlywiki.com/dev",
|
||||
tiddlersPath: "dev/tiddlers"
|
||||
wikiPath: "dev"
|
||||
});
|
||||
copyEdition({
|
||||
bagName: "tour",
|
||||
bagDescription: "TiddlyWiki Interactive Tour from https://tiddlywiki.com",
|
||||
recipeName: "tour",
|
||||
recipeDescription: "TiddlyWiki Interactive Tour from https://tiddlywiki.com",
|
||||
wikiPath: "tour"
|
||||
});
|
||||
// copyEdition({
|
||||
// bagName: "full",
|
||||
// bagDescription: "TiddlyWiki Fully Loaded Edition from https://tiddlywiki.com",
|
||||
// recipeName: "full",
|
||||
// recipeDescription: "TiddlyWiki Fully Loaded Edition from https://tiddlywiki.com",
|
||||
// wikiPath: "full"
|
||||
// });
|
||||
// Create bags and recipes
|
||||
store.createBag("bag-alpha","A test bag");
|
||||
store.createBag("bag-beta","Another test bag");
|
||||
|
@ -75,7 +75,7 @@ SqlTiddlerStore.prototype.dispatchEvent = function(type /*, args */) {
|
||||
/*
|
||||
Returns null if a bag/recipe name is valid, or a string error message if not
|
||||
*/
|
||||
SqlTiddlerStore.prototype.validateItemName = function(name) {
|
||||
SqlTiddlerStore.prototype.validateItemName = function(name,allowPrivilegedCharacters) {
|
||||
if(typeof name !== "string") {
|
||||
return "Not a valid string";
|
||||
}
|
||||
@ -83,8 +83,14 @@ SqlTiddlerStore.prototype.validateItemName = function(name) {
|
||||
return "Too long";
|
||||
}
|
||||
// Removed ~ from this list temporarily
|
||||
if(!(/^[^\s\u00A0\x00-\x1F\x7F`!@#$%^&*()+={}\[\];:\'\"<>.,\/\\\?]+$/g.test(name))) {
|
||||
return "Invalid character(s)";
|
||||
if(allowPrivilegedCharacters) {
|
||||
if(!(/^[^\s\u00A0\x00-\x1F\x7F`!@#%^&*()+={}\[\];\'\"<>,\\\?]+$/g.test(name))) {
|
||||
return "Invalid character(s)";
|
||||
}
|
||||
} else {
|
||||
if(!(/^[^\s\u00A0\x00-\x1F\x7F`!@#$%^&*()+={}\[\];:\'\"<>.,\/\\\?]+$/g.test(name))) {
|
||||
return "Invalid character(s)";
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
@ -92,14 +98,14 @@ SqlTiddlerStore.prototype.validateItemName = function(name) {
|
||||
/*
|
||||
Returns null if the argument is an array of valid bag/recipe names, or a string error message if not
|
||||
*/
|
||||
SqlTiddlerStore.prototype.validateItemNames = function(names) {
|
||||
SqlTiddlerStore.prototype.validateItemNames = function(names,allowPrivilegedCharacters) {
|
||||
if(!$tw.utils.isArray(names)) {
|
||||
return "Not a valid array";
|
||||
}
|
||||
var errors = [];
|
||||
for(const name of names) {
|
||||
const result = this.validateItemName(name);
|
||||
if(result) {
|
||||
const result = this.validateItemName(name,allowPrivilegedCharacters);
|
||||
if(result && errors.indexOf(result) === -1) {
|
||||
errors.push(result);
|
||||
}
|
||||
}
|
||||
@ -184,10 +190,16 @@ SqlTiddlerStore.prototype.listBags = function() {
|
||||
return this.sqlTiddlerDatabase.listBags();
|
||||
};
|
||||
|
||||
SqlTiddlerStore.prototype.createBag = function(bag_name,description) {
|
||||
/*
|
||||
Options include:
|
||||
|
||||
allowPrivilegedCharacters - allows "$", ":" and "/" to appear in recipe name
|
||||
*/
|
||||
SqlTiddlerStore.prototype.createBag = function(bag_name,description,options) {
|
||||
options = options || {};
|
||||
var self = this;
|
||||
return this.sqlTiddlerDatabase.transaction(function() {
|
||||
const validationBagName = self.validateItemName(bag_name);
|
||||
const validationBagName = self.validateItemName(bag_name,options.allowPrivilegedCharacters);
|
||||
if(validationBagName) {
|
||||
return {message: validationBagName};
|
||||
}
|
||||
@ -203,18 +215,19 @@ SqlTiddlerStore.prototype.listRecipes = function() {
|
||||
|
||||
/*
|
||||
Returns null on success, or {message:} on error
|
||||
|
||||
Options include:
|
||||
|
||||
allowPrivilegedCharacters - allows "$", ":" and "/" to appear in recipe name
|
||||
*/
|
||||
SqlTiddlerStore.prototype.createRecipe = function(recipe_name,bag_names,description) {
|
||||
SqlTiddlerStore.prototype.createRecipe = function(recipe_name,bag_names,description,options) {
|
||||
bag_names = bag_names || [];
|
||||
description = description || "";
|
||||
const validationRecipeName = this.validateItemName(recipe_name);
|
||||
options = options || {};
|
||||
const validationRecipeName = this.validateItemName(recipe_name,options.allowPrivilegedCharacters);
|
||||
if(validationRecipeName) {
|
||||
return {message: validationRecipeName};
|
||||
}
|
||||
const validationBagNames = this.validateItemNames(bag_names);
|
||||
if(validationBagNames) {
|
||||
return {message: validationBagNames};
|
||||
}
|
||||
if(bag_names.length === 0) {
|
||||
return {message: "Recipes must contain at least one bag"};
|
||||
}
|
||||
|
@ -1,5 +1,11 @@
|
||||
title: $:/plugins/tiddlywiki/multiwikiserver/templates/get-index
|
||||
|
||||
|
||||
\function .hide.system()
|
||||
[<show-system>match[on]]
|
||||
[all[]!prefix[$:/]]
|
||||
\end
|
||||
|
||||
\procedure bagPill(element-tag:"span",is-topmost:"yes")
|
||||
\whitespace trim
|
||||
<$genesis $type=<<element-tag>> class={{{ mws-bag-pill [<is-topmost>match[yes]then[mws-bag-pill-topmost]] +[join[ ]] }}}>
|
||||
@ -45,9 +51,9 @@ title: $:/plugins/tiddlywiki/multiwikiserver/templates/get-index
|
||||
</div>
|
||||
<div class="mws-wiki-card-meta">
|
||||
<%if true %>
|
||||
<ol class="mws-horizontal-list">
|
||||
<$list filter="[<recipe-info>jsonget[bag_names]]" variable="bag-name" counter="counter">
|
||||
<$transclude $variable="bagPill" is-topmost={{{ [<counter-last>match[yes]] }}} element-tag="li"/>
|
||||
<ol class="mws-vertical-list">
|
||||
<$list filter="[<recipe-info>jsonget[bag_names]reverse[]] :filter[.hide.system[]]" variable="bag-name" counter="counter">
|
||||
<$transclude $variable="bagPill" is-topmost={{{ [<counter-first>match[yes]] }}} element-tag="li"/>
|
||||
</$list>
|
||||
</ol>
|
||||
<%else%>
|
||||
@ -96,7 +102,7 @@ title: $:/plugins/tiddlywiki/multiwikiserver/templates/get-index
|
||||
! Bags
|
||||
|
||||
<ul class="mws-vertical-list">
|
||||
<$list filter="[<bag-list>jsonindexes[]] :sort[<currentTiddler>jsonget[bag_name]]" variable="bag-index" counter="counter">
|
||||
<$list filter="[<bag-list>jsonindexes[]] :filter[<bag-list>jsonget<currentTiddler>,[bag_name].hide.system[]] :sort[<bag-list>jsonget<currentTiddler>,[bag_name]]" variable="bag-index" counter="counter">
|
||||
<li class="mws-wiki-card">
|
||||
<$let
|
||||
bag-info={{{ [<bag-list>jsonextract<bag-index>] }}}
|
||||
@ -131,3 +137,16 @@ title: $:/plugins/tiddlywiki/multiwikiserver/templates/get-index
|
||||
<input type="submit" value="Create or Update Bag" formmethod="post"/>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
! Advanced
|
||||
|
||||
|
||||
<form id="checkboxForm" action="." method="GET">
|
||||
<%if [<show-system>match[on]] %>
|
||||
<input type="checkbox" id="chkShowSystem" name="show_system" value="on" checked="checked"/>
|
||||
<%else%>
|
||||
<input type="checkbox" id="chkShowSystem" name="show_system" value="on"/>
|
||||
<%endif%>
|
||||
<label for="chkShowSystem">Show system bags</label>
|
||||
<button type="submit">Update</button>
|
||||
</form>
|
Loading…
x
Reference in New Issue
Block a user