mirror of
https://github.com/Jermolene/TiddlyWiki5
synced 2026-01-22 19:04:38 +00:00
Compare commits
13 Commits
multi-wiki
...
media-quer
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f879313d9b | ||
|
|
9aed6a5248 | ||
|
|
d97a163e30 | ||
|
|
365b734a22 | ||
|
|
818a9977a3 | ||
|
|
508c74f8e4 | ||
|
|
806960afc7 | ||
|
|
ed2c7c90de | ||
|
|
964ced35ff | ||
|
|
e78d3ae04e | ||
|
|
ea2426eea1 | ||
|
|
7f9b8bf0c1 | ||
|
|
ba17e342b2 |
28
.github/workflows/ci.yml
vendored
28
.github/workflows/ci.yml
vendored
@@ -4,19 +4,18 @@ on:
|
||||
branches:
|
||||
- master
|
||||
- tiddlywiki-com
|
||||
- multi-wiki-support
|
||||
env:
|
||||
NODE_VERSION: "23"
|
||||
NODE_VERSION: "22"
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v1
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "${{ env.NODE_VERSION }}"
|
||||
- run: "./bin/ci-test.sh"
|
||||
- uses: actions/upload-artifact@v3
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: always()
|
||||
with:
|
||||
name: playwright-report
|
||||
@@ -31,8 +30,8 @@ jobs:
|
||||
TW5_BUILD_MAIN_EDITION: "./editions/prerelease"
|
||||
TW5_BUILD_OUTPUT: "./output/prerelease"
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v1
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "${{ env.NODE_VERSION }}"
|
||||
- run: "./bin/ci-pre-build.sh"
|
||||
@@ -63,8 +62,8 @@ jobs:
|
||||
TW5_BUILD_OUTPUT: "./output"
|
||||
TW5_BUILD_ARCHIVE: "./output"
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v1
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "${{ env.NODE_VERSION }}"
|
||||
- run: "./bin/ci-pre-build.sh"
|
||||
@@ -83,14 +82,3 @@ jobs:
|
||||
- run: "./bin/build-tw-org.sh"
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUBPUSHTOKEN }}
|
||||
build-mws-tiddlywiki-com:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.ref == 'refs/heads/multi-wiki-support'
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: "${{ env.NODE_VERSION }}"
|
||||
- run: "./bin/build-mws-site.sh"
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUBPUSHTOKEN }}
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -5,9 +5,7 @@
|
||||
tmp/
|
||||
output/
|
||||
node_modules/
|
||||
store/
|
||||
/test-results/
|
||||
/playwright-report/
|
||||
/playwright/.cache/
|
||||
$__StoryList.tid
|
||||
/editions/test/test-store/*
|
||||
@@ -1,4 +0,0 @@
|
||||
The `multi-wiki-support` branch includes some changes that are not intended to be merged into the `master` branch:
|
||||
|
||||
* Readme update (see `editions/tw5.com/tiddlers/readme/ReadMe.tid`)
|
||||
* Remove `multiwikiserver` plugin from `readme-bld.sh` (see `bin/readme-bld.sh`)
|
||||
@@ -1,97 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Build mws.tiddlywiki.com assets.
|
||||
|
||||
# Default to the version of TiddlyWiki installed in this repo
|
||||
|
||||
if [ -z "$MWSTWCOM_BUILD_TIDDLYWIKI" ]; then
|
||||
MWSTWCOM_BUILD_TIDDLYWIKI=./tiddlywiki.js
|
||||
fi
|
||||
|
||||
echo "Using MWSTWCOM_BUILD_TIDDLYWIKI as [$MWSTWCOM_BUILD_TIDDLYWIKI]"
|
||||
|
||||
# Set up the build details
|
||||
|
||||
if [ -z "$MWSTWCOM_BUILD_DETAILS" ]; then
|
||||
MWSTWCOM_BUILD_DETAILS="$(git symbolic-ref --short HEAD)-$(git rev-parse HEAD) from $(git remote get-url origin)"
|
||||
fi
|
||||
|
||||
echo "Using MWSTWCOM_BUILD_DETAILS as [$MWSTWCOM_BUILD_DETAILS]"
|
||||
|
||||
if [ -z "$MWSTWCOM_BUILD_COMMIT" ]; then
|
||||
MWSTWCOM_BUILD_COMMIT="$(git rev-parse HEAD)"
|
||||
fi
|
||||
|
||||
echo "Using MWSTWCOM_BUILD_COMMIT as [$MWSTWCOM_BUILD_COMMIT]"
|
||||
|
||||
# Set up the build output directory
|
||||
|
||||
if [ -z "$MWSTWCOM_BUILD_OUTPUT" ]; then
|
||||
MWSTWCOM_BUILD_OUTPUT=$(mktemp -d)
|
||||
fi
|
||||
|
||||
mkdir -p $MWSTWCOM_BUILD_OUTPUT
|
||||
|
||||
if [ ! -d "$MWSTWCOM_BUILD_OUTPUT" ]; then
|
||||
echo 'A valid MWSTWCOM_BUILD_OUTPUT environment variable must be set'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Using MWSTWCOM_BUILD_OUTPUT as [$MWSTWCOM_BUILD_OUTPUT]"
|
||||
|
||||
# Pull existing GitHub pages content
|
||||
|
||||
git clone --depth=1 --branch=main "https://github.com/TiddlyWiki/mws.tiddlywiki.com-gh-pages.git" $MWSTWCOM_BUILD_OUTPUT
|
||||
|
||||
# Make the CNAME file that GitHub Pages requires
|
||||
|
||||
echo "mws.tiddlywiki.com" > $MWSTWCOM_BUILD_OUTPUT/CNAME
|
||||
|
||||
# Delete any existing static content
|
||||
|
||||
mkdir -p $MWSTWCOM_BUILD_OUTPUT/static
|
||||
rm $MWSTWCOM_BUILD_OUTPUT/static/*
|
||||
|
||||
# Put the build details into a .tid file so that it can be included in each build (deleted at the end of this script)
|
||||
|
||||
echo -e -n "title: $:/build\ncommit: $MWSTWCOM_BUILD_COMMIT\n\n$MWSTWCOM_BUILD_DETAILS\n" > $MWSTWCOM_BUILD_OUTPUT/build.tid
|
||||
|
||||
######################################################
|
||||
#
|
||||
# mws.tiddlywiki.com distribution
|
||||
#
|
||||
######################################################
|
||||
|
||||
# /index.html Main site
|
||||
# /favicon.ico Favicon for main site
|
||||
# /static.html Static rendering of default tiddlers
|
||||
# /alltiddlers.html Static rendering of all tiddlers
|
||||
# /static/* Static single tiddlers
|
||||
# /static/static.css Static stylesheet
|
||||
# /static/favicon.ico Favicon for static pages
|
||||
node $MWSTWCOM_BUILD_TIDDLYWIKI \
|
||||
editions/multiwikidocs \
|
||||
--verbose \
|
||||
--version \
|
||||
--load $MWSTWCOM_BUILD_OUTPUT/build.tid \
|
||||
--output $MWSTWCOM_BUILD_OUTPUT \
|
||||
--build favicon static index \
|
||||
|| exit 1
|
||||
|
||||
# Delete the temporary build tiddler
|
||||
|
||||
rm $MWSTWCOM_BUILD_OUTPUT/build.tid || exit 1
|
||||
|
||||
# Push output back to GitHub
|
||||
|
||||
# Exit script immediately if any command fails
|
||||
set -e
|
||||
|
||||
pushd $MWSTWCOM_BUILD_OUTPUT
|
||||
git config --global user.email "actions@github.com"
|
||||
git config --global user.name "GitHub Actions"
|
||||
git add -A .
|
||||
git commit --message "GitHub build: $GITHUB_RUN_NUMBER of $TW5_BUILD_BRANCH ($(date +'%F %T %Z'))"
|
||||
git remote add deploy "https://$GH_TOKEN@github.com/TiddlyWiki/mws.tiddlywiki.com-gh-pages.git" &>/dev/null
|
||||
git push deploy main &>/dev/null
|
||||
popd
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
# test TiddlyWiki5 for tiddlywiki.com
|
||||
|
||||
npm install
|
||||
|
||||
node ./tiddlywiki.js \
|
||||
./editions/test \
|
||||
--verbose \
|
||||
|
||||
@@ -10,7 +10,6 @@ fi
|
||||
|
||||
# tw5.com readmes
|
||||
node $TW5_BUILD_TIDDLYWIKI \
|
||||
+plugins/tiddlywiki/multiwikiserver \
|
||||
editions/tw5.com \
|
||||
--verbose \
|
||||
--output . \
|
||||
|
||||
117
core/modules/background-actions.js
Normal file
117
core/modules/background-actions.js
Normal file
@@ -0,0 +1,117 @@
|
||||
/*\
|
||||
title: $:/core/modules/background-actions.js
|
||||
type: application/javascript
|
||||
module-type: global
|
||||
|
||||
Class to dispatch actions when filters change
|
||||
|
||||
\*/
|
||||
(function(){
|
||||
|
||||
/*jslint node: true, browser: true */
|
||||
/*global $tw: false */
|
||||
"use strict";
|
||||
|
||||
function BackgroundActionDispatcher(filterTracker,wiki) {
|
||||
var self = this;
|
||||
this.filterTracker = filterTracker;
|
||||
this.wiki = wiki;
|
||||
this.nextTrackedFilterId = 1;
|
||||
this.trackedFilters = Object.create(null); // Hashmap by id
|
||||
// Track the filter for the background actions
|
||||
this.filterTracker.track({
|
||||
filterString: "[all[tiddlers+shadows]tag[$:/tags/BackgroundAction]!is[draft]]",
|
||||
fnEnter: function fnEnter(title) {
|
||||
return self.trackFilter(title);
|
||||
},
|
||||
fnLeave: function fnLeave(title,enterValue) {
|
||||
self.untrackFilter(enterValue);
|
||||
},
|
||||
fnChange: function fnChange(title,enterValue) {
|
||||
self.untrackFilter(enterValue);
|
||||
return self.trackFilter(title);
|
||||
},
|
||||
fnProcess: function fnProcess(changes) {
|
||||
self.process(changes);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
BackgroundActionDispatcher.prototype.trackFilter = function(title) {
|
||||
var tiddler = this.wiki.getTiddler(title),
|
||||
id = this.nextTrackedFilterId++,
|
||||
tracker = new BackgroundActionTracker({
|
||||
wiki: this.wiki,
|
||||
title: title,
|
||||
trackFilter: tiddler.fields["track-filter"],
|
||||
actions: tiddler.fields.text
|
||||
});
|
||||
this.trackedFilters[id] = tracker;
|
||||
return id;
|
||||
};
|
||||
|
||||
BackgroundActionDispatcher.prototype.untrackFilter = function(enterValue) {
|
||||
var tracker = this.trackedFilters[enterValue];
|
||||
if(tracker) {
|
||||
tracker.destroy();
|
||||
}
|
||||
delete this.trackedFilters[enterValue];
|
||||
};
|
||||
|
||||
BackgroundActionDispatcher.prototype.process = function(changes) {
|
||||
for(var id in this.trackedFilters) {
|
||||
this.trackedFilters[id].process(changes);
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
Represents an individual tracked filter. Options include:
|
||||
wiki: wiki to use
|
||||
title: title of the tiddler being tracked
|
||||
trackFilter: filter string to track changes
|
||||
actions: actions to be executed when the filter changes
|
||||
*/
|
||||
function BackgroundActionTracker(options) {
|
||||
var self = this;
|
||||
this.wiki = options.wiki;
|
||||
this.title = options.title;
|
||||
this.trackFilter = options.trackFilter;
|
||||
this.actions = options.actions
|
||||
this.filterTracker = new $tw.FilterTracker(this.wiki);
|
||||
this.hasChanged = false;
|
||||
this.trackerID = this.filterTracker.track({
|
||||
filterString: this.trackFilter,
|
||||
fnEnter: function(title) {
|
||||
self.hasChanged = true;
|
||||
},
|
||||
fnLeave: function(title,enterValue) {
|
||||
self.hasChanged = true;
|
||||
},
|
||||
fnProcess: function(changes) {
|
||||
if(self.hasChanged) {
|
||||
self.hasChanged = false;
|
||||
self.wiki.invokeActionString(
|
||||
self.actions,
|
||||
null,
|
||||
{
|
||||
title: self.title
|
||||
},{
|
||||
parentWidget: $tw.rootWidget
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
BackgroundActionTracker.prototype.process = function(changes) {
|
||||
this.filterTracker.handleChangeEvent(changes);
|
||||
};
|
||||
|
||||
BackgroundActionTracker.prototype.destroy = function() {
|
||||
this.filterTracker.untrack(this.trackerID);
|
||||
};
|
||||
|
||||
exports.BackgroundActionDispatcher = BackgroundActionDispatcher;
|
||||
|
||||
})();
|
||||
@@ -38,13 +38,6 @@ Commander.prototype.log = function(str) {
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
Clear pending commands
|
||||
*/
|
||||
Commander.prototype.clearCommands = function() {
|
||||
this.commandTokens = this.commandTokens.slice(0,this.nextToken);
|
||||
};
|
||||
|
||||
/*
|
||||
Write a string if verbose flag is set
|
||||
*/
|
||||
|
||||
@@ -16,7 +16,7 @@ var Server = require("$:/core/modules/server/server.js").Server;
|
||||
|
||||
exports.info = {
|
||||
name: "listen",
|
||||
synchronous: false,
|
||||
synchronous: true,
|
||||
namedParameterMode: true,
|
||||
mandatoryParameters: []
|
||||
};
|
||||
@@ -38,11 +38,7 @@ Command.prototype.execute = function() {
|
||||
wiki: this.commander.wiki,
|
||||
variables: self.params
|
||||
});
|
||||
var nodeServer = this.server.listen(null,null,null,{
|
||||
callback: function() {
|
||||
self.callback();
|
||||
}
|
||||
});
|
||||
var nodeServer = this.server.listen();
|
||||
$tw.hooks.invokeHook("th-server-command-post-start",this.server,nodeServer,"tiddlywiki");
|
||||
return null;
|
||||
};
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
/*\
|
||||
title: $:/core/modules/commands/quit.js
|
||||
type: application/javascript
|
||||
module-type: command
|
||||
|
||||
Immediately ends the TiddlyWiki process
|
||||
|
||||
\*/
|
||||
(function(){
|
||||
|
||||
/*jslint node: true, browser: true */
|
||||
/*global $tw: false */
|
||||
"use strict";
|
||||
|
||||
exports.info = {
|
||||
name: "quit",
|
||||
synchronous: true
|
||||
};
|
||||
|
||||
var Command = function(params,commander,callback) {
|
||||
var self = this;
|
||||
this.params = params;
|
||||
this.commander = commander;
|
||||
this.callback = callback;
|
||||
};
|
||||
|
||||
Command.prototype.execute = function() {
|
||||
// Clear any pending commands
|
||||
this.commander.clearCommands();
|
||||
// We don't actually quit, we just issue the "th-quit" hook to give listeners a chance to exit
|
||||
$tw.hooks.invokeHook("th-quit");
|
||||
return null;
|
||||
};
|
||||
|
||||
exports.Command = Command;
|
||||
|
||||
})();
|
||||
108
core/modules/filter-tracker.js
Normal file
108
core/modules/filter-tracker.js
Normal file
@@ -0,0 +1,108 @@
|
||||
/*\
|
||||
title: $:/core/modules/filter-tracker.js
|
||||
type: application/javascript
|
||||
module-type: global
|
||||
|
||||
Class to track the results of a filter string
|
||||
|
||||
\*/
|
||||
(function(){
|
||||
|
||||
/*jslint node: true, browser: true */
|
||||
/*global $tw: false */
|
||||
"use strict";
|
||||
|
||||
function FilterTracker(wiki) {
|
||||
this.wiki = wiki;
|
||||
this.trackers = [];
|
||||
this.nextTrackerId = 1;
|
||||
}
|
||||
|
||||
FilterTracker.prototype.handleChangeEvent = function(changes) {
|
||||
this.processTrackers();
|
||||
this.processChanges(changes);
|
||||
};
|
||||
|
||||
/*
|
||||
Add a tracker to the filter tracker. Returns null if any of the parameters are invalid, or a tracker id if the tracker was added successfully. Options include:
|
||||
filterString: the filter string to track
|
||||
fnEnter: function to call when a title enters the filter results. Called even if the tiddler does not actually exist. Called as (title), and should return a truthy value that is stored in the tracker as the "enterValue"
|
||||
fnLeave: function to call when a title leaves the filter results. Called as (title,enterValue)
|
||||
fnChange: function to call when a tiddler changes in the filter results. Only called for filter results that identify a tiddler or shadow tiddler. Called as (title,enterValue), and may optionally return a replacement enterValue
|
||||
fnProcess: function to call each time the tracker is processed, after any enter, leave or change functions are called. Called as (changes)
|
||||
*/
|
||||
FilterTracker.prototype.track = function(options) {
|
||||
// Add the tracker details
|
||||
var tracker = {
|
||||
id: this.nextTrackerId++,
|
||||
filterString: options.filterString,
|
||||
fnEnter: options.fnEnter,
|
||||
fnLeave: options.fnLeave,
|
||||
fnChange: options.fnChange,
|
||||
fnProcess: options.fnProcess,
|
||||
previousResults: [], // Results from the previous time the tracker was processed
|
||||
resultValues: {} // Map by title to the value returned by fnEnter
|
||||
};
|
||||
this.trackers.push(tracker);
|
||||
// Process the tracker
|
||||
this.processTracker(this.trackers.length - 1);
|
||||
return tracker.id;
|
||||
};
|
||||
|
||||
FilterTracker.prototype.untrack = function(id) {
|
||||
for(var t=0; t<this.trackers.length; t++) {
|
||||
if(this.trackers[t].id === id) {
|
||||
this.trackers.splice(t,1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
FilterTracker.prototype.processTrackers = function() {
|
||||
for(var t=0; t<this.trackers.length; t++) {
|
||||
this.processTracker(t);
|
||||
}
|
||||
};
|
||||
|
||||
FilterTracker.prototype.processTracker = function(index) {
|
||||
var tracker = this.trackers[index];
|
||||
var results = [];
|
||||
// Evaluate the filter and remove duplicate results
|
||||
$tw.utils.each(this.wiki.filterTiddlers(tracker.filterString),function(title) {
|
||||
$tw.utils.pushTop(results,title);
|
||||
});
|
||||
// Process the newly entered results
|
||||
$tw.utils.each(results,function(title) {
|
||||
if(tracker.previousResults.indexOf(title) === -1 && !tracker.resultValues[title] && tracker.fnEnter) {
|
||||
tracker.resultValues[title] = tracker.fnEnter(title) || true;
|
||||
}
|
||||
});
|
||||
// Process the results that have just left
|
||||
$tw.utils.each(tracker.previousResults,function(title) {
|
||||
if(results.indexOf(title) === -1 && tracker.resultValues[title] && tracker.fnLeave) {
|
||||
tracker.fnLeave(title,tracker.resultValues[title]);
|
||||
delete tracker.resultValues[title];
|
||||
}
|
||||
});
|
||||
// Update the previous results
|
||||
tracker.previousResults = results;
|
||||
};
|
||||
|
||||
FilterTracker.prototype.processChanges = function(changes) {
|
||||
for(var t=0; t<this.trackers.length; t++) {
|
||||
var tracker = this.trackers[t];
|
||||
$tw.utils.each(changes,function(change,title) {
|
||||
if(title && tracker.previousResults.indexOf(title) !== -1 && tracker.fnChange) {
|
||||
// Call the change function and if it doesn't return a value then keep the old value
|
||||
tracker.resultValues[title] = tracker.fnChange(title,tracker.resultValues[title]) || tracker.resultValues[title];
|
||||
}
|
||||
});
|
||||
if(tracker.fnProcess) {
|
||||
tracker.fnProcess(changes);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
exports.FilterTracker = FilterTracker;
|
||||
|
||||
})();
|
||||
72
core/modules/info/mediaquerytracker.js
Normal file
72
core/modules/info/mediaquerytracker.js
Normal file
@@ -0,0 +1,72 @@
|
||||
/*\
|
||||
title: $:/core/modules/info/mediaquerytracker.js
|
||||
type: application/javascript
|
||||
module-type: info
|
||||
|
||||
Initialise $:/info/ tiddlers derived from media queries via
|
||||
|
||||
\*/
|
||||
(function(){
|
||||
|
||||
/*jslint node: true, browser: true */
|
||||
/*global $tw: false */
|
||||
"use strict";
|
||||
|
||||
exports.getInfoTiddlerFields = function(updateInfoTiddlersCallback) {
|
||||
if($tw.browser) {
|
||||
// Functions to start and stop tracking a particular media query tracker tiddler
|
||||
function track(title) {
|
||||
var result = {},
|
||||
tiddler = $tw.wiki.getTiddler(title);
|
||||
if(tiddler) {
|
||||
var mediaQuery = tiddler.fields["media-query"],
|
||||
infoTiddler = tiddler.fields["info-tiddler"],
|
||||
infoTiddlerAlt = tiddler.fields["info-tiddler-alt"];
|
||||
if(mediaQuery && infoTiddler) {
|
||||
// Evaluate and track the media query
|
||||
result.mqList = window.matchMedia(mediaQuery);
|
||||
function getResultTiddlers() {
|
||||
var value = result.mqList.matches ? "yes" : "no",
|
||||
tiddlers = [];
|
||||
tiddlers.push({title: infoTiddler, text: value});
|
||||
if(infoTiddlerAlt) {
|
||||
tiddlers.push({title: infoTiddlerAlt, text: value})
|
||||
}
|
||||
return tiddlers;
|
||||
};
|
||||
updateInfoTiddlersCallback(getResultTiddlers());
|
||||
result.handler = function(event) {
|
||||
updateInfoTiddlersCallback(getResultTiddlers());
|
||||
};
|
||||
result.mqList.addEventListener("change",result.handler);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
function untrack(enterValue) {
|
||||
if(enterValue.mqList && enterValue.handler) {
|
||||
enterValue.mqList.removeEventListener("change",enterValue.handler);
|
||||
}
|
||||
}
|
||||
// Track media query tracker tiddlers
|
||||
function fnEnter(title) {
|
||||
return track(title);
|
||||
}
|
||||
function fnLeave(title,enterValue) {
|
||||
untrack(enterValue);
|
||||
}
|
||||
function fnChange(title,enterValue) {
|
||||
untrack(enterValue);
|
||||
return track(title);
|
||||
}
|
||||
$tw.filterTracker.track({
|
||||
filterString: "[all[tiddlers+shadows]tag[$:/tags/MediaQueryTracker]!is[draft]]",
|
||||
fnEnter: fnEnter,
|
||||
fnLeave: fnLeave,
|
||||
fnChange: fnChange
|
||||
});
|
||||
}
|
||||
return [];
|
||||
};
|
||||
|
||||
})();
|
||||
@@ -36,13 +36,6 @@ exports.getInfoTiddlerFields = function(updateInfoTiddlersCallback) {
|
||||
// Screen size
|
||||
infoTiddlerFields.push({title: "$:/info/browser/screen/width", text: window.screen.width.toString()});
|
||||
infoTiddlerFields.push({title: "$:/info/browser/screen/height", text: window.screen.height.toString()});
|
||||
// Dark mode through event listener on MediaQueryList
|
||||
var mqList = window.matchMedia("(prefers-color-scheme: dark)"),
|
||||
getDarkModeTiddler = function() {return {title: "$:/info/darkmode", text: mqList.matches ? "yes" : "no"};};
|
||||
infoTiddlerFields.push(getDarkModeTiddler());
|
||||
mqList.addListener(function(event) {
|
||||
updateInfoTiddlersCallback([getDarkModeTiddler()]);
|
||||
});
|
||||
// Language
|
||||
infoTiddlerFields.push({title: "$:/info/browser/language", text: navigator.language || ""});
|
||||
}
|
||||
|
||||
@@ -364,11 +364,6 @@ Server.prototype.listen = function(port,host,prefix) {
|
||||
}
|
||||
// Display the port number after we've started listening (the port number might have been specified as zero, in which case we will get an assigned port)
|
||||
server.on("listening",function() {
|
||||
// Stop listening when we get the "th-quit" hook
|
||||
$tw.hooks.addHook("th-quit",function() {
|
||||
server.close();
|
||||
});
|
||||
// Log listening details
|
||||
var address = server.address(),
|
||||
url = self.protocol + "://" + (address.family === "IPv6" ? "[" + address.address + "]" : address.address) + ":" + address.port + prefix;
|
||||
$tw.utils.log("Serving on " + url,"brown/orange");
|
||||
|
||||
@@ -16,6 +16,9 @@ Load core modules
|
||||
exports.name = "load-modules";
|
||||
exports.synchronous = true;
|
||||
|
||||
// Set to `true` to enable performance instrumentation
|
||||
var PERFORMANCE_INSTRUMENTATION_CONFIG_TITLE = "$:/config/Performance/Instrumentation";
|
||||
|
||||
exports.startup = function() {
|
||||
// Load modules
|
||||
$tw.modules.applyMethods("utils",$tw.utils);
|
||||
@@ -35,6 +38,19 @@ exports.startup = function() {
|
||||
$tw.macros = $tw.modules.getModulesByTypeAsHashmap("macro");
|
||||
$tw.wiki.initParsers();
|
||||
$tw.Commander.initCommands();
|
||||
// --------------------------
|
||||
// The rest of the startup process here is not strictly to do with loading modules, but are needed before other startup
|
||||
// modules are executed. It is easier to put them here than to introduce a new startup module
|
||||
// --------------------------
|
||||
// Set up the performance framework
|
||||
$tw.perf = new $tw.Performance($tw.wiki.getTiddlerText(PERFORMANCE_INSTRUMENTATION_CONFIG_TITLE,"no") === "yes");
|
||||
// Kick off the filter tracker
|
||||
$tw.filterTracker = new $tw.FilterTracker($tw.wiki);
|
||||
$tw.wiki.addEventListener("change",function(changes) {
|
||||
$tw.filterTracker.handleChangeEvent(changes);
|
||||
});
|
||||
// Kick off the background action dispatcher
|
||||
$tw.backgroundActionDispatcher = new $tw.BackgroundActionDispatcher($tw.filterTracker,$tw.wiki);
|
||||
};
|
||||
|
||||
})();
|
||||
|
||||
@@ -17,9 +17,6 @@ exports.name = "startup";
|
||||
exports.after = ["load-modules"];
|
||||
exports.synchronous = true;
|
||||
|
||||
// Set to `true` to enable performance instrumentation
|
||||
var PERFORMANCE_INSTRUMENTATION_CONFIG_TITLE = "$:/config/Performance/Instrumentation";
|
||||
|
||||
var widget = require("$:/core/modules/widgets/widget.js");
|
||||
|
||||
exports.startup = function() {
|
||||
@@ -57,8 +54,6 @@ exports.startup = function() {
|
||||
}
|
||||
// Initialise version
|
||||
$tw.version = $tw.utils.extractVersionInfo();
|
||||
// Set up the performance framework
|
||||
$tw.perf = new $tw.Performance($tw.wiki.getTiddlerText(PERFORMANCE_INSTRUMENTATION_CONFIG_TITLE,"no") === "yes");
|
||||
// Create a root widget for attaching event handlers. By using it as the parentWidget for another widget tree, one can reuse the event handlers
|
||||
$tw.rootWidget = new widget.widget({
|
||||
type: "widget",
|
||||
|
||||
@@ -257,11 +257,7 @@ Save an incoming tiddler in the store, and updates the associated tiddlerInfo
|
||||
Syncer.prototype.storeTiddler = function(tiddlerFields) {
|
||||
// Save the tiddler
|
||||
var tiddler = new $tw.Tiddler(tiddlerFields);
|
||||
// Only save the tiddler if it has changed
|
||||
var existingTiddler = this.wiki.getTiddler(tiddlerFields.title);
|
||||
if(!existingTiddler || !existingTiddler.isEqual(tiddler)) {
|
||||
this.wiki.addTiddler(tiddler);
|
||||
}
|
||||
this.wiki.addTiddler(tiddler);
|
||||
// Save the tiddler revision and changeCount details
|
||||
this.tiddlerInfo[tiddlerFields.title] = {
|
||||
revision: this.getTiddlerRevision(tiddlerFields.title),
|
||||
@@ -560,7 +556,6 @@ SaveTiddlerTask.prototype.run = function(callback) {
|
||||
// Invoke the callback
|
||||
callback(null);
|
||||
},{
|
||||
syncer: self.syncer,
|
||||
tiddlerInfo: self.syncer.tiddlerInfo[self.title]
|
||||
});
|
||||
} else {
|
||||
@@ -591,7 +586,6 @@ DeleteTiddlerTask.prototype.run = function(callback) {
|
||||
// Invoke the callback
|
||||
callback(null);
|
||||
},{
|
||||
syncer: self.syncer,
|
||||
tiddlerInfo: self.syncer.tiddlerInfo[this.title]
|
||||
});
|
||||
};
|
||||
@@ -620,8 +614,6 @@ LoadTiddlerTask.prototype.run = function(callback) {
|
||||
}
|
||||
// Invoke the callback
|
||||
callback(null);
|
||||
},{
|
||||
syncer: self.syncer
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -163,4 +163,4 @@ ImageWidget.prototype.refresh = function(changedTiddlers) {
|
||||
|
||||
exports.image = ImageWidget;
|
||||
|
||||
})();
|
||||
})();
|
||||
|
||||
9
core/wiki/SampleBackgroundAction Dark Mode.tid
Normal file
9
core/wiki/SampleBackgroundAction Dark Mode.tid
Normal file
@@ -0,0 +1,9 @@
|
||||
title: SampleBackgroundAction: Dark Mode
|
||||
tags: $:/tags/BackgroundAction
|
||||
track-filter: [{$:/info/browser/darkmode}]
|
||||
|
||||
<%if [{$:/info/browser/darkmode}match[no]] %>
|
||||
<$action-setfield $tiddler="$:/palette" text="$:/palettes/Vanilla"/>
|
||||
<%else%>
|
||||
<$action-setfield $tiddler="$:/palette" text="$:/palettes/SolarizedDark"/>
|
||||
<%endif%>
|
||||
15
core/wiki/SampleBackgroundAction Story Change.tid
Normal file
15
core/wiki/SampleBackgroundAction Story Change.tid
Normal file
@@ -0,0 +1,15 @@
|
||||
title: SampleBackgroundAction: Story Change
|
||||
tags: $:/tags/BackgroundAction
|
||||
track-filter: [list[$:/StoryList]]
|
||||
|
||||
<$action-sendmessage $message="tm-notify" $param="SampleBackgroundAction: Story Change" list={{$:/StoryList!!list}}/>
|
||||
|
||||
Story List:
|
||||
|
||||
<ol>
|
||||
<$list filter="[enlist<list>]">
|
||||
<li>
|
||||
<$text text=<<currentTiddler>>/>
|
||||
</li>
|
||||
</$list>
|
||||
</ol>
|
||||
@@ -0,0 +1,5 @@
|
||||
title: $:/core/wiki/config/MediaQueryTrackers/DarkLightPreferred
|
||||
tags: $:/tags/MediaQueryTracker
|
||||
media-query: (prefers-color-scheme: dark)
|
||||
info-tiddler: $:/info/browser/darkmode
|
||||
info-tiddler-alt: $:/info/darkmode
|
||||
@@ -1,41 +0,0 @@
|
||||
created: 20240309135835396
|
||||
modified: 20240309142156125
|
||||
title: Bags and Recipes
|
||||
type: text/vnd.tiddlywiki
|
||||
|
||||
The bags and recipes model is a reference architecture for how tiddlers can be shared between multiple wikis. It was first introduced by TiddlyWeb in 2008.
|
||||
|
||||
The principles of bags and recipes can be simply stated:
|
||||
|
||||
# Tiddlers are stored in named "bags"
|
||||
# Bags have access controls that determines which users can read or write to them
|
||||
# Recipes are named lists of bags, ordered from lowest priority to highest
|
||||
# The tiddlers within a recipe are accumulated in turn from each bag in the recipe in order of increasing priority. Thus, if there are multiple tiddlers with the same title in different bags then the one from the highest priority bag will be used as the recipe tiddler
|
||||
# Wikis are composed by splicing the tiddlers from the corresponding recipe into the standard TW5 HTML template
|
||||
|
||||
A very simple example of the recipe/bag model might be for a single user who maintains the following bags:
|
||||
|
||||
* ''recipes'' - tiddlers related to cooking recipes
|
||||
* ''work'' - tiddlers related to work
|
||||
* ''app'' - common tiddlers for customising TiddlyWiki
|
||||
|
||||
Those bags would be used with the following recipes:
|
||||
|
||||
* ''recipes'' --> recipes, app - wiki for working with recipes, with common custom components
|
||||
* ''work'' --> work, app - wiki for working with work, with common custom components
|
||||
* ''app'' --> app - wiki for maintaining custom components
|
||||
|
||||
All of this will work dynamically, so changes to the app bag will instantly ripple into the affected hosted wikis.
|
||||
|
||||
A more complex example might be for a teacher working with a group of students:
|
||||
|
||||
* ''student-{name}'' bag for each students work
|
||||
* ''teacher-course'' bag for the coursework, editable by the teacher
|
||||
* ''teacher-tools'' bag for custom tools used by the teacher
|
||||
|
||||
Those bags would be exposed through the following hosted wikis:
|
||||
|
||||
* ''student-{name}'' hosted wiki for each students work, including the coursework material
|
||||
* ''teacher-course'' hosted wiki for the coursework, editable by the teacher
|
||||
* ''teacher'' hosted wiki for the teacher, bringing together all the bags, giving them an overview of all the students work
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
title: Database Engines
|
||||
tags: Reference
|
||||
|
||||
MWS uses [[SQLite]] for data storage. It supports choosing between two "database engines" that are based on two different npm modules:
|
||||
|
||||
* [[better-sqlite3|https://www.npmjs.com/package/better-sqlite3]] is written partially in C/C++ and so requires compilation for the target platform
|
||||
* [[node-sqlite3-wasm|https://www.npmjs.com/package/node-sqlite3-wasm]] is written entirely in JavaScript and does not require compilation, but does require a WebAssembly-capable Node.js host. This is not currently possible on some platforms such as iOS/iPadOS
|
||||
|
||||
By default `npm install` will install both database engines. By default it will use `better-sqlite3`. To switch to using `node-sqlite3-wasm`, set the system configuration tiddler `$:/config/MultiWikiServer/Engine` to `wasm` (the default value is `better`). Note that this tiddler resides in the [[Administration Wiki]].
|
||||
|
||||
!! Avoiding Installation Errors
|
||||
|
||||
If you encounter errors during `npm install` related to `gyp` or `prebuild`, you may be able to avoid them by using `node-sqlite3-wasm` instead of `better-sqlite3`. However, it will be necessary to manually install `node-sqlite3-wasm` and its dependencies. This can be done by running the following commands in your terminal:
|
||||
|
||||
<<.copy-code-to-clipboard "npm install node-sqlite3-wasm">>
|
||||
@@ -1,6 +0,0 @@
|
||||
title: $:/DefaultTiddlers
|
||||
|
||||
HelloThere
|
||||
Installation
|
||||
Usage
|
||||
Reference
|
||||
@@ -1,19 +0,0 @@
|
||||
title: HelloThere
|
||||
tags: TableOfContents
|
||||
|
||||
!! ~TiddlyWiki is Growing Up
|
||||
|
||||
<span class="tc-float-right">[img width=200 [MWS Banner.png]]</span>
|
||||
~MultiWikiServer is a new development that drastically improves ~TiddlyWiki's capabilities when running as a server under Node.js. It brings ~TiddlyWiki up to par with common web-based tools like ~WordPress or ~MediaWiki by supporting multiple wikis and multiple users at the same time.
|
||||
|
||||
Features under development include:
|
||||
|
||||
* Hosting multiple wikis at once, using the [[Bags and Recipes]] mechanism for sharing data between them
|
||||
* Based on [[SQLite|MWS and SQLite]] for performance and reliability
|
||||
* Robust built-in synchronisation handlers for syncing data to the filesystem
|
||||
* Flexible authentication and authorisation options
|
||||
* Improved handling of file uploads and attachments, allowing gigabyte video files to be uploaded and streamed
|
||||
* Instantaneous synchronisation of changes between the server and all connected clients
|
||||
* Workflow processing on the server, for example to automatically compress images, or to archive webpages
|
||||
|
||||
MWS is currently [[under development at GitHub|https://github.com/TiddlyWiki/TiddlyWiki5/pull/7915]] but it is already functional and usable.
|
||||
@@ -1,19 +0,0 @@
|
||||
title: Installation using Git
|
||||
tags: Installation
|
||||
modified: 20241105133737778
|
||||
created: 20241105133737778
|
||||
|
||||
These instructions require basic knowledge both of the terminal and of Git. There are also [[alternative instructions without using Git|Installation]].
|
||||
|
||||
# Clone the code from GitHub with: <<.copy-code-to-clipboard "git clone -b multi-wiki-support --single-branch https://github.com/TiddlyWiki/TiddlyWiki5">>
|
||||
# Open a terminal window and set the current directory to the root of the downloaded folder
|
||||
# Install the dependencies with the command: <<.copy-code-to-clipboard "npm install">>
|
||||
# Start the server with the command: <<.copy-code-to-clipboard "npm start">>
|
||||
# To use MWS, visit http://localhost:8080 in a browser on the same computer
|
||||
# When you have finished using MWS, stop the server with <kbd>ctrl-C</kbd>
|
||||
|
||||
See [[Troubleshooting]] if you encounter any errors.
|
||||
|
||||
To update your copy of MWS with newer changes from ~GitHub, run the following command:
|
||||
|
||||
<<.copy-code-to-clipboard "git pull">>
|
||||
@@ -1,18 +0,0 @@
|
||||
title: Installation
|
||||
tags: TableOfContents
|
||||
modified: 20241105133737778
|
||||
created: 20241105133737778
|
||||
|
||||
These instructions require minimal knowledge of the terminal. There are also [[alternative instructions for those using Git|Installation using Git]].
|
||||
|
||||
# Download the code [[direct from GitHub|https://github.com/TiddlyWiki/TiddlyWiki5/archive/refs/pull/7915/head.zip]]
|
||||
# Open a terminal window and set the current directory to the root of the downloaded folder
|
||||
# Install the dependencies with the command: <<.copy-code-to-clipboard "npm install">>
|
||||
# To verify that MWS is working correctly, start the server with the command: <<.copy-code-to-clipboard "npm start">> and then visit http://localhost:8080 in a browser on the same computer
|
||||
# When you have finished using MWS, stop the server with <kbd>ctrl-C</kbd>
|
||||
|
||||
See [[Troubleshooting]] if you encounter any errors.
|
||||
|
||||
|
||||
|
||||
To update your copy of MWS in the future with newer changes will require re-downloading the code, taking care not to lose any changes you might have made.
|
||||
@@ -1,2 +0,0 @@
|
||||
title: Architecture
|
||||
tags: TableOfContents
|
||||
@@ -1,25 +0,0 @@
|
||||
title: MWS and SQLite
|
||||
tags: Architecture
|
||||
|
||||
! Introduction
|
||||
|
||||
SQLite is a very popular open source embedded SQL database with some [[unusual characteristics|https://www.sqlite.org/different.html]]. It has proved itself to be robust, fast and scalable, and has been widely adopted in a range of applications including web browsers, mobile devices, and embedded systems.
|
||||
|
||||
The "embedded" part means that developers access SQLite as a library of C functions that run as part of a larger application. This contrasts with more familiar database applications like Microsoft's SQL Server or Oracle that are accessed as network services.
|
||||
|
||||
MWS uses SQLite for the tiddler store and associated data. It brings many advantages:
|
||||
|
||||
* ''Performance'': the optimising query engine inside SQLite makes it much faster and more efficient than could be achieved in plain JavaScript. In some cases, it is [[faster than writing directly to the file system||https://www.sqlite.org/fasterthanfs.html]]
|
||||
* ''Reliability'': SQLite uses protocols that [[ensure data integrity and consistency|https://www.sqlite.org/hirely.html]], even when the application crashes
|
||||
* ''Scalability'': SQLite can handle extremely [[large datasets and complex queries|https://www.sqlite.org/limits.html]]
|
||||
* ''Portability'': SQLite databases are stored as [[a single file|https://www.sqlite.org/fileformat.html]] that can be easily copied and moved between systems
|
||||
|
||||
! Misconceptions
|
||||
|
||||
TiddlyWiki 5 has always incorporated a database. Until MWS, that database has always been a custom tiddler database written in JavaScript. Over the years it has been enhanced and optimised with indexes and other database features that have given us reasonably decent performance for a range of common operations.
|
||||
|
||||
In terms of the traditional architecture of TiddlyWiki, MWS uses SQLite as the basis for an internal API that is equivalent to that of the `$tw.Wiki` object: basic CRUD operations on a database of tiddlers stored by their titles.
|
||||
|
||||
In the context of MWS, SQLite is just a fast and efficient equivalent of TiddlyWiki's existing JavaScript database engine. It gives us the option of persisting the database in file storage, but we also retain the option to run the database entirely within memory and rely on a file synchronisation process to save changes as individual `.tid` files in the file system, just as we do today.
|
||||
|
||||
One particular misconception to avoid is the idea that SQLite replaces the folders of `.tid` files that characterise the Node.js configuration of TiddlyWiki. These are separate components with a different purpose. The tiddler files are the result of syncing a database to the filesystem, and that database can be conceptually interchanged between our custom JavaScript database or the new SQLite implementation in MWS.
|
||||
@@ -1,230 +0,0 @@
|
||||
title: Reference
|
||||
tags: TableOfContents
|
||||
|
||||
! Authentication & Authorization
|
||||
|
||||
!! Overview
|
||||
|
||||
Our application has transitioned from a conventional username/password authentication system to a more robust Authentication and Authorization implementation. This new system supports multiple user accounts, roles, permissions, and a comprehensive access control list.
|
||||
|
||||
!! Key Features
|
||||
|
||||
# Multiple User Accounts
|
||||
# Role-based Access Control
|
||||
# Granular Permissions
|
||||
# Access Control List (ACL)
|
||||
|
||||
!! Application Access & Security
|
||||
|
||||
!!! Initial Setup
|
||||
When you first launch the Multiwiki Server, it operates in an unauthenticated mode to facilitate initial configuration. During this initial state, the system creates a temporary anonymous administrator account. Upon accessing the landing page, you'll receive a prominent security warning with instructions to establish a permanent ADMIN account. It's crucial to create this account immediately to secure your installation.
|
||||
|
||||
!!! User Types and Permissions
|
||||
|
||||
!!!! Administrator (ADMIN)
|
||||
|
||||
* Full system access and configuration rights
|
||||
* Can create, modify, and delete user accounts
|
||||
* Manages role assignments and permissions
|
||||
* Controls global system settings
|
||||
* Can configure guest access policies
|
||||
* Has complete control over all recipes and tiddlers
|
||||
|
||||
!!!! Regular Users
|
||||
* Custom accounts created by administrators
|
||||
* Permissions determined by assigned roles
|
||||
* Access limited to specific recipes based on role permissions
|
||||
* Can have READ and/or WRITE permissions
|
||||
|
||||
!!!! Guest Users
|
||||
* Default anonymous access level
|
||||
* No inherent permissions
|
||||
* Can only access recipes without Access Control Lists (ACLs)
|
||||
* Read/write capabilities configurable by ADMIN
|
||||
* Useful for public wikis or documentation sites
|
||||
|
||||
!!! Access Control System
|
||||
|
||||
!!!! Recipe-Level Security
|
||||
* Access control is implemented at the recipe level
|
||||
* Each recipe can have its own Access Control List (ACL)
|
||||
* Permissions are granular:
|
||||
** READ: Allows viewing recipe contents
|
||||
** WRITE: Allows modifications to recipe contents
|
||||
|
||||
!!!! Role-Based Access Control (RBAC)
|
||||
* Administrators can create custom roles
|
||||
* Roles can be assigned specific READ/WRITE permissions
|
||||
* Users inherit permissions from their assigned roles
|
||||
* Multiple roles can be assigned to a single user
|
||||
* Provides flexible and scalable access management
|
||||
|
||||
!!!! Permission Inheritance
|
||||
* Users receive combined permissions from all assigned roles
|
||||
* When roles grant different permission levels for the same resource, the higher access level is granted. For example, if one role grants "read" and another grants "write" access to a recipe, the user receives "write" access since it includes all lower-level permissions.
|
||||
* Guest access is overridden by recipe ACLs
|
||||
* When different permission rules conflict, the system follows a "most restrictive wins" principle: if any applicable rule denies access or requires a higher security level, that restriction takes precedence over more permissive rules. This ensures security is maintained even when users have multiple overlapping role assignments or inherited permissions.
|
||||
|
||||
This security model allows for fine-grained control over content access while maintaining flexibility for both private and public wiki deployments.
|
||||
|
||||
!! User Management & Security Architecture
|
||||
|
||||
!!! User Account Management
|
||||
|
||||
Users can be administered through two interfaces:
|
||||
|
||||
# Web-based Administrative Interface
|
||||
#* Accessible only to ADMIN users
|
||||
#* Provides graphical interface for user operations
|
||||
#* Real-time validation and feedback
|
||||
# Command-line Interface (CLI) Tools
|
||||
#* Suitable for automation and scripting
|
||||
#* Enables batch operations
|
||||
#* Useful for system initialization
|
||||
|
||||
Each user account contains the following essential components:
|
||||
|
||||
* ''Username''
|
||||
** Must be unique across the system
|
||||
** Case-sensitive
|
||||
** Used for authentication and audit trails
|
||||
* ''Password''
|
||||
** Stored using secure hashing algorithms
|
||||
** Never stored in plaintext
|
||||
** Subject to complexity requirements
|
||||
* ''Role Assignments''
|
||||
** Multiple roles can be assigned
|
||||
** Inherited permissions from all assigned roles
|
||||
** Dynamic permission calculation based on role combination
|
||||
|
||||
!!! Role & Permission Framework
|
||||
|
||||
!!!! Role Management
|
||||
|
||||
Roles serve as permission containers and provide organized access control. The system includes:
|
||||
|
||||
Built-in Roles:
|
||||
|
||||
* `ADMIN`
|
||||
** Highest privilege level
|
||||
** Full system access
|
||||
** Cannot be modified or deleted
|
||||
** Can create and manage other roles
|
||||
** Controls guest access policies
|
||||
|
||||
* `USER`
|
||||
** Basic access rights
|
||||
** Typically limited to specific recipes
|
||||
|
||||
**Custom Roles (Examples):**
|
||||
|
||||
* `MANAGER`
|
||||
** Intermediate access level
|
||||
** Can manage subset of resources
|
||||
** Custom roles as needed for specific use cases
|
||||
|
||||
!!!! Permission Architecture
|
||||
|
||||
Core Permissions:
|
||||
|
||||
* `READ` Permission
|
||||
** View recipe contents
|
||||
** Access tiddler data
|
||||
** View metadata
|
||||
** Export capabilities
|
||||
|
||||
* `WRITE` Permission
|
||||
** Create new tiddlers
|
||||
** Modify existing content
|
||||
** Delete resources
|
||||
** Manage recipe contents
|
||||
|
||||
**Guest Access:**
|
||||
|
||||
* No default permissions
|
||||
* Access limited to non-ACL recipes
|
||||
* Configurable by ADMIN users
|
||||
* Suitable for public wikis
|
||||
|
||||
!!! Access Control List (ACL) Implementation
|
||||
|
||||
The ACL system provides granular security control through:
|
||||
|
||||
!!!! Entity-Level Control
|
||||
|
||||
* Recipe-based access control
|
||||
* Individual resource protection
|
||||
* Hierarchical permission inheritance
|
||||
|
||||
!!! Authentication Process Flow
|
||||
|
||||
* Initial Authentication
|
||||
** User submits credentials
|
||||
** System validates username existence
|
||||
** Password hash comparison
|
||||
** Session token generation
|
||||
|
||||
* Session Management
|
||||
** Secure session storage
|
||||
** Token-based authentication
|
||||
** Automatic session expiration
|
||||
** Re-authentication requirements
|
||||
|
||||
!!! Authorization Workflow
|
||||
|
||||
* Request Processing
|
||||
** Capture user action request
|
||||
** Identify target resource
|
||||
** Extract required permissions
|
||||
|
||||
* Permission Validation
|
||||
** Check user roles
|
||||
** Aggregate permissions
|
||||
** Verify ACL entries
|
||||
** Apply guest policies if applicable
|
||||
|
||||
* Access Decision
|
||||
** Compare required vs. available permissions
|
||||
** Apply most restrictive policy
|
||||
** Return access decision
|
||||
|
||||
!!! System Extension Guidelines
|
||||
|
||||
!!!! Adding New Roles
|
||||
|
||||
# Access administrative interface
|
||||
# Define role identifier
|
||||
# Assign base permissions
|
||||
# Configure ACL mappings
|
||||
# Test role functionality
|
||||
|
||||
!!!! Permission Expansion
|
||||
|
||||
# Define new permission type
|
||||
# Update ACL structure
|
||||
# Implement permission checks
|
||||
# Update validation logic
|
||||
# Document new permission
|
||||
|
||||
!!!! Security Considerations
|
||||
|
||||
* Regular permission audits
|
||||
* Role assignment reviews
|
||||
* Guest access monitoring
|
||||
* Security log analysis
|
||||
* Access pattern monitoring
|
||||
|
||||
This comprehensive security model provides flexible, maintainable, and secure access control while supporting both authenticated and guest users within the Multiwiki Server environment.
|
||||
|
||||
! HTTP API
|
||||
|
||||
The ~MultiWikiServer HTTP API provides access to resources hosted by the MWS store. It is based on [[the API of TiddlyWeb|https://tank.peermore.com/tanks/tiddlyweb/HTTP%20API]], first developed in 2008 by Chris Dent.
|
||||
|
||||
The design goals of the API are:
|
||||
|
||||
* To follow the principles of REST where practical
|
||||
* To present resources as nouns, not verbs
|
||||
|
||||
General points about the design:
|
||||
|
||||
* In MWS there are no resources that end with / (except for the root path which is /)
|
||||
@@ -1,19 +0,0 @@
|
||||
title: Troublesheeting gyp/prebuild Installation Errors
|
||||
tags: Troubleshooting
|
||||
modified: 20241105133737778
|
||||
created: 20241105133737778
|
||||
|
||||
Installation may fail with errors related to `gyp` or `prebuild`. These errors are caused by missing dependencies or incorrect versions of dependencies.
|
||||
|
||||
Note that in most cases, these errors occur because of the use of the npm module [[better-sqlite3|https://www.npmjs.com/package/better-sqlite3]]. This module is mostly written in C, and thus requires compilation for the target platform. MWS supports switchable database engines, and also supports the use of the [[node-sqlite3-wasm|https://www.npmjs.com/package/node-sqlite3-wasm]] module which is written in ~JavaScript and does not require compilation and so may avoid these errors. See [[Database Engines]] for more details of how to switch between engines.
|
||||
|
||||
The following steps may help resolve errors involving `gyp` or `prebuild`:
|
||||
|
||||
# Ensure that you have the latest version of Node.js installed. You can download the latest version from the [[Node.js website|https://nodejs.org/]].
|
||||
# Update npm to the latest version by running the following command in your terminal: <<.copy-code-to-clipboard "npm install -g npm@latest">>
|
||||
# Clear the npm cache by running the following command in your terminal: <<.copy-code-to-clipboard "npm cache clean --force">>
|
||||
# Delete the `node_modules` folder in your TiddlyWiki directory by running the following command in your terminal: <<.copy-code-to-clipboard "rm -rf node_modules">>
|
||||
# Reinstall the dependencies by running the following command in your terminal: <<.copy-code-to-clipboard "npm install">>
|
||||
# If you continue to encounter errors, try running the following command in your terminal: <<.copy-code-to-clipboard "npm rebuild">>
|
||||
# If you are still experiencing issues, you may need to manually install the `gyp` and `prebuild` dependencies. You can do this by running the following commands in your terminal: <<.copy-code-to-clipboard "npm install -g node-gyp">> <<.copy-code-to-clipboard "npm install -g prebuild">>
|
||||
# Once you have installed the dependencies, try reinstalling the TiddlyWiki dependencies by running the following command in your terminal: <<.copy-code-to-clipboard "npm install">>
|
||||
@@ -1,4 +0,0 @@
|
||||
title: Troubleshooting
|
||||
tags: TableOfContents
|
||||
|
||||
<<list-links "[tag[Troubleshooting]]">>
|
||||
@@ -1,13 +0,0 @@
|
||||
title: Usage
|
||||
tags: TableOfContents
|
||||
modified: 20241105133737778
|
||||
created: 20241105133737778
|
||||
|
||||
Once MWS is successfully [[installed|Installation]], you can access it by visiting http://localhost:8080 in a browser on the same computer.
|
||||
|
||||
By default, MWS is installed with full anonymous access enabled, meaning that anyone with access to the server has full access to read and modify anything. However, also by default, the server is only accessible to browsers on the same machine.
|
||||
|
||||
If you intend to put an MWS installation on a public network like the Internet, the server will need to be secured with the following steps:
|
||||
|
||||
* Create and login with a new administrator account
|
||||
* Disable anonymouse access
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 173 KiB |
@@ -1,4 +0,0 @@
|
||||
title: MWS Banner.png
|
||||
type: image/png
|
||||
tags: picture
|
||||
alt-text: Banner for the new Multi Wiki Server plugin for TiddlyWiki
|
||||
@@ -1,16 +0,0 @@
|
||||
list-after: $:/core/ui/EditTemplate/title
|
||||
tags: $:/tags/EditTemplate
|
||||
title: $:/ContributionBanner
|
||||
|
||||
\define base-github()
|
||||
https://github.com/TiddlyWiki/TiddlyWiki5/edit/multi-wiki-support/editions/multiwikidocs/tiddlers/
|
||||
\end
|
||||
|
||||
<$set name="draft-of" value={{{ [<currentTiddler>get[draft.of]] }}}>
|
||||
<$list filter="[[$:/config/OriginalTiddlerPaths]getindex<draft-of>]" variable="target" >
|
||||
<div class="tc-improvement-banner">
|
||||
{{$:/core/images/star-filled}} Can you help us improve this documentation? [[Find out how|Improving TiddlyWiki Documentation]] to
|
||||
<a href={{{ [<target>addprefix<base-github>] }}} class="tc-tiddlylink-external" target="_blank" rel="noopener noreferrer">edit it directly on ~GitHub</a>
|
||||
</div>
|
||||
</$list>
|
||||
</$set>
|
||||
@@ -1,3 +0,0 @@
|
||||
title: $:/SiteDomain
|
||||
|
||||
mws.tiddlywiki.com
|
||||
@@ -1,3 +0,0 @@
|
||||
title: $:/SitePreviewUrl
|
||||
|
||||
https://tiddlywiki.com/images/Introduction%2520Video%2520Thumbnail.jpg
|
||||
@@ -1,3 +0,0 @@
|
||||
title: $:/SiteSubtitle
|
||||
|
||||
~TiddlyWiki for the People
|
||||
@@ -1,4 +0,0 @@
|
||||
title: $:/SiteTitle
|
||||
type: text/vnd.tiddlywiki
|
||||
|
||||
~TiddlyWiki ~MultiWikiServer
|
||||
@@ -1,3 +0,0 @@
|
||||
title: $:/SiteUrl
|
||||
|
||||
https://mws.tiddlywiki.com/
|
||||
@@ -1,129 +0,0 @@
|
||||
tags: $:/tags/RawMarkupWikified/TopBody
|
||||
title: $:/SplashScreen
|
||||
type: text/vnd.tiddlywiki
|
||||
|
||||
\import [subfilter{$:/core/config/GlobalImportFilter}]
|
||||
|
||||
\procedure show-icon(title)
|
||||
<$wikify name="icon" text={{{ [<title>addprefix[{{]addsuffix[}}]] }}} output="html">
|
||||
<$text text=<<icon>>/>
|
||||
</$wikify>
|
||||
\end
|
||||
|
||||
\rules only filteredtranscludeinline transcludeinline macrocallinline
|
||||
<div class="tc-remove-when-wiki-loaded">
|
||||
<style scoped>
|
||||
|
||||
.tc-splash-text {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
|
||||
font-size: 16px;
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
margin: 20px auto 0;
|
||||
width: 200px;
|
||||
text-align: center;
|
||||
color: <<colour foreground>>;
|
||||
fill: <<colour foreground>>;
|
||||
}
|
||||
|
||||
.tc-splash-text img {
|
||||
width: 150px;
|
||||
<<box-shadow "2px 2px 10px rgba(0, 0, 0, 0.5)">>
|
||||
}
|
||||
|
||||
html body.tc-body {
|
||||
background: <<colour page-background>>;
|
||||
}
|
||||
|
||||
/*
|
||||
Spinner from https://github.com/tobiasahlin/SpinKit/ by Tobias Ahlin
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 Tobias Ahlin
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
.tc-splash-spinner {
|
||||
margin: 20px auto 0;
|
||||
width: 70px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.tc-splash-spinner > div {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
border-radius: 100%;
|
||||
display: inline-block;
|
||||
-webkit-animation: sk-bouncedelay 1.4s infinite ease-in-out both;
|
||||
animation: sk-bouncedelay 1.4s infinite ease-in-out both;
|
||||
background-color: #f88;
|
||||
}
|
||||
|
||||
.tc-splash-spinner .tc-splash-bounce-1 {
|
||||
-webkit-animation-delay: -0.32s;
|
||||
animation-delay: -0.32s;
|
||||
background-color: #8f8;
|
||||
}
|
||||
|
||||
.tc-splash-spinner .tc-splash-bounce-2 {
|
||||
-webkit-animation-delay: -0.16s;
|
||||
animation-delay: -0.16s;
|
||||
background-color: #88f;
|
||||
}
|
||||
|
||||
@-webkit-keyframes sk-bouncedelay {
|
||||
0%, 80%, 100% { -webkit-transform: scale(0) }
|
||||
40% { -webkit-transform: scale(1.0) }
|
||||
}
|
||||
|
||||
@keyframes sk-bouncedelay {
|
||||
0%, 80%, 100% {
|
||||
-webkit-transform: scale(0);
|
||||
transform: scale(0);
|
||||
} 40% {
|
||||
-webkit-transform: scale(1.0);
|
||||
transform: scale(1.0);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="tc-splash-spinner">
|
||||
<div class="tc-splash-bounce-1"></div>
|
||||
<div class="tc-splash-bounce-2"></div>
|
||||
<div class="tc-splash-bounce-3"></div>
|
||||
</div>
|
||||
|
||||
<div class="tc-splash-text">
|
||||
<strong>{{$:/SiteTitle}}</strong>
|
||||
<br/>
|
||||
is loading
|
||||
</div>
|
||||
|
||||
<!-- Demonstrating how to embed a bitmap graphic -->
|
||||
<div class="tc-splash-text">
|
||||
<img src="data:image/jpeg;base64,{{MWS Banner.png||$:/core/templates/plain-text-tiddler}}" width="100"/>
|
||||
</div>
|
||||
|
||||
<!-- Demonstrating how to embed a wikitext SVG graphic -->
|
||||
<div class="tc-splash-text">
|
||||
<<show-icon "$:/core/icon">>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@@ -1,3 +0,0 @@
|
||||
title: $:/StaticBanner
|
||||
|
||||
<div class="tc-static-alert"><div class="tc-static-alert-inner">This page is part of a static HTML representation of the ~TiddlyWiki at https://tiddlywiki.com/</div></div>
|
||||
@@ -1,49 +0,0 @@
|
||||
tags: $:/tags/Stylesheet
|
||||
title: $:/_styles
|
||||
type: text/vnd.tiddlywiki
|
||||
|
||||
\rules only filteredtranscludeinline transcludeinline macrodef macrocallinline macrocallblock
|
||||
|
||||
.tc-improvement-banner {
|
||||
font-size: 0.7em;
|
||||
background: #fcc;
|
||||
padding-left: 5px;
|
||||
margin-top: 6px;
|
||||
margin-bottom: 12px;
|
||||
<<box-shadow "2px 2px 2px rgba(0,0,0,0.4)">>
|
||||
}
|
||||
|
||||
@media (max-width: {{$:/themes/tiddlywiki/vanilla/metrics/sidebarbreakpoint}}) {
|
||||
|
||||
.tc-improvement-banner {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@media (min-width: {{$:/themes/tiddlywiki/vanilla/metrics/sidebarbreakpoint}}) {
|
||||
|
||||
.tc-improvement-banner {
|
||||
margin-right: -53px;
|
||||
margin-left: -53px;
|
||||
}
|
||||
|
||||
.tc-improvement-banner:before {
|
||||
display: block;
|
||||
position: absolute;
|
||||
width: 0;
|
||||
height: 0;
|
||||
content: " ";
|
||||
margin-left: -5px;
|
||||
margin-top: -10px;
|
||||
border-top: 5px solid transparent;
|
||||
border-left: 5px solid transparent;
|
||||
border-right: 5px solid #C07E7E;
|
||||
border-bottom: 5px solid #C07E7E;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.tc-improvement-banner svg {
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
tags: $:/tags/SideBar
|
||||
title: TableOfContents
|
||||
list-before:
|
||||
|
||||
<div class="tc-table-of-contents">
|
||||
|
||||
<<toc-selective-expandable 'TableOfContents'>>
|
||||
|
||||
</div>
|
||||
@@ -1,3 +0,0 @@
|
||||
title: $:/config/DefaultSidebarTab
|
||||
|
||||
TableOfContents
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 12 KiB |
@@ -1,2 +0,0 @@
|
||||
title: $:/favicon.ico
|
||||
type: image/png
|
||||
@@ -1,11 +0,0 @@
|
||||
code-body: yes
|
||||
tags: $:/tags/Macro
|
||||
title: $:/editions/multiwikidocs/doc-macros
|
||||
type: text/vnd.tiddlywiki
|
||||
|
||||
\procedure .copy-code-to-clipboard(text)
|
||||
<div>
|
||||
<$transclude $variable="copy-to-clipboard-above-right" src=<<text>>/>
|
||||
<$codeblock code=<<text>>/>
|
||||
</div>
|
||||
\end .copy-code-to-clipboard
|
||||
@@ -1,149 +0,0 @@
|
||||
title: $:/palettes/MWS
|
||||
name: MWS Palette
|
||||
description: Palette for mws.tiddlywiki.com
|
||||
tags: $:/tags/Palette
|
||||
type: application/x-tiddler-dictionary
|
||||
|
||||
alert-background: #ffe476
|
||||
alert-border: #b99e2f
|
||||
alert-highlight: #881122
|
||||
alert-muted-foreground: #b99e2f
|
||||
background: #ffffff
|
||||
blockquote-bar: <<colour muted-foreground>>
|
||||
button-background:
|
||||
button-foreground:
|
||||
button-border:
|
||||
code-background: #f7f7f9
|
||||
code-border: #e1e1e8
|
||||
code-foreground: #dd1144
|
||||
diff-delete-background: #ffc9c9
|
||||
diff-delete-foreground: <<colour foreground>>
|
||||
diff-equal-background:
|
||||
diff-equal-foreground: <<colour foreground>>
|
||||
diff-insert-background: #aaefad
|
||||
diff-insert-foreground: <<colour foreground>>
|
||||
diff-invisible-background:
|
||||
diff-invisible-foreground: <<colour muted-foreground>>
|
||||
dirty-indicator: #ff0000
|
||||
download-background: #34c734
|
||||
download-foreground: <<colour background>>
|
||||
dragger-background: <<colour foreground>>
|
||||
dragger-foreground: <<colour background>>
|
||||
dropdown-background: <<colour background>>
|
||||
dropdown-border: <<colour muted-foreground>>
|
||||
dropdown-tab-background-selected: #ffffff
|
||||
dropdown-tab-background: #ececec
|
||||
dropzone-background: rgba(0,200,0,0.7)
|
||||
external-link-background-hover: inherit
|
||||
external-link-background-visited: inherit
|
||||
external-link-background: inherit
|
||||
external-link-foreground-hover: inherit
|
||||
external-link-foreground-visited: #0000aa
|
||||
external-link-foreground: #0000ee
|
||||
footnote-target-background: #ecf2ff
|
||||
foreground: #333333
|
||||
highlight-background: #ffff00
|
||||
highlight-foreground: #000000
|
||||
message-background: #ecf2ff
|
||||
message-border: #cfd6e6
|
||||
message-foreground: #547599
|
||||
modal-backdrop: <<colour foreground>>
|
||||
modal-background: <<colour background>>
|
||||
modal-border: #999999
|
||||
modal-footer-background: #f5f5f5
|
||||
modal-footer-border: #dddddd
|
||||
modal-header-border: #eeeeee
|
||||
muted-foreground: #bbbbbb
|
||||
network-activity-foreground: #448844
|
||||
notification-background: #ffffdd
|
||||
notification-border: #999999
|
||||
page-background: #eddee7
|
||||
pre-background: #f5f5f5
|
||||
pre-border: #cccccc
|
||||
primary: #5778d8
|
||||
selection-background:
|
||||
selection-foreground:
|
||||
select-tag-background:
|
||||
select-tag-foreground:
|
||||
sidebar-button-foreground: <<colour foreground>>
|
||||
sidebar-controls-foreground-hover: #000000
|
||||
sidebar-controls-foreground: #aaaaaa
|
||||
sidebar-foreground-shadow: rgba(255,255,255, 0.8)
|
||||
sidebar-foreground: #acacac
|
||||
sidebar-muted-foreground-hover: #444444
|
||||
sidebar-muted-foreground: #c0c0c0
|
||||
sidebar-tab-background-selected: #eddee7
|
||||
sidebar-tab-background: #c7b7bf
|
||||
sidebar-tab-border-selected: <<colour tab-border-selected>>
|
||||
sidebar-tab-border: <<colour tab-border>>
|
||||
sidebar-tab-divider: #e4e4e4
|
||||
sidebar-tab-foreground-selected:
|
||||
sidebar-tab-foreground: <<colour tab-foreground>>
|
||||
sidebar-tiddler-link-foreground-hover: #444444
|
||||
sidebar-tiddler-link-foreground: #999999
|
||||
site-title-foreground: <<colour tiddler-title-foreground>>
|
||||
stability-stable: #008000
|
||||
stability-experimental: #c07c00
|
||||
stability-deprecated: #ff0000
|
||||
stability-legacy: #0000ff
|
||||
static-alert-foreground: #aaaaaa
|
||||
tab-background-selected: #ffffff
|
||||
tab-background: #d8d8d8
|
||||
tab-border-selected: #d8d8d8
|
||||
tab-border: #cccccc
|
||||
tab-divider: #d8d8d8
|
||||
tab-foreground-selected: <<colour tab-foreground>>
|
||||
tab-foreground: #666666
|
||||
table-border: #dddddd
|
||||
table-footer-background: #a8a8a8
|
||||
table-header-background: #f0f0f0
|
||||
tag-background: #eecc66
|
||||
tag-foreground: #ffffff
|
||||
testcase-accent-level-1: #c1eaff
|
||||
testcase-accent-level-2: #E3B740
|
||||
testcase-accent-level-3: #5FD564
|
||||
tiddler-background: <<colour background>>
|
||||
tiddler-border: <<colour background>>
|
||||
tiddler-controls-foreground-hover: #888888
|
||||
tiddler-controls-foreground-selected: #444444
|
||||
tiddler-controls-foreground: #cccccc
|
||||
tiddler-editor-background: #f8f8f8
|
||||
tiddler-editor-border-image: #ffffff
|
||||
tiddler-editor-border: #cccccc
|
||||
tiddler-editor-fields-even: #e0e8e0
|
||||
tiddler-editor-fields-odd: #f0f4f0
|
||||
tiddler-info-background: #f8f8f8
|
||||
tiddler-info-border: #dddddd
|
||||
tiddler-info-tab-background: #f8f8f8
|
||||
tiddler-link-background: <<colour background>>
|
||||
tiddler-link-foreground: <<colour primary>>
|
||||
tiddler-subtitle-foreground: #c0c0c0
|
||||
tiddler-title-foreground: #182955
|
||||
toolbar-new-button:
|
||||
toolbar-options-button:
|
||||
toolbar-save-button:
|
||||
toolbar-info-button:
|
||||
toolbar-edit-button:
|
||||
toolbar-close-button:
|
||||
toolbar-delete-button:
|
||||
toolbar-cancel-button:
|
||||
toolbar-done-button:
|
||||
untagged-background: #999999
|
||||
very-muted-foreground: #888888
|
||||
wikilist-background: #e5e5e5
|
||||
wikilist-item: #ffffff
|
||||
wikilist-info: #000000
|
||||
wikilist-title: #666666
|
||||
wikilist-title-svg: <<colour wikilist-title>>
|
||||
wikilist-url: #aaaaaa
|
||||
wikilist-button-open: #4fb82b
|
||||
wikilist-button-open-hover: green
|
||||
wikilist-button-reveal: #5778d8
|
||||
wikilist-button-reveal-hover: blue
|
||||
wikilist-button-remove: #d85778
|
||||
wikilist-button-remove-hover: red
|
||||
wikilist-toolbar-background: #d3d3d3
|
||||
wikilist-toolbar-foreground: #888888
|
||||
wikilist-droplink-dragover: rgba(255,192,192,0.5)
|
||||
wikilist-button-background: #acacac
|
||||
wikilist-button-foreground: #000000
|
||||
@@ -1,3 +0,0 @@
|
||||
title: $:/palette
|
||||
|
||||
$:/palettes/MWS
|
||||
@@ -1,3 +0,0 @@
|
||||
title: $:/themes/tiddlywiki/vanilla/options/sidebarlayout
|
||||
|
||||
fluid-fixed
|
||||
@@ -1,18 +0,0 @@
|
||||
title: $:/core/templates/static.content
|
||||
|
||||
\define tv-wikilink-template() https://tiddlywiki.com/static/$uri_doubleencoded$.html
|
||||
|
||||
<!-- For Google, and people without JavaScript-->
|
||||
|
||||
<$reveal default="yes" text=<<savingEmpty>> type="nomatch">
|
||||
|
||||
It looks like this browser doesn't run JavaScript. You can use one of these static HTML versions to browse the same content:
|
||||
|
||||
* https://mws.tiddlywiki.com/static.html - browse individual tiddlers as separate pages
|
||||
* https://mws.tiddlywiki.com/alltiddlers.html#HelloThere - single file containing all tiddlers
|
||||
|
||||
---
|
||||
|
||||
{{HelloThere}}
|
||||
|
||||
</$reveal>
|
||||
@@ -1,26 +0,0 @@
|
||||
{
|
||||
"description": "Multiple wiki server documentation edition",
|
||||
"plugins": [
|
||||
],
|
||||
"themes": [
|
||||
"tiddlywiki/vanilla",
|
||||
"tiddlywiki/snowwhite"
|
||||
],
|
||||
"build": {
|
||||
"index": [
|
||||
"--savetiddlers","[tag[external-image]]","images",
|
||||
"--setfield","[tag[external-image]]","_canonical_uri","$:/core/templates/canonical-uri-external-image","text/plain",
|
||||
"--setfield","[tag[external-image]]","text","","text/plain",
|
||||
"--render","$:/core/save/all","index.html","text/plain"],
|
||||
"favicon": [
|
||||
"--savetiddler","$:/favicon.ico","favicon.ico"],
|
||||
"static": [
|
||||
"--render","$:/core/templates/static.template.html","static.html","text/plain",
|
||||
"--render","$:/core/templates/alltiddlers.template.html","alltiddlers.html","text/plain",
|
||||
"--render","[!is[system]]","[encodeuricomponent[]addprefix[static/]addsuffix[.html]]","text/plain","$:/core/templates/static.tiddler.html",
|
||||
"--render","$:/core/templates/static.template.css","static/static.css","text/plain"]
|
||||
},
|
||||
"config": {
|
||||
"retain-original-tiddler-path": true
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
list: GettingStarted
|
||||
title: $:/StoryList
|
||||
@@ -1,2 +0,0 @@
|
||||
title: $:/config/MultiWikiServer/Engine
|
||||
text: better
|
||||
@@ -1,39 +0,0 @@
|
||||
{
|
||||
"description": "Multiple wiki client-server edition",
|
||||
"plugins": [
|
||||
"tiddlywiki/tiddlyweb",
|
||||
"tiddlywiki/filesystem",
|
||||
"tiddlywiki/multiwikiclient",
|
||||
"tiddlywiki/multiwikiserver"
|
||||
],
|
||||
"themes": [
|
||||
"tiddlywiki/vanilla",
|
||||
"tiddlywiki/snowwhite"
|
||||
],
|
||||
"build": {
|
||||
"load-mws-demo-data": [
|
||||
"--mws-load-wiki-folder","./editions/multiwikidocs","mws-docs", "MWS Documentation from https://mws.tiddlywiki.com","mws-docs","MWS Documentation from https://mws.tiddlywiki.com",
|
||||
"--mws-load-wiki-folder","./editions/tw5.com","docs", "TiddlyWiki Documentation from https://tiddlywiki.com","docs","TiddlyWiki Documentation from https://tiddlywiki.com",
|
||||
"--mws-load-wiki-folder","./editions/dev","dev","TiddlyWiki Developer Documentation from https://tiddlywiki.com/dev","dev-docs", "TiddlyWiki Developer Documentation from https://tiddlywiki.com/dev",
|
||||
"--mws-load-wiki-folder","./editions/tour","tour","TiddlyWiki Interactive Tour from https://tiddlywiki.com","tour", "TiddlyWiki Interactive Tour from https://tiddlywiki.com",
|
||||
"--mws-create-bag","bag-alpha","A test bag",
|
||||
"--mws-create-bag","bag-beta","Another test bag",
|
||||
"--mws-create-bag","bag-gamma","A further test bag",
|
||||
"--mws-create-recipe","recipe-rho","bag-alpha bag-beta","First wiki",
|
||||
"--mws-create-recipe","recipe-sigma","bag-alpha bag-gamma","Second Wiki",
|
||||
"--mws-create-recipe","recipe-tau","bag-alpha","Third Wiki",
|
||||
"--mws-create-recipe","recipe-upsilon","bag-alpha bag-gamma bag-beta","Fourth Wiki",
|
||||
"--mws-save-tiddler-text","bag-alpha","$:/SiteTitle","bag-alpha",
|
||||
"--mws-save-tiddler-text","bag-alpha","😀😃😄😁😆🥹😅😂","bag-alpha",
|
||||
"--mws-save-tiddler-text","bag-beta","$:/SiteTitle","bag-beta",
|
||||
"--mws-save-tiddler-text","bag-gamma","$:/SiteTitle","bag-gamma",
|
||||
"--mws-add-permission", "READ", "Allows user to read recipes and bags",
|
||||
"--mws-add-permission", "WRITE", "Gives the user the permission to edit and delete tiddlers",
|
||||
"--mws-add-role", "ADMIN", "System Administrator",
|
||||
"--mws-add-role", "USER", "Basic User",
|
||||
"--mws-assign-role-permission", "ADMIN", "READ",
|
||||
"--mws-assign-role-permission", "ADMIN", "WRITE",
|
||||
"--mws-assign-role-permission", "USER", "READ"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -12,9 +12,9 @@ test('get started link', async ({ page }) => {
|
||||
|
||||
// Load the generated test TW html
|
||||
await page.goto(`file:///${crossPlatformIndexPath}`);
|
||||
const title = await page.title();
|
||||
|
||||
// Sanity check
|
||||
await expect(title, "Expected correct page title to verify the test page was loaded").toContain('TiddlyWiki5');
|
||||
await expect(page.locator('.tc-site-title'), "Expected correct page title to verify the test page was loaded").toHaveText('TiddlyWiki5');
|
||||
|
||||
// Wait for jasmine results bar to appear
|
||||
await expect(page.locator('.jasmine-overall-result'), "Expected jasmine test results bar to be present").toBeVisible({timeout});
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
"description": "TiddlyWiki core tests",
|
||||
"plugins": [
|
||||
"tiddlywiki/jasmine",
|
||||
"tiddlywiki/multiwikiserver",
|
||||
"tiddlywiki/geospatial"
|
||||
],
|
||||
"themes": [
|
||||
|
||||
@@ -4,8 +4,8 @@ tags: Mechanisms
|
||||
title: InfoMechanism
|
||||
type: text/vnd.tiddlywiki
|
||||
|
||||
\define example(name)
|
||||
<$transclude tiddler="""$:/info/url/$name$""" mode="inline"/>
|
||||
\procedure example(name)
|
||||
<$text text={{{ [[$:/info/url/]addsuffix<name>get[text]] }}} />
|
||||
\end
|
||||
|
||||
System tiddlers in the namespace `$:/info/` are used to expose information about the system (including the current browser) so that WikiText applications can adapt themselves to available features.
|
||||
@@ -19,6 +19,8 @@ System tiddlers in the namespace `$:/info/` are used to expose information about
|
||||
|[[$:/info/browser/language]] |<<.from-version "5.1.20">> Language as reported by browser (note that some browsers report two character codes such as `en` while others report full codes such as `en-GB`) |
|
||||
|[[$:/info/browser/screen/width]] |Screen width in pixels |
|
||||
|[[$:/info/browser/screen/height]] |Screen height in pixels |
|
||||
|[[$:/info/browser/darkmode]] |<<.from-version "5.3.7">> Is dark mode preferred? ("yes" or "no") |
|
||||
|[[$:/info/darkmode]] |<<.deprecated-since "5.3.7">> Alias for $:/info/browser/darkmode |
|
||||
|[[$:/info/node]] |Running under [[Node.js]]? ("yes" or "no") |
|
||||
|[[$:/info/url/full]] |<<.from-version "5.1.14">> Full URL of wiki (eg, ''<<example full>>'') |
|
||||
|[[$:/info/url/host]] |<<.from-version "5.1.14">> Host portion of URL of wiki (eg, ''<<example host>>'') |
|
||||
@@ -28,4 +30,3 @@ System tiddlers in the namespace `$:/info/` are used to expose information about
|
||||
|[[$:/info/url/port]] |<<.from-version "5.1.14">> Port portion of URL of wiki (eg, ''<<example port>>'') |
|
||||
|[[$:/info/url/protocol]] |<<.from-version "5.1.14">> Protocol portion of URL of wiki (eg, ''<<example protocol>>'') |
|
||||
|[[$:/info/url/search]] |<<.from-version "5.1.14">> Search portion of URL of wiki (eg, ''<<example search>>'') |
|
||||
|[[$:/info/darkmode]] |<<.from-version "5.1.23">> Is dark mode enabled? ("yes" or "no") |
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
title: MediaQueryTrackerMechanism
|
||||
tags: Mechanisms
|
||||
|
||||
<<.from-version "5.3.7">> The media query tracker mechanism allows you to define [[custom CSS media queries|https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_media_queries/Using_media_queries]] to be bound to a specified [[info|InfoMechanism]] tiddler. The info tiddler will be dynamically update to reflect the current state of the media query.
|
||||
|
||||
Adding or modifying a tiddler tagged $:/tags/MediaQueryTracker takes effect immediately.
|
||||
|
||||
The media queries are always applied against the main window. This is relevant for viewport related media queries such as `min-width` which will always respect the main window and ignore the sizes of any external windows.
|
||||
@@ -6,23 +6,7 @@ type: text/vnd.tiddlywiki
|
||||
\define tv-wikilink-template() https://tiddlywiki.com/static/$uri_doubleencoded$.html
|
||||
\import [subfilter{$:/core/config/GlobalImportFilter}]
|
||||
|
||||
---
|
||||
|
||||
! ~TiddlyWiki ~MultiWikiServer
|
||||
|
||||
UNDER DEVELOPMENT
|
||||
|
||||
This is a branch of TiddlyWiki that adds the ~MultiWikiServer plugin.
|
||||
|
||||
!! Readme
|
||||
|
||||
{{$:/plugins/tiddlywiki/multiwikiserver/readme}}
|
||||
|
||||
!! Docs
|
||||
|
||||
{{$:/plugins/tiddlywiki/multiwikiserver/docs}}
|
||||
|
||||
---
|
||||
! Welcome
|
||||
|
||||
Welcome to TiddlyWiki, a non-linear personal web notebook that anyone can use and keep forever, independently of any corporation.
|
||||
|
||||
|
||||
1478
package-lock.json
generated
1478
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
16
package.json
16
package.json
@@ -24,26 +24,18 @@
|
||||
"wiki"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.47.2",
|
||||
"eslint": "^9.12.0",
|
||||
"@eslint/js": "^9.12.0",
|
||||
"playwright": "^1.47.2"
|
||||
"@eslint/js": "^9.12.0"
|
||||
},
|
||||
"bundleDependencies": [],
|
||||
"license": "BSD",
|
||||
"engines": {
|
||||
"node": ">=0.8.2"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "node ./tiddlywiki.js ./editions/multiwikiserver --mws-load-plugin-bags --build load-mws-demo-data --mws-listen",
|
||||
"build:test-edition": "node ./tiddlywiki.js ./editions/test --verbose --version --build index",
|
||||
"test:multiwikiserver-edition": "node ./tiddlywiki.js ./editions/multiwikiserver/ --build load-mws-demo-data --mws-listen --mws-test-server http://127.0.0.1:8080/ --quit",
|
||||
"mws-add-user": "node ./tiddlywiki.js ./editions/multiwikiserver --build load-mws-demo-data --mws-listen --build mws-add-user --quit",
|
||||
"test": "npm run build:test-edition && npm run test:multiwikiserver-edition",
|
||||
"dev": "node ./tiddlywiki.js ./editions/tw5.com-server --listen",
|
||||
"test": "node ./tiddlywiki.js ./editions/test --verbose --version --build index",
|
||||
"lint:fix": "eslint . --fix",
|
||||
"lint": "eslint ."
|
||||
},
|
||||
"dependencies": {
|
||||
"better-sqlite3": "^11.5.0",
|
||||
"node-sqlite3-wasm": "^0.8.25"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
title: GettingStarted
|
||||
tags: $:/tags/GettingStarted
|
||||
caption: Step 1<br>Syncing
|
||||
|
||||
! ~TiddlyWiki ~MultiWikiServer
|
||||
|
||||
Welcome to ~TiddlyWiki and the ~TiddlyWiki community.
|
||||
|
||||
Please note that ~MultiWikiServer is under active development, and may not be fully robust. Do not use it for anything critical.
|
||||
@@ -1,3 +0,0 @@
|
||||
title: $:/config/SaveWikiButton/Template
|
||||
|
||||
$:/plugins/tiddlywiki/multiwikiclient/save/offline
|
||||
@@ -1,2 +0,0 @@
|
||||
title: $:/config/multiwikiclient/incoming-updates-filter
|
||||
text: [all[]] -[[$:/isEncrypted]] -[prefix[$:/temp/]] -[prefix[$:/status/]] -[[$:/boot/boot.js]] -[[$:/boot/bootprefix.js]] -[[$:/library/sjcl.js]] -[[$:/core]] -[prefix[$:/StoryList]] -[prefix[$:/HistoryList]]
|
||||
@@ -1,2 +0,0 @@
|
||||
title: $:/config/Server/ExternalFilters/[all[tiddlers]] -[[$:/isEncrypted]] -[prefix[$:/temp/]] -[prefix[$:/status/]] -[[$:/boot/boot.js]] -[[$:/boot/bootprefix.js]] -[[$:/library/sjcl.js]] -[[$:/core]]
|
||||
text: yes
|
||||
@@ -1,2 +0,0 @@
|
||||
title: $:/config/multiwikiclient/use-server-sent-events
|
||||
text: no
|
||||
@@ -1,7 +0,0 @@
|
||||
title: $:/config/OfficialPluginLibrary
|
||||
tags: $:/tags/PluginLibrary
|
||||
url: https://tiddlywiki.com/library/v5.1.23/index.html
|
||||
caption: {{$:/language/OfficialPluginLibrary}}
|
||||
enabled: no
|
||||
|
||||
The official plugin library is disabled when using the client-server configuration. Instead, plugins should be installed via the `tiddlywiki.info` file, as described [[here|https://tiddlywiki.com/#Installing%20a%20plugin%20from%20the%20plugin%20library]].
|
||||
@@ -1,4 +0,0 @@
|
||||
title: $:/plugins/tiddlywiki/multiwikiclient/icon/cloud
|
||||
tags: $:/tags/Image
|
||||
|
||||
<svg class="tc-image-cloud tc-image-button" width="22pt" height="22pt" viewBox="0 0 128 128"><g><path d="M24 103C10.745 103 0 92.255 0 79c0-9.697 5.75-18.05 14.027-21.836A24.787 24.787 0 0114 56c0-13.255 10.745-24 24-24 1.373 0 2.718.115 4.028.337C48.628 24.2 58.707 19 70 19c19.882 0 36 16.118 36 36v.082c12.319 1.016 22 11.336 22 23.918 0 12.239-9.16 22.337-20.999 23.814L107 103H24z"/><path class="tc-image-cloud-idle" d="M57.929 84.698a6 6 0 01-8.485 0L35.302 70.556a6 6 0 118.485-8.485l9.9 9.9L81.97 43.686a6 6 0 018.485 8.486L57.929 84.698z"/><path class="tc-image-cloud-progress tc-animate-rotate-slow" d="M44.8 40a3.6 3.6 0 100 7.2h2.06A23.922 23.922 0 0040 64c0 13.122 10.531 23.785 23.603 23.997L64 88l.001-7.2c-9.171 0-16.626-7.348-16.798-16.477L47.2 64c0-5.165 2.331-9.786 5.999-12.868L53.2 55.6a3.6 3.6 0 107.2 0v-12a3.6 3.6 0 00-3.6-3.6h-12zM64 40v7.2c9.278 0 16.8 7.522 16.8 16.8 0 5.166-2.332 9.787-6 12.869V72.4a3.6 3.6 0 10-7.2 0v12a3.6 3.6 0 003.6 3.6h12a3.6 3.6 0 100-7.2l-2.062.001A23.922 23.922 0 0088 64c0-13.255-10.745-24-24-24z"/></g></svg>
|
||||
@@ -1,8 +0,0 @@
|
||||
title: $:/plugins/tiddlywiki/multiwikiclient/info-segment
|
||||
tags: $:/tags/TiddlerInfoSegment
|
||||
|
||||
<$reveal type="nomatch" state=<<folded-state>> text="hide" tag="div" retain="yes" animate="yes">
|
||||
<div class="tc-subtitle">
|
||||
Bag: <$view tiddler="$:/state/multiwikiclient/tiddlers/bag" index=<<currentTiddler>>>(none)</$view>
|
||||
</div>
|
||||
</$reveal>
|
||||
@@ -1,65 +0,0 @@
|
||||
/*\
|
||||
title: $:/plugins/tiddlywiki/multiwikiclient/managetiddleraction.js
|
||||
type: application/javascript
|
||||
module-type: widget
|
||||
|
||||
A widget to manage tiddler actions.
|
||||
\*/
|
||||
(function(){
|
||||
|
||||
/*jslint node: true, browser: true */
|
||||
/*global $tw: false */
|
||||
"use strict";
|
||||
|
||||
var Widget = require("$:/core/modules/widgets/widget.js").widget;
|
||||
|
||||
var ManageTiddlerAction = function(parseTreeNode,options) {
|
||||
this.initialise(parseTreeNode,options);
|
||||
};
|
||||
|
||||
/*
|
||||
Inherit from the base widget class
|
||||
*/
|
||||
ManageTiddlerAction.prototype = new Widget();
|
||||
|
||||
/*
|
||||
Render this widget into the DOM
|
||||
*/
|
||||
ManageTiddlerAction.prototype.render = function(parent,nextSibling) {
|
||||
this.computeAttributes();
|
||||
this.execute();
|
||||
};
|
||||
|
||||
/*
|
||||
Compute the internal state of the widget
|
||||
*/
|
||||
ManageTiddlerAction.prototype.execute = function() {
|
||||
this.tiddler = this.getAttribute("tiddler");
|
||||
};
|
||||
|
||||
/*
|
||||
Invoke the action associated with this widget
|
||||
*/
|
||||
ManageTiddlerAction.prototype.invokeAction = function(triggeringWidget,event) {
|
||||
var pathname = window.location.pathname;
|
||||
var paths = pathname.split("/");
|
||||
var recipeName = paths[paths.length - 1];
|
||||
var bagName = document.querySelector("h1.tc-site-title").innerHTML;
|
||||
window.location.href = "/admin/acl/"+recipeName+"/"+bagName;
|
||||
};
|
||||
|
||||
/*
|
||||
Refresh the widget by ensuring our attributes are up to date
|
||||
*/
|
||||
ManageTiddlerAction.prototype.refresh = function(changedTiddlers) {
|
||||
var changedAttributes = this.computeAttributes();
|
||||
if(changedAttributes.tiddler) {
|
||||
this.refreshSelf();
|
||||
return true;
|
||||
}
|
||||
return this.refreshChildren(changedTiddlers);
|
||||
};
|
||||
|
||||
exports["action-managetiddler"] = ManageTiddlerAction;
|
||||
|
||||
})();
|
||||
@@ -1,399 +0,0 @@
|
||||
/*\
|
||||
title: $:/plugins/tiddlywiki/multiwikiclient/multiwikiclientadaptor.js
|
||||
type: application/javascript
|
||||
module-type: syncadaptor
|
||||
|
||||
A sync adaptor module for synchronising with MultiWikiServer-compatible servers. It has three key areas of concern:
|
||||
|
||||
* Basic operations like put, get, and delete a tiddler on the server
|
||||
* Real time updates from the server (handled by SSE)
|
||||
* Managing login/logout (not yet implemeneted)
|
||||
* Bags and recipes, which are unknown to the syncer
|
||||
|
||||
A key aspect of the design is that the syncer never overlaps basic server operations; it waits for the
|
||||
previous operation to complete before sending a new one.
|
||||
|
||||
\*/
|
||||
(function(){
|
||||
|
||||
/*jslint node: true, browser: true */
|
||||
/*global $tw: false */
|
||||
"use strict";
|
||||
|
||||
var CONFIG_HOST_TIDDLER = "$:/config/multiwikiclient/host",
|
||||
DEFAULT_HOST_TIDDLER = "$protocol$//$host$/",
|
||||
MWC_STATE_TIDDLER_PREFIX = "$:/state/multiwikiclient/",
|
||||
BAG_STATE_TIDDLER = "$:/state/multiwikiclient/tiddlers/bag",
|
||||
REVISION_STATE_TIDDLER = "$:/state/multiwikiclient/tiddlers/revision",
|
||||
CONNECTION_STATE_TIDDLER = "$:/state/multiwikiclient/connection",
|
||||
INCOMING_UPDATES_FILTER_TIDDLER = "$:/config/multiwikiclient/incoming-updates-filter",
|
||||
ENABLE_SSE_TIDDLER = "$:/config/multiwikiclient/use-server-sent-events";
|
||||
|
||||
var SERVER_NOT_CONNECTED = "NOT CONNECTED",
|
||||
SERVER_CONNECTING_SSE = "CONNECTING SSE",
|
||||
SERVER_CONNECTED_SSE = "CONNECTED SSE",
|
||||
SERVER_POLLING = "SERVER POLLING";
|
||||
|
||||
function MultiWikiClientAdaptor(options) {
|
||||
this.wiki = options.wiki;
|
||||
this.host = this.getHost();
|
||||
this.recipe = this.wiki.getTiddlerText("$:/config/multiwikiclient/recipe");
|
||||
this.useServerSentEvents = this.wiki.getTiddlerText(ENABLE_SSE_TIDDLER) === "yes";
|
||||
this.last_known_tiddler_id = $tw.utils.parseNumber(this.wiki.getTiddlerText("$:/state/multiwikiclient/recipe/last_tiddler_id","0"));
|
||||
this.outstandingRequests = Object.create(null); // Hashmap by title of outstanding request object: {type: "PUT"|"GET"|"DELETE"}
|
||||
this.lastRecordedUpdate = Object.create(null); // Hashmap by title of last recorded update via SSE: {type: "update"|"detetion", tiddler_id:}
|
||||
this.logger = new $tw.utils.Logger("MultiWikiClientAdaptor");
|
||||
this.isLoggedIn = false;
|
||||
this.isReadOnly = false;
|
||||
this.logoutIsAvailable = true;
|
||||
// Compile the dirty tiddler filter
|
||||
this.incomingUpdatesFilterFn = this.wiki.compileFilter(this.wiki.getTiddlerText(INCOMING_UPDATES_FILTER_TIDDLER));
|
||||
this.setUpdateConnectionStatus(SERVER_NOT_CONNECTED);
|
||||
}
|
||||
|
||||
MultiWikiClientAdaptor.prototype.setUpdateConnectionStatus = function(status) {
|
||||
this.serverUpdateConnectionStatus = status;
|
||||
this.wiki.addTiddler({
|
||||
title: CONNECTION_STATE_TIDDLER,
|
||||
text: status
|
||||
});
|
||||
};
|
||||
|
||||
MultiWikiClientAdaptor.prototype.name = "multiwikiclient";
|
||||
|
||||
MultiWikiClientAdaptor.prototype.supportsLazyLoading = true;
|
||||
|
||||
MultiWikiClientAdaptor.prototype.setLoggerSaveBuffer = function(loggerForSaving) {
|
||||
this.logger.setSaveBuffer(loggerForSaving);
|
||||
};
|
||||
|
||||
MultiWikiClientAdaptor.prototype.isReady = function() {
|
||||
return true;
|
||||
};
|
||||
|
||||
MultiWikiClientAdaptor.prototype.getHost = function() {
|
||||
var text = this.wiki.getTiddlerText(CONFIG_HOST_TIDDLER,DEFAULT_HOST_TIDDLER),
|
||||
substitutions = [
|
||||
{name: "protocol", value: document.location.protocol},
|
||||
{name: "host", value: document.location.host},
|
||||
{name: "pathname", value: document.location.pathname}
|
||||
];
|
||||
for(var t=0; t<substitutions.length; t++) {
|
||||
var s = substitutions[t];
|
||||
text = $tw.utils.replaceString(text,new RegExp("\\$" + s.name + "\\$","mg"),s.value);
|
||||
}
|
||||
return text;
|
||||
};
|
||||
|
||||
MultiWikiClientAdaptor.prototype.getTiddlerInfo = function(tiddler) {
|
||||
var title = tiddler.fields.title,
|
||||
revision = this.wiki.extractTiddlerDataItem(REVISION_STATE_TIDDLER,title),
|
||||
bag = this.wiki.extractTiddlerDataItem(BAG_STATE_TIDDLER,title);
|
||||
if(revision && bag) {
|
||||
return {
|
||||
title: title,
|
||||
revision: revision,
|
||||
bag: bag
|
||||
};
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
MultiWikiClientAdaptor.prototype.getTiddlerBag = function(title) {
|
||||
return this.wiki.extractTiddlerDataItem(BAG_STATE_TIDDLER,title);
|
||||
};
|
||||
|
||||
MultiWikiClientAdaptor.prototype.getTiddlerRevision = function(title) {
|
||||
return this.wiki.extractTiddlerDataItem(REVISION_STATE_TIDDLER,title);
|
||||
};
|
||||
|
||||
MultiWikiClientAdaptor.prototype.setTiddlerInfo = function(title,revision,bag) {
|
||||
this.wiki.setText(BAG_STATE_TIDDLER,null,title,bag,{suppressTimestamp: true});
|
||||
this.wiki.setText(REVISION_STATE_TIDDLER,null,title,revision,{suppressTimestamp: true});
|
||||
};
|
||||
|
||||
MultiWikiClientAdaptor.prototype.removeTiddlerInfo = function(title) {
|
||||
this.wiki.setText(BAG_STATE_TIDDLER,null,title,undefined,{suppressTimestamp: true});
|
||||
this.wiki.setText(REVISION_STATE_TIDDLER,null,title,undefined,{suppressTimestamp: true});
|
||||
};
|
||||
|
||||
/*
|
||||
Get the current status of the server connection
|
||||
*/
|
||||
MultiWikiClientAdaptor.prototype.getStatus = function(callback) {
|
||||
// Invoke the callback if present
|
||||
if(callback) {
|
||||
callback(
|
||||
null, // Error
|
||||
true, // Is logged in
|
||||
this.username, // Username
|
||||
false, // Is read only
|
||||
true // Is anonymous
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
Get details of changed tiddlers from the server
|
||||
*/
|
||||
MultiWikiClientAdaptor.prototype.getUpdatedTiddlers = function(syncer,callback) {
|
||||
if(this.useServerSentEvents) {
|
||||
var self = this;
|
||||
// Do nothing if there's already a connection in progress.
|
||||
if(this.serverUpdateConnectionStatus !== SERVER_NOT_CONNECTED) {
|
||||
return callback(null,{
|
||||
modifications: [],
|
||||
deletions: []
|
||||
});
|
||||
}
|
||||
// Try to connect a server stream
|
||||
this.setUpdateConnectionStatus(SERVER_CONNECTING_SSE);
|
||||
this.connectServerStream({
|
||||
syncer: syncer,
|
||||
onerror: function(err) {
|
||||
self.logger.log("Error connecting SSE stream",err);
|
||||
// If the stream didn't work, try polling
|
||||
self.setUpdateConnectionStatus(SERVER_POLLING);
|
||||
self.pollServer({
|
||||
callback: function(err,changes) {
|
||||
self.setUpdateConnectionStatus(SERVER_NOT_CONNECTED);
|
||||
callback(null,changes);
|
||||
}
|
||||
});
|
||||
},
|
||||
onopen: function() {
|
||||
self.setUpdateConnectionStatus(SERVER_CONNECTED_SSE);
|
||||
// The syncer is expecting a callback but we don't have any data to send
|
||||
callback(null,{
|
||||
modifications: [],
|
||||
deletions: []
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.pollServer({
|
||||
callback: function(err,changes) {
|
||||
callback(null,changes);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
Attempt to establish an SSE stream with the server and transfer tiddler changes. Options include:
|
||||
|
||||
syncer: reference to syncer object used for storing data
|
||||
onopen: invoked when the stream is successfully opened
|
||||
onerror: invoked if there is an error
|
||||
*/
|
||||
MultiWikiClientAdaptor.prototype.connectServerStream = function(options) {
|
||||
var self = this;
|
||||
const eventSource = new EventSource("/recipes/" + this.recipe + "/events?last_known_tiddler_id=" + this.last_known_tiddler_id);
|
||||
eventSource.onerror = function(event) {
|
||||
if(options.onerror) {
|
||||
options.onerror(event);
|
||||
}
|
||||
}
|
||||
eventSource.onopen = function(event) {
|
||||
if(options.onopen) {
|
||||
options.onopen(event);
|
||||
}
|
||||
}
|
||||
eventSource.addEventListener("change", function(event) {
|
||||
const data = $tw.utils.parseJSONSafe(event.data);
|
||||
if(data) {
|
||||
console.log("SSE data",data)
|
||||
// Update last seen tiddler_id
|
||||
if(data.tiddler_id > self.last_known_tiddler_id) {
|
||||
self.last_known_tiddler_id = data.tiddler_id;
|
||||
}
|
||||
// Record the last update to this tiddler
|
||||
self.lastRecordedUpdate[data.title] = {
|
||||
type: data.is_deleted ? "deletion" : "update",
|
||||
tiddler_id: data.tiddler_id
|
||||
};
|
||||
console.log(`Oustanding requests is ${JSON.stringify(self.outstandingRequests[data.title])}`)
|
||||
// Process the update if the tiddler is not the subject of an outstanding request
|
||||
if(!self.outstandingRequests[data.title]) {
|
||||
if(data.is_deleted) {
|
||||
self.removeTiddlerInfo(data.title);
|
||||
delete options.syncer.tiddlerInfo[data.title];
|
||||
options.syncer.logger.log("Deleting tiddler missing from server:",data.title);
|
||||
options.syncer.wiki.deleteTiddler(data.title);
|
||||
options.syncer.processTaskQueue();
|
||||
} else {
|
||||
var result = self.incomingUpdatesFilterFn.call(self.wiki,self.wiki.makeTiddlerIterator([data.title]));
|
||||
if(result.length > 0) {
|
||||
self.setTiddlerInfo(data.title,data.tiddler_id.toString(),data.bag_name);
|
||||
options.syncer.storeTiddler(data.tiddler);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/*
|
||||
Poll the server for changes. Options include:
|
||||
|
||||
callback: invoked on completion as (err,changes)
|
||||
*/
|
||||
MultiWikiClientAdaptor.prototype.pollServer = function(options) {
|
||||
var self = this;
|
||||
$tw.utils.httpRequest({
|
||||
url: this.host + "recipes/" + this.recipe + "/tiddlers.json",
|
||||
data: {
|
||||
last_known_tiddler_id: this.last_known_tiddler_id,
|
||||
include_deleted: "true"
|
||||
},
|
||||
callback: function(err,data) {
|
||||
// Check for errors
|
||||
if(err) {
|
||||
return options.callback(err);
|
||||
}
|
||||
var modifications = [],
|
||||
deletions = [];
|
||||
var tiddlerInfoArray = $tw.utils.parseJSONSafe(data);
|
||||
$tw.utils.each(tiddlerInfoArray,function(tiddlerInfo) {
|
||||
if(tiddlerInfo.tiddler_id > self.last_known_tiddler_id) {
|
||||
self.last_known_tiddler_id = tiddlerInfo.tiddler_id;
|
||||
}
|
||||
if(tiddlerInfo.is_deleted) {
|
||||
deletions.push(tiddlerInfo.title);
|
||||
} else {
|
||||
modifications.push(tiddlerInfo.title);
|
||||
}
|
||||
});
|
||||
// Invoke the callback with the results
|
||||
options.callback(null,{
|
||||
modifications: modifications,
|
||||
deletions: deletions
|
||||
});
|
||||
// If Browswer Storage tiddlers were cached on reloading the wiki, add them after sync from server completes in the above callback.
|
||||
if($tw.browserStorage && $tw.browserStorage.isEnabled()) {
|
||||
$tw.browserStorage.addCachedTiddlers();
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/*
|
||||
Queue a load for a tiddler if there has been an update for it since the specified revision
|
||||
*/
|
||||
MultiWikiClientAdaptor.prototype.checkLastRecordedUpdate = function(title,revision,syncer) {
|
||||
var lru = this.lastRecordedUpdate[title];
|
||||
if(lru) {
|
||||
var numRevision = $tw.utils.getInt(revision);
|
||||
console.log(`Checking for updates to ${title} since ${JSON.stringify(revision)} comparing to ${numRevision}`)
|
||||
if(lru.tiddler_id > numRevision) {
|
||||
options.syncer.enqueueLoadTiddler(title);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Save a tiddler and invoke the callback with (err,adaptorInfo,revision)
|
||||
*/
|
||||
MultiWikiClientAdaptor.prototype.saveTiddler = function(tiddler,callback,options) {
|
||||
var self = this,
|
||||
title = tiddler.fields.title;
|
||||
if(this.isReadOnly || title.substr(0,MWC_STATE_TIDDLER_PREFIX.length) === MWC_STATE_TIDDLER_PREFIX) {
|
||||
return callback(null);
|
||||
}
|
||||
self.outstandingRequests[title] = {type: "PUT"};
|
||||
$tw.utils.httpRequest({
|
||||
url: this.host + "recipes/" + encodeURIComponent(this.recipe) + "/tiddlers/" + encodeURIComponent(title),
|
||||
type: "PUT",
|
||||
headers: {
|
||||
"Content-type": "application/json"
|
||||
},
|
||||
data: JSON.stringify(tiddler.getFieldStrings()),
|
||||
callback: function(err,data,request) {
|
||||
delete self.outstandingRequests[title];
|
||||
if(err) {
|
||||
return callback(err);
|
||||
}
|
||||
//If Browser-Storage plugin is present, remove tiddler from local storage after successful sync to the server
|
||||
if($tw.browserStorage && $tw.browserStorage.isEnabled()) {
|
||||
$tw.browserStorage.removeTiddlerFromLocalStorage(title)
|
||||
}
|
||||
// Save the details of the new revision of the tiddler
|
||||
var revision = request.getResponseHeader("X-Revision-Number"),
|
||||
bag_name = request.getResponseHeader("X-Bag-Name");
|
||||
console.log(`Saved ${title} with revision ${revision} and bag ${bag_name}`)
|
||||
// If there has been a more recent update from the server then enqueue a load of this tiddler
|
||||
self.checkLastRecordedUpdate(title,revision,options.syncer);
|
||||
// Invoke the callback
|
||||
self.setTiddlerInfo(title,revision,bag_name);
|
||||
callback(null,{bag: bag_name},revision);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/*
|
||||
Load a tiddler and invoke the callback with (err,tiddlerFields)
|
||||
*/
|
||||
MultiWikiClientAdaptor.prototype.loadTiddler = function(title,callback,options) {
|
||||
var self = this;
|
||||
self.outstandingRequests[title] = {type: "GET"};
|
||||
$tw.utils.httpRequest({
|
||||
url: this.host + "recipes/" + encodeURIComponent(this.recipe) + "/tiddlers/" + encodeURIComponent(title),
|
||||
callback: function(err,data,request) {
|
||||
delete self.outstandingRequests[title];
|
||||
if(err === 404) {
|
||||
return callback(null,null);
|
||||
} else if(err) {
|
||||
return callback(err);
|
||||
}
|
||||
var revision = request.getResponseHeader("X-Revision-Number"),
|
||||
bag_name = request.getResponseHeader("X-Bag-Name");
|
||||
// If there has been a more recent update from the server then enqueue a load of this tiddler
|
||||
self.checkLastRecordedUpdate(title,revision,options.syncer);
|
||||
// Invoke the callback
|
||||
self.setTiddlerInfo(title,revision,bag_name);
|
||||
callback(null,$tw.utils.parseJSONSafe(data));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/*
|
||||
Delete a tiddler and invoke the callback with (err)
|
||||
options include:
|
||||
tiddlerInfo: the syncer's tiddlerInfo for this tiddler
|
||||
*/
|
||||
MultiWikiClientAdaptor.prototype.deleteTiddler = function(title,callback,options) {
|
||||
var self = this;
|
||||
if(this.isReadOnly) {
|
||||
return callback(null);
|
||||
}
|
||||
// If we don't have a bag it means that the tiddler hasn't been seen by the server, so we don't need to delete it
|
||||
var bag = this.getTiddlerBag(title);
|
||||
if(!bag) {
|
||||
return callback(null,options.tiddlerInfo.adaptorInfo);
|
||||
}
|
||||
self.outstandingRequests[title] = {type: "DELETE"};
|
||||
// Issue HTTP request to delete the tiddler
|
||||
$tw.utils.httpRequest({
|
||||
url: this.host + "bags/" + encodeURIComponent(bag) + "/tiddlers/" + encodeURIComponent(title),
|
||||
type: "DELETE",
|
||||
callback: function(err,data,request) {
|
||||
delete self.outstandingRequests[title];
|
||||
if(err) {
|
||||
return callback(err);
|
||||
}
|
||||
var revision = request.getResponseHeader("X-Revision-Number");
|
||||
// If there has been a more recent update from the server then enqueue a load of this tiddler
|
||||
self.checkLastRecordedUpdate(title,revision,options.syncer);
|
||||
self.removeTiddlerInfo(title);
|
||||
// Invoke the callback & return null adaptorInfo
|
||||
callback(null,null);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
if($tw.browser && document.location.protocol.substr(0,4) === "http" ) {
|
||||
exports.adaptorClass = MultiWikiClientAdaptor;
|
||||
}
|
||||
|
||||
})();
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"title": "$:/plugins/tiddlywiki/multiwikiclient",
|
||||
"name": "MultiWikiClient",
|
||||
"description": "Synchronise changes from the browser to TiddlyWiki ~MultiWikiServer",
|
||||
"list": "readme",
|
||||
"plugin-priority": 10,
|
||||
"stability": "STABILITY_1_EXPERIMENTAL"
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
title: $:/plugins/tiddlywiki/multiwikiclient/readme
|
||||
|
||||
This plugin runs in the browser to synchronise tiddler changes to and from a TiddlyWiki server running ~MultiWikiServer.
|
||||
|
||||
|
||||
This plugin is inert when run under Node.js. Disabling this plugin via the browser can not be undone via the browser since this plugin provides the mechanism to synchronize settings with the server.
|
||||
|
||||
Changes made while offline are saved in memory and automatically synchonised with the server when the connection is re-established. However, if the browser tab is closed or another URL is loaded, the in-memory changes will be lost. The [[https://tiddlywiki.com/#BrowserStorage Plugin]] may be added to provide temporary filesystem storage of tiddler changes made while offline and enable them to be synchronised with the server the next time the wiki is loaded in the same browser.
|
||||
@@ -1,27 +0,0 @@
|
||||
title: $:/plugins/tiddlywiki/multiwikiclient/readonly
|
||||
tags: [[$:/tags/Stylesheet]]
|
||||
|
||||
\define button-selector(title)
|
||||
button.$title$, .tc-drop-down button.$title$, div.$title$
|
||||
\end
|
||||
|
||||
\define hide-edit-controls()
|
||||
<$reveal state="$:/status/IsReadOnly" type="match" text="yes" default="yes">
|
||||
<<button-selector tc-btn-\%24\%3A\%2Fcore\%2Fui\%2FButtons\%2Fclone>>`,`
|
||||
<<button-selector tc-btn-\%24\%3A\%2Fcore\%2Fui\%2FButtons\%2Fdelete>>`,`
|
||||
<<button-selector tc-btn-\%24\%3A\%2Fcore\%2Fui\%2FButtons\%2Fedit>>`,`
|
||||
<<button-selector tc-btn-\%24\%3A\%2Fcore\%2Fui\%2FButtons\%2Fnew-here>>`,`
|
||||
<<button-selector tc-btn-\%24\%3A\%2Fcore\%2Fui\%2FButtons\%2Fnew-journal-here>>`,`
|
||||
<<button-selector tc-btn-\%24\%3A\%2Fcore\%2Fui\%2FButtons\%2Fimport>>`,`
|
||||
<<button-selector tc-btn-\%24\%3A\%2Fcore\%2Fui\%2FButtons\%2Fmanager>>`,`
|
||||
<<button-selector tc-btn-\%24\%3A\%2Fcore\%2Fui\%2FButtons\%2Fnew-image>>`,`
|
||||
<<button-selector tc-btn-\%24\%3A\%2Fcore\%2Fui\%2FButtons\%2Fnew-journal>>`,`
|
||||
<<button-selector tc-btn-\%24\%3A\%2Fcore\%2Fui\%2FButtons\%2Fnew-tiddler>> `{
|
||||
display: none;
|
||||
}`
|
||||
</$reveal>
|
||||
\end
|
||||
|
||||
\rules only filteredtranscludeinline transcludeinline macrodef macrocallinline macrocallblock
|
||||
|
||||
<<hide-edit-controls>>
|
||||
@@ -1,7 +0,0 @@
|
||||
title: $:/plugins/tiddlywiki/multiwikiclient/save/offline
|
||||
|
||||
\import [subfilter{$:/core/config/GlobalImportFilter}]
|
||||
\define saveTiddlerFilter()
|
||||
[is[tiddler]] -[[$:/boot/boot.css]] -[prefix[$:/HistoryList]] -[status[pending]plugin-type[import]] -[type[application/javascript]library[yes]] -[[$:/boot/boot.js]] -[[$:/boot/bootprefix.js]] -[[$:/plugins/tiddlywiki/filesystem]] -[[$:/plugins/tiddlywiki/multiwikiclient]] -[prefix[$:/temp/]] +[sort[title]] $(publishFilter)$
|
||||
\end
|
||||
{{$:/core/templates/tiddlywiki5.html}}
|
||||
@@ -1,26 +0,0 @@
|
||||
title: $:/core/ui/Buttons/save-wiki
|
||||
tags: $:/tags/PageControls
|
||||
caption: {{$:/plugins/tiddlywiki/multiwikiclient/icon/cloud}} Server status
|
||||
description: Status of synchronisation with server
|
||||
|
||||
\whitespace trim
|
||||
\define config-title()
|
||||
$:/config/PageControlButtons/Visibility/$(listItem)$
|
||||
\end
|
||||
<$button popup=<<qualify "$:/state/popup/save-wiki">> tooltip="Status of synchronisation with server" aria-label="Server status" class=<<tv-config-toolbar-class>> selectedClass="tc-selected">
|
||||
<span class="tc-dirty-indicator">
|
||||
<$list filter="[<tv-config-toolbar-icons>match[yes]]">
|
||||
{{$:/plugins/tiddlywiki/multiwikiclient/icon/cloud}}
|
||||
</$list>
|
||||
<$list filter="[<tv-config-toolbar-text>match[yes]]">
|
||||
<span class="tc-btn-text"><$text text="Server status"/></span>
|
||||
</$list>
|
||||
</span>
|
||||
</$button>
|
||||
<$reveal state=<<qualify "$:/state/popup/save-wiki">> type="popup" position="belowleft" animate="yes">
|
||||
<div class="tc-drop-down">
|
||||
<$list filter="[all[shadows+tiddlers]tag[$:/tags/SyncerDropdown]!has[draft.of]]" variable="listItem">
|
||||
<$transclude tiddler=<<listItem>>/>
|
||||
</$list>
|
||||
</div>
|
||||
</$reveal>
|
||||
@@ -1,9 +0,0 @@
|
||||
title: $:/plugins/multiwikiclient/SideBarSegment
|
||||
tags: $:/tags/SideBarSegment
|
||||
list-before: $:/core/ui/SideBarSegments/page-controls
|
||||
|
||||
<%if [{$:/config/multiwikiclient/use-server-sent-events}match[yes]] %>
|
||||
|
||||
MWS Connection Status: {{$:/state/multiwikiclient/connection}}
|
||||
|
||||
<%endif%>
|
||||
@@ -1,44 +0,0 @@
|
||||
title: $:/plugins/tiddlywiki/multiwikiclient/styles
|
||||
tags: [[$:/tags/Stylesheet]]
|
||||
|
||||
\rules only filteredtranscludeinline transcludeinline macrodef macrocallinline macrocallblock
|
||||
|
||||
body.tc-dirty span.tc-dirty-indicator svg {
|
||||
transition: fill 250ms ease-in-out;
|
||||
}
|
||||
|
||||
body .tc-image-cloud-idle {
|
||||
fill: <<colour background>>;
|
||||
transition: opacity 250ms ease-in-out;
|
||||
opacity: 1;
|
||||
display: unset;
|
||||
}
|
||||
|
||||
body.tc-dirty .tc-image-cloud-idle {
|
||||
opacity: 0;
|
||||
display: none;
|
||||
}
|
||||
|
||||
body .tc-image-cloud-progress {
|
||||
transition: opacity 250ms ease-in-out;
|
||||
transform-origin: 50% 50%;
|
||||
transform: rotate(359deg);
|
||||
animation: animation-rotate-slow 2s infinite linear;
|
||||
fill: <<colour background>>;
|
||||
display: none;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
body.tc-dirty .tc-image-cloud-progress {
|
||||
opacity: 1;
|
||||
display: unset;
|
||||
}
|
||||
|
||||
@keyframes animation-rotate-slow {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: scale(359deg);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
title: $:/plugins/tiddlywiki/multiwikiclient/syncer-actions/copy-logs
|
||||
tags: $:/tags/SyncerDropdown
|
||||
|
||||
<$button message="tm-copy-syncer-logs-to-clipboard" class="tc-btn-invisible">
|
||||
{{$:/core/images/copy-clipboard}} Copy syncer logs to clipboard
|
||||
</$button>
|
||||
@@ -1,9 +0,0 @@
|
||||
title: $:/plugins/tiddlywiki/multiwikiclient/syncer-actions/login-status
|
||||
tags: $:/tags/SyncerDropdown
|
||||
|
||||
<$reveal state="$:/status/IsLoggedIn" type="match" text="yes">
|
||||
<div class="tc-drop-down-info">
|
||||
You are logged in<$reveal state="$:/status/UserName" type="nomatch" text="" default=""> as <strong><$text text={{$:/status/UserName}}/></strong></$reveal><$reveal state="$:/status/IsReadOnly" type="match" text="yes" default="no"> (read-only)</$reveal>
|
||||
</div>
|
||||
<hr/>
|
||||
</$reveal>
|
||||
@@ -1,8 +0,0 @@
|
||||
title: $:/plugins/tiddlywiki/multiwikiclient/syncer-actions/login
|
||||
tags: $:/tags/SyncerDropdown
|
||||
|
||||
<$reveal state="$:/status/IsLoggedIn" type="nomatch" text="yes">
|
||||
<$button message="tm-login" class="tc-btn-invisible">
|
||||
{{$:/core/images/unlocked-padlock}} Login
|
||||
</$button>
|
||||
</$reveal>
|
||||
@@ -1,8 +0,0 @@
|
||||
title: $:/plugins/tiddlywiki/multiwikiclient/syncer-actions/logout
|
||||
tags: $:/tags/SyncerDropdown
|
||||
|
||||
<$reveal state="$:/status/IsLoggedIn" type="match" text="yes">
|
||||
<$button message="tm-logout" class="tc-btn-invisible">
|
||||
{{$:/core/images/cancel-button}} Logout
|
||||
</$button>
|
||||
</$reveal>
|
||||
@@ -1,9 +0,0 @@
|
||||
title: $:/plugins/tiddlywiki/multiwikiclient/syncer-actions/refresh
|
||||
tags: $:/tags/SyncerDropdown
|
||||
|
||||
<$reveal state="$:/status/IsLoggedIn" type="match" text="yes">
|
||||
<$button tooltip="Get latest changes from the server" aria-label="Refresh from server" class="tc-btn-invisible">
|
||||
<$action-sendmessage $message="tm-server-refresh"/>
|
||||
{{$:/core/images/refresh-button}}<span class="tc-btn-text"><$text text="Get latest changes from the server"/></span>
|
||||
</$button>
|
||||
</$reveal>
|
||||
@@ -1,9 +0,0 @@
|
||||
title: $:/plugins/tiddlywiki/multiwikiclient/syncer-actions/save-snapshot
|
||||
tags: $:/tags/SyncerDropdown
|
||||
|
||||
<$button class="tc-btn-invisible">
|
||||
<$wikify name="site-title" text={{$:/config/SaveWikiButton/Filename}}>
|
||||
<$action-sendmessage $message="tm-download-file" $param={{$:/config/SaveWikiButton/Template}} filename=<<site-title>>/>
|
||||
</$wikify>
|
||||
{{$:/core/images/download-button}} Save snapshot for offline use
|
||||
</$button>
|
||||
@@ -1,2 +0,0 @@
|
||||
title: $:/tags/SyncerDropdown
|
||||
list: $:/plugins/tiddlywiki/multiwikiclient/syncer-actions/login-status $:/plugins/tiddlywiki/multiwikiclient/syncer-actions/login $:/plugins/tiddlywiki/multiwikiclient/syncer-actions/refresh $:/plugins/tiddlywiki/multiwikiclient/syncer-actions/logout $:/plugins/tiddlywiki/multiwikiclient/syncer-actions/save-snapshot $:/plugins/tiddlywiki/multiwikiclient/syncer-actions/copy-logs
|
||||
@@ -1,43 +0,0 @@
|
||||
/*\
|
||||
title: $:/plugins/tiddlywiki/multiwikiserver/auth/authentication.js
|
||||
type: application/javascript
|
||||
module-type: library
|
||||
|
||||
Handles authentication related operations
|
||||
|
||||
\*/
|
||||
|
||||
(function() {
|
||||
|
||||
/*jslint node: true, browser: true */
|
||||
/*global $tw: false */
|
||||
"use strict";
|
||||
|
||||
var crypto = require("crypto");
|
||||
|
||||
function Authenticator(database) {
|
||||
if(!(this instanceof Authenticator)) {
|
||||
return new Authenticator(database);
|
||||
}
|
||||
this.sqlTiddlerDatabase = database;
|
||||
}
|
||||
|
||||
Authenticator.prototype.verifyPassword = function(inputPassword, storedHash) {
|
||||
var hashedInput = this.hashPassword(inputPassword);
|
||||
return hashedInput === storedHash;
|
||||
};
|
||||
|
||||
Authenticator.prototype.hashPassword = function(password) {
|
||||
return crypto.createHash("sha256").update(password).digest("hex");
|
||||
};
|
||||
|
||||
Authenticator.prototype.createSession = function(userId) {
|
||||
var sessionId = crypto.randomBytes(16).toString("hex");
|
||||
// Store the session in your database or in-memory store
|
||||
this.sqlTiddlerDatabase.createUserSession(userId, sessionId);
|
||||
return sessionId;
|
||||
};
|
||||
|
||||
exports.Authenticator = Authenticator;
|
||||
|
||||
})();
|
||||
@@ -1,19 +0,0 @@
|
||||
title: $:/plugins/tiddlywiki/multiwikiserver/auth/form/login
|
||||
tags: $:/tags/ServerRoute
|
||||
route-method: GET
|
||||
route-path: /login
|
||||
|
||||
<$transclude tiddler="$:/plugins/tiddlywiki/multiwikiserver/auth/form/login/styles"/>
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<$transclude tiddler="$:/plugins/tiddlywiki/multiwikiserver/auth/form/login/head"/>
|
||||
</head>
|
||||
<body>
|
||||
<div class="login-container">
|
||||
<$transclude tiddler="$:/plugins/tiddlywiki/multiwikiserver/auth/form/login/header" mode="block"/>
|
||||
<$transclude tiddler="$:/plugins/tiddlywiki/multiwikiserver/auth/form/login/form" mode="block"/>
|
||||
<$transclude tiddler="$:/plugins/tiddlywiki/multiwikiserver/auth/form/login/error-message" mode="block"/>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,7 +0,0 @@
|
||||
title: $:/plugins/tiddlywiki/multiwikiserver/auth/form/login/error-message
|
||||
|
||||
<$list filter="[[$:/temp/mws/login/error]!is[missing]]" variable="errorTiddler">
|
||||
<div class="tc-error-message">
|
||||
{{$:/temp/mws/login/error}}
|
||||
</div>
|
||||
</$list>
|
||||
@@ -1,8 +0,0 @@
|
||||
title: $:/plugins/tiddlywiki/multiwikiserver/auth/form/login/form
|
||||
|
||||
<form class="login-form" method="POST" action="/login">
|
||||
<input type="hidden" name="returnUrl" value=<<returnUrl>>/>
|
||||
<input type="text" name="username" placeholder="Username"/>
|
||||
<input type="password" name="password" placeholder="Password"/>
|
||||
<input type="submit" value="Log In"/>
|
||||
</form>
|
||||
@@ -1,3 +0,0 @@
|
||||
title: $:/plugins/tiddlywiki/multiwikiserver/auth/form/login/head
|
||||
|
||||
<title>TiddlyWiki Login</title>
|
||||
@@ -1,41 +0,0 @@
|
||||
title: $:/plugins/tiddlywiki/multiwikiserver/auth/form/login/header
|
||||
|
||||
<div class="login-header">
|
||||
<h1>Be our Guest</h1>
|
||||
<a class="" href="/">Explore as Guest</a>
|
||||
<h2>TiddlyWiki Login</h2>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.login-header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.login-header h1 {
|
||||
font-size: 30px;
|
||||
}
|
||||
|
||||
.login-header h2 {
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
.login-header h1,
|
||||
.login-header h2 {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.login-header a {
|
||||
padding: 10px 20px;
|
||||
background-color: #1366a8;
|
||||
color: #FFF;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
border-radius: 5px;
|
||||
width: 85%;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
@@ -1,48 +0,0 @@
|
||||
title: $:/plugins/tiddlywiki/multiwikiserver/auth/form/login/styles
|
||||
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
background-color: #f0f0f0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100vh;
|
||||
}
|
||||
.login-container {
|
||||
max-width: 300px;
|
||||
padding: 20px;
|
||||
background-color: #ffffff;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||
}
|
||||
.login-container h1 {
|
||||
text-align: center;
|
||||
color: #333;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.login-form input {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
margin: 10px 0;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.login-form input[type="submit"] {
|
||||
background-color: #4CAF50;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
}
|
||||
.login-form input[type="submit"]:hover {
|
||||
background-color: #45a049;
|
||||
}
|
||||
.tc-error-message {
|
||||
color: #ff0000;
|
||||
text-align: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
</style>
|
||||
@@ -1,4 +0,0 @@
|
||||
title: $:/config/MultiWikiServer/AllowAnonymousReads
|
||||
text: no
|
||||
description: Controls whether anonymous users can read wiki content
|
||||
type: text/plain
|
||||
@@ -1,4 +0,0 @@
|
||||
title: $:/config/MultiWikiServer/AllowAnonymousWrites
|
||||
text: no
|
||||
description: Controls whether anonymous users can write to the wiki
|
||||
type: text/plain
|
||||
@@ -1,2 +0,0 @@
|
||||
title: $:/config/MultiWikiServer/AttachmentSizeLimit
|
||||
text: 204800
|
||||
@@ -1,2 +0,0 @@
|
||||
title: $:/config/MultiWikiServer/EnableAttachments
|
||||
text: yes
|
||||
@@ -1,5 +0,0 @@
|
||||
title: $:/plugins/tiddlywiki/multiwikiserver/readme
|
||||
|
||||
This plugin extends the TiddlyWiki 5 server running on Node.js to be able to host multiple wikis that can share content or be independent.
|
||||
|
||||
See https://mws.tiddlywiki.com/ for more information.
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user