1
0
mirror of https://github.com/Jermolene/TiddlyWiki5 synced 2026-05-28 00:02:17 +00:00

Compare commits

...

279 Commits

Author SHA1 Message Date
webplusai 4dc2486d4b Fix issue with default user role on create, profile navigation issue (#8761) 2024-11-19 11:43:07 +00:00
Jeremy Ruston d9256707f4 Add troubleshooting instructions 2024-11-18 12:25:07 +00:00
Jeremy Ruston e85cfdefa6 Merge branch 'master' into multi-wiki-support 2024-11-18 11:08:43 +00:00
Jeremy Ruston b198270cb5 Merge branch 'master' into multi-wiki-support 2024-11-18 09:53:48 +00:00
Jeremy Ruston 3836a5d08c Styles for the improvement banner 2024-11-17 18:06:54 +00:00
Jeremy Ruston 83f7b9023d Retain original tiddler paths to try to get the contribution banner working 2024-11-17 17:40:42 +00:00
Jeremy Ruston 9362121e8d Typo 2024-11-17 12:55:04 +00:00
Mario Pietsch 07c3081e7f [MWS] Move test dependencies to devDep. Update better-sqlite3 to latest (#8717)
* move test dependencies to devDep. Update better-sqlite3 to latest

* update Node version for workflow

* downgrade engines - node

* adjust wasm package
2024-11-17 10:35:49 +00:00
Jeremy Ruston 7c4d0d7f5e Docs: Add table of contents and custom palette 2024-11-16 12:35:04 +00:00
Jeremy Ruston 520188dc2b Tweak favicon 2024-11-16 08:53:20 +00:00
Jeremy Ruston aa0d59e4ba Docs: Add basic system tiddlers 2024-11-15 21:34:47 +00:00
Jeremy Ruston c1a441b7a1 Style tweak 2024-11-15 21:25:26 +00:00
Jeremy Ruston 0f558a1442 Move manage-acl page control into multiwikiclient plugin
@webplusai otherwise the control shows up for all wikis built with this version of the core, including those that don't include any MWS plugins. They get an "Undefined widget 'action-managetiddler'" error
2024-11-15 21:24:47 +00:00
Jeremy Ruston 31a7d648e5 Docs: Move documentation from plugin into multiwikidocs edition
@webplusai future docs updates should go to `editions/multiwikidocs`

By the way, it's worth noting that the recent updates in # 8748 were in Markdown syntax, which is slightly different from TiddlyWiki syntax. See the corrections here.
2024-11-15 21:04:15 +00:00
Jeremy Ruston a6c5b86343 Docs: Copy content from tiddlywiki.com 2024-11-15 20:47:55 +00:00
Jeremy Ruston ffbf24ae92 CI: We don't need to run the tests when building mws.tiddlywiki.com 2024-11-15 20:30:46 +00:00
webplusai 12e48af372 update multiwiki server documentation (#8748)
* mws authentication

* add more tests and permission checkers

* add logic to ensure that only authenticated users' requests are handled

* add custom login page

* Implement user authentication as well as session handling

* work on user operations authorization

* add middleware to route handlers for bags & tiddlers routes

* add feature that only returns the tiddlers and bags which the user has permission to access on index page

* refactor auth routes & added user management page

* fix Ci Test failure issue

* fix users list page, add manage roles page

* add commands and scripts to create new user & assign roles and permissions

* resolved ci-test failure

* add ACL permissions to bags & tiddlers on creation

* fix comments and access control list bug

* fix indentation issues

* working on user profile edit

* remove list users command & added support for database in server options

* implement user profile update and password change feature

* update plugin readme

* implement command which triggers protected mode on the server

* revert server-wide auth flag. Implement selective authorization

* ACL management feature

* Complete Access control list implementation

* Added support to manage users' assigned role by admin

* fix comments

* fix comment

* Add user profile management and account deletion functionality

* add success and error message feedback for user profile operations

* fix indentation issues

* Add command to create admin user if none exists when the start command is executed

* refactor annonymous user flow with create admin implementation

* remove mws-add-user from start command

* admin configuration for annonymous read-write opearations

* fix comments

* change get-anon handler to POST

* update multiwiki server documentation
2024-11-15 20:29:39 +00:00
Jeremy Ruston f61a131119 Transparent background for favicon 2024-11-15 18:19:10 +00:00
Jeremy Ruston 8d53a37d34 Docs: Fix CI by including a favicon 2024-11-15 18:09:44 +00:00
Jeremy Ruston 386af03f6d Add multiwikidocs edition and CI to build it 2024-11-15 17:56:00 +00:00
Jeremy Ruston 8ac2730eb1 Merge branch 'master' into multi-wiki-support 2024-11-15 17:43:04 +00:00
webplusai e873518d6f Admin configuration for anonymous read-write operations (#8736)
* mws authentication

* add more tests and permission checkers

* add logic to ensure that only authenticated users' requests are handled

* add custom login page

* Implement user authentication as well as session handling

* work on user operations authorization

* add middleware to route handlers for bags & tiddlers routes

* add feature that only returns the tiddlers and bags which the user has permission to access on index page

* refactor auth routes & added user management page

* fix Ci Test failure issue

* fix users list page, add manage roles page

* add commands and scripts to create new user & assign roles and permissions

* resolved ci-test failure

* add ACL permissions to bags & tiddlers on creation

* fix comments and access control list bug

* fix indentation issues

* working on user profile edit

* remove list users command & added support for database in server options

* implement user profile update and password change feature

* update plugin readme

* implement command which triggers protected mode on the server

* revert server-wide auth flag. Implement selective authorization

* ACL management feature

* Complete Access control list implementation

* Added support to manage users' assigned role by admin

* fix comments

* fix comment

* Add user profile management and account deletion functionality

* add success and error message feedback for user profile operations

* fix indentation issues

* Add command to create admin user if none exists when the start command is executed

* refactor annonymous user flow with create admin implementation

* remove mws-add-user from start command

* admin configuration for annonymous read-write opearations

* fix comments

* change get-anon handler to POST
2024-11-14 17:47:25 +00:00
webplusai 316bd65296 Add success and error message feedback for user profile operations (#8716)
* mws authentication

* add more tests and permission checkers

* add logic to ensure that only authenticated users' requests are handled

* add custom login page

* Implement user authentication as well as session handling

* work on user operations authorization

* add middleware to route handlers for bags & tiddlers routes

* add feature that only returns the tiddlers and bags which the user has permission to access on index page

* refactor auth routes & added user management page

* fix Ci Test failure issue

* fix users list page, add manage roles page

* add commands and scripts to create new user & assign roles and permissions

* resolved ci-test failure

* add ACL permissions to bags & tiddlers on creation

* fix comments and access control list bug

* fix indentation issues

* working on user profile edit

* remove list users command & added support for database in server options

* implement user profile update and password change feature

* update plugin readme

* implement command which triggers protected mode on the server

* revert server-wide auth flag. Implement selective authorization

* ACL management feature

* Complete Access control list implementation

* Added support to manage users' assigned role by admin

* fix comments

* fix comment

* Add user profile management and account deletion functionality

* add success and error message feedback for user profile operations

* fix indentation issues

* Add command to create admin user if none exists when the start command is executed

* refactor annonymous user flow with create admin implementation

* remove mws-add-user from start command
2024-11-08 10:09:42 +00:00
Jeremy Ruston 3a5f67d4f5 Update dependencies
Tests were breaking in GitHub Actions but work OK on my machine. Problem was better-sqlite3 not being prebuilt, so I am testing to see if an update helps.
2024-11-06 15:31:41 +00:00
Jeremy Ruston ad901a9f50 Merge branch 'master' into multi-wiki-support 2024-11-06 15:20:44 +00:00
webplusai c7531e53ab Add user profile management and account deletion functionality (#8712)
* mws authentication

* add more tests and permission checkers

* add logic to ensure that only authenticated users' requests are handled

* add custom login page

* Implement user authentication as well as session handling

* work on user operations authorization

* add middleware to route handlers for bags & tiddlers routes

* add feature that only returns the tiddlers and bags which the user has permission to access on index page

* refactor auth routes & added user management page

* fix Ci Test failure issue

* fix users list page, add manage roles page

* add commands and scripts to create new user & assign roles and permissions

* resolved ci-test failure

* add ACL permissions to bags & tiddlers on creation

* fix comments and access control list bug

* fix indentation issues

* working on user profile edit

* remove list users command & added support for database in server options

* implement user profile update and password change feature

* update plugin readme

* implement command which triggers protected mode on the server

* revert server-wide auth flag. Implement selective authorization

* ACL management feature

* Complete Access control list implementation

* Added support to manage users' assigned role by admin

* fix comments

* fix comment

* Add user profile management and account deletion functionality
2024-10-30 18:38:21 +00:00
webplusai 6a7612ddf8 MWS authentication (#8596)
* mws authentication

* add more tests and permission checkers

* add logic to ensure that only authenticated users' requests are handled

* add custom login page

* Implement user authentication as well as session handling

* work on user operations authorization

* add middleware to route handlers for bags & tiddlers routes

* add feature that only returns the tiddlers and bags which the user has permission to access on index page

* refactor auth routes & added user management page

* fix Ci Test failure issue

* fix users list page, add manage roles page

* add commands and scripts to create new user & assign roles and permissions

* resolved ci-test failure

* add ACL permissions to bags & tiddlers on creation

* fix comments and access control list bug

* fix indentation issues

* working on user profile edit

* remove list users command & added support for database in server options

* implement user profile update and password change feature

* update plugin readme

* implement command which triggers protected mode on the server

* revert server-wide auth flag. Implement selective authorization

* ACL management feature

* Complete Access control list implementation

* Added support to manage users' assigned role by admin

* fix comments

* fix comment
2024-10-30 17:59:44 +00:00
Jeremy Ruston 5d6ddaee7e Fix plugin bag names 2024-10-17 15:51:01 +01:00
Jeremy Ruston d0575d6e8e Merge branch 'master' into multi-wiki-support 2024-10-17 13:19:19 +01:00
Jeremy Ruston 6dd6b7bef1 Refactor conditional function definitions
Function definitions within a condition do not function as expected thanks to JS hoisting, so it is better to avoid it, and keep ESLint happy as well.

See discussion at #8622 with @pmario
2024-09-30 15:28:37 +01:00
Jeremy Ruston 517dae8242 Merge branch 'master' into multi-wiki-support 2024-09-30 15:20:56 +01:00
Mario Pietsch d0b5f413a4 MWS icon remove redundant paths (#8621) 2024-09-30 14:37:31 +01:00
Jeremy Ruston 6e7efeb126 Merge branch 'master' into multi-wiki-support 2024-09-20 13:58:19 +01:00
Jeremy Ruston 0f4f60f978 Fix load wiki folder command 2024-09-20 13:57:54 +01:00
Jeremy Ruston a9c124cffd Whitespace clean up 2024-09-20 13:57:12 +01:00
Jeremy Ruston 4050ba5f92 Avoid optional chaining syntax
So that we can run on older Node.js versions
2024-09-11 19:22:29 +01:00
webplusai b698b3e9cf revert playwright test (#8573) 2024-08-30 19:55:04 +01:00
webplusai a930411cfd Handle directory creation if doesn't exist for test store (#8562)
* fix breaking bug in image tiddler attachment

* fix comments

* fix code format

* refactor processIncomingTiddler flow

* remove whitespaces after if statements

* refactor attachment_blob persistence flow

* refactor process tiddler to support different attachments

* add tests for attachment

* add more attachement test cases

* working on adding instanbul for test coverage report

* code coverage report generation

* remove unnecessary packages

* fix comments

* handle directory creation if doesn't exist for test store

* resolve issue with CI tests failure
2024-08-30 13:14:18 +01:00
Jeremy Ruston 90848a1e96 Change for the sake of CI 2024-08-29 10:00:08 +01:00
Jeremy Ruston c73311684b Merge branch 'master' into multi-wiki-support 2024-08-28 17:17:22 +01:00
Jeremy Ruston 8a5a684218 Clean ups from #8456 2024-08-28 17:16:35 +01:00
webplusai 3287dce40c MWS: fix editing attachment tiddlers (#8455)
* fix breaking bug in image tiddler attachment

* fix comments

* fix code format

* refactor processIncomingTiddler flow

* remove whitespaces after if statements

* refactor attachment_blob persistence flow

* refactor process tiddler to support different attachments

* add tests for attachment

* add more attachement test cases

* working on adding instanbul for test coverage report

* code coverage report generation

* remove unnecessary packages

* fix comments
2024-08-28 17:13:52 +01:00
Jeremy Ruston eac8a2c3d8 Merge branch 'master' into multi-wiki-support 2024-08-27 09:19:00 +01:00
Jeremy Ruston 535c440741 Remove extraneous storylist tiddler 2024-08-22 11:58:10 +01:00
Jeremy Ruston 9eb28a0e5f Merge branch 'master' into multi-wiki-support 2024-08-22 11:57:38 +01:00
Jeremy Ruston edc32c5262 Merge branch 'master' into multi-wiki-support 2024-08-19 13:11:04 +01:00
Jeremy Ruston 6492ed36bf Merge branch 'master' into multi-wiki-support 2024-08-06 21:38:38 +01:00
Jeremy Ruston 2819b53c04 Merge branch 'master' into multi-wiki-support 2024-07-31 14:03:38 +01:00
Jeremy Ruston adb2ffcd4d Update package.json 2024-07-31 14:03:35 +01:00
Jeremy Ruston 97db75e741 Merge branch 'master' into multi-wiki-support 2024-07-26 14:00:34 +01:00
Jeremy Ruston 066771e6e9 Allow attachment handling to be switched off 2024-07-16 10:32:34 +01:00
Jeremy Ruston b4664bd7d1 getBagTiddlerStream should return bagname 2024-07-16 10:31:13 +01:00
Jeremy Ruston 882438db14 Merge branch 'master' into multi-wiki-support 2024-07-15 20:58:31 +01:00
Jeremy Ruston e9f314579d Fix failing tests 2024-07-10 14:42:56 +01:00
Jeremy Ruston 9e2962b24e Merge branch 'master' into multi-wiki-support 2024-07-10 14:37:30 +01:00
Jeremy Ruston 5b53a14903 Use $tw.utils.encodeURIComponentExtended for consistency with encodeuricomponent operator 2024-07-10 13:16:47 +01:00
poc2go 39b7a4fb71 Adds 'created' field to attachment meta.json file (#8356) 2024-07-09 16:35:18 +01:00
Jeremy Ruston f1d0e52ff7 New commands 2024-07-05 18:40:59 +01:00
Jeremy Ruston d1edf6424d Merge branch 'master' into multi-wiki-support 2024-07-05 16:52:41 +01:00
Jeremy Ruston e9cbb51f68 Merge branch 'master' into multi-wiki-support 2024-06-28 14:55:50 +01:00
Jeremy Ruston 0ff3875b8a Update readme.tid 2024-06-24 11:42:59 +01:00
Jeremy Ruston 38e1ea8f90 Merge branch 'master' into multi-wiki-support 2024-06-22 16:56:46 +01:00
Jeremy Ruston 2b2fd4bdb7 Merge branch 'master' into multi-wiki-support 2024-06-08 14:59:17 +01:00
Jeremy Ruston b8f463d8ba Merge branch 'master' into multi-wiki-support 2024-05-31 22:39:14 +01:00
Jeremy Ruston 3f30cb4991 Merge branch 'master' into multi-wiki-support 2024-05-31 14:05:30 +01:00
Jeremy Ruston 049951e269 Merge branch 'master' into multi-wiki-support 2024-05-31 13:46:36 +01:00
Jeremy Ruston 3c36e4bd55 Merge branch 'master' into multi-wiki-support 2024-05-31 13:32:53 +01:00
Jeremy Ruston d03ad0bca6 Merge branch 'master' into multi-wiki-support 2024-05-29 21:42:34 +01:00
Jeremy Ruston 109d92ae68 Update readme 2024-05-28 15:36:34 +01:00
Jeremy Ruston e396334b20 Disable SSE by default
The current implementation is still broken, and actually more broken than it was before a37d50166f.

It seems that we should be exposing the SSE events to the syncer so that the resulting updates can be handled by the syncers existing task scheduler
2024-05-28 13:15:34 +01:00
Jeremy Ruston d8fd9af63a Fix string vs. number confusion for revisions 2024-05-28 09:08:17 +01:00
Jeremy Ruston 9b6d677726 Fix revision comparisons 2024-05-27 17:33:27 +01:00
Jeremy Ruston a37d50166f Tentative fix for SSE issue
See https://github.com/Jermolene/TiddlyWiki5/pull/7915#issuecomment-2124341900
2024-05-27 15:01:15 +01:00
Jeremy Ruston b4564e31bd Merge branch 'master' into multi-wiki-support 2024-05-26 10:40:19 +01:00
Jeremy Ruston 33fb857d6b Merge branch 'master' into multi-wiki-support 2024-05-22 15:42:22 +01:00
Jeremy Ruston 4c2c7266d7 Make usage of SSE be configurable 2024-05-22 10:39:02 +01:00
Jeremy Ruston 3c7f06009b Temporarily Disable SSE 2024-05-22 10:33:35 +01:00
Jeremy Ruston 87f4a525f8 Mark MWS plugins as experimental 2024-05-21 12:07:01 +01:00
Jeremy Ruston 92a1c56d31 Merge branch 'master' into multi-wiki-support 2024-05-21 12:05:26 +01:00
Jeremy Ruston d8eb5cb459 Merge branch 'master' into multi-wiki-support 2024-05-20 14:42:11 +01:00
Jeremy Ruston 268aaebaf0 Logging 2024-04-16 14:51:26 +01:00
Jeremy Ruston ea318bab6e Fix file separator for Windows 2024-04-16 13:47:15 +01:00
Jeremy Ruston aafe775779 Debugging 2024-04-16 12:49:09 +01:00
Jeremy Ruston 129bbe421c Debugging 2024-04-16 12:45:35 +01:00
Jeremy Ruston 471ba99a5d Debugging 2024-04-16 12:43:41 +01:00
Jeremy Ruston 516a17a6f0 More debugging 2024-04-16 12:41:50 +01:00
Jeremy Ruston 2e4980bb97 Add some error checking 2024-04-16 12:38:05 +01:00
Jeremy Ruston db9978f8c2 Syncer should only save existing tiddlers if they have changed 2024-04-16 11:59:52 +01:00
Jeremy Ruston cc4cb04900 Turn on WAL mode for better-sqlite3 2024-04-14 19:12:21 +01:00
Jeremy Ruston 9ba4556250 Add support for the plugin library
We create a system bag to contain each plugin/theme/language. It seems wasteful because it results in lots of bags, but the semantics are exactly right and so it seems like the right approach
2024-04-14 18:41:34 +01:00
Jeremy Ruston 131a5abeb8 Protect against syncing client state tiddlers to the server
The default sync filter omits $:/state/, but users can customise it
2024-04-12 10:58:45 +01:00
Jeremy Ruston ce79a4add8 Add bag indicator to tiddler info panel 2024-04-12 10:28:26 +01:00
Jeremy Ruston 28a831489b Merge branch 'master' into multi-wiki-support 2024-04-12 09:59:12 +01:00
Jeremy Ruston 51cdca6841 Merge branch 'master' into multi-wiki-support 2024-04-03 21:51:51 +01:00
Jeremy Ruston d51ad80f80 Incoming updates filter should exclude tiddlers prefixed $:/StoryList and $:/HistoryList
Thanks @pmario
2024-03-28 17:32:10 +00:00
Jeremy Ruston ad528d6b1f Fix get wiki crash when serving a recipe with no tiddlers in it
Fixes #8110
2024-03-27 14:41:03 +00:00
Jeremy Ruston f2947e73b3 Filter updates from the server
We don't want $:/StoryList etc.
2024-03-26 17:16:14 +00:00
Jeremy Ruston baee0bb301 Fix broken updating of last_known_tiddler_id from streamed data 2024-03-26 12:25:49 +00:00
Jeremy Ruston 8a2111f150 Update GettingStarted 2024-03-26 12:25:13 +00:00
Jeremy Ruston fcffff3964 npm start should use mws-listen 2024-03-25 22:38:10 +00:00
Jeremy Ruston cca1f21d02 Undo unneeded changes to image widget 2024-03-25 22:35:42 +00:00
Jeremy Ruston 37f6930bf2 Quit command should abort any pending commands 2024-03-25 22:33:41 +00:00
Jeremy Ruston 4b1affee50 Load streamed tiddlers immediately, rather than scheduling a load 2024-03-25 17:49:34 +00:00
Jeremy Ruston 464d17b522 Update last known tiddler ID for events delivered via SSE 2024-03-25 17:43:29 +00:00
Jeremy Ruston d1bb7159b8 Expose the connection status in the UI 2024-03-25 17:07:36 +00:00
Jeremy Ruston b58cfe6324 SSE client: better state management to avoid multiple connections 2024-03-25 16:43:41 +00:00
Jeremy Ruston 7a0c43436f First pass at SSE support 2024-03-25 08:36:42 +00:00
Jeremy Ruston 708e21951f Fix syncing 2024-03-24 21:15:59 +00:00
Jeremy Ruston 8198574087 Remove code that is unneeded for the moment 2024-03-24 21:15:31 +00:00
Jeremy Ruston 6c9b92400e GET recipes/name/tiddlers.json should use include_deleted parameter 2024-03-24 18:32:56 +00:00
Jeremy Ruston 8091db37e8 Merge branch 'master' into multi-wiki-support 2024-03-23 17:32:03 +00:00
Jeremy Ruston e66b67dedc Only turn binary tiddlers into attachments 2024-03-23 09:54:15 +00:00
Jeremy Ruston a2012dcff8 Typo in comment 2024-03-23 09:54:03 +00:00
Jeremy Ruston 08649dd1eb More efficient syncing
Thank you @PotOfCoffee2Go I ended up taking some of your code from #8101 to get this up and running. There's still some stuff missing (like the tests!) but it gets things moving.
2024-03-23 09:27:54 +00:00
Jeremy Ruston 52f76380c7 Remove code relating to revision and bag as fields 2024-03-22 17:58:52 +00:00
Jeremy Ruston 1f63bcbbd0 Remove some templates that are no longer needed 2024-03-20 19:24:21 +00:00
Jeremy Ruston 3aa5607a3a Stop storing bag and revision details as tiddler fields
Instead we store them as dictionary tiddlers
2024-03-20 19:22:12 +00:00
Jeremy Ruston eaebeb87c9 Include bagname in tiddler Etags 2024-03-20 18:52:54 +00:00
Jeremy Ruston 808b94468e Fix multipart form data POST test 2024-03-20 18:52:37 +00:00
Jeremy Ruston 60e6c8bcb2 Fix etag handling 2024-03-20 17:56:47 +00:00
Jeremy Ruston 891f0fd599 Fix page body class in static pages
So that we get the TW background colour
2024-03-20 17:55:50 +00:00
Jeremy Ruston 6154de0d2c Rename misnamed route 2024-03-20 15:53:49 +00:00
Jeremy Ruston cae9dbf5d1 Fix colours in static renderings 2024-03-20 15:51:58 +00:00
Jeremy Ruston ae8ef305fa Get rid of those annoying missing image icons
By adding a `?fallback=url` parameter to tiddler requests
2024-03-20 15:40:38 +00:00
Jeremy Ruston 9b3ca525ee Introduce multiwikiclient plugin
Routes are now rationalised, too.
2024-03-20 15:13:50 +00:00
Jeremy Ruston 38ee942d8f Don't do full debug logging during tests 2024-03-20 15:12:35 +00:00
Jeremy Ruston 957329d515 Add store directories to gitignore 2024-03-20 13:10:30 +00:00
Jeremy Ruston 6063256439 Create new static index route with ability to create/update bags and recipes
Also introduces a new /.system/filename route for stylesheets, scripts etc.
2024-03-20 09:44:52 +00:00
Jeremy Ruston 1c64646393 Fix quit command to work gracefully 2024-03-19 10:04:32 +00:00
Jeremy Ruston 259b3dca1b Add a new static index route 2024-03-18 22:26:24 +00:00
Jeremy Ruston 6a673e6aea Stop syncing state tiddlers to the admin wiki 2024-03-18 21:08:48 +00:00
Jeremy Ruston f606e33415 Stop using the existing core server infrastructure
Too much of a constraint now that we're starting work on client-server sync
2024-03-18 08:44:45 +00:00
Jeremy Ruston 09de91940e Typo 2024-03-17 19:30:22 +00:00
Jeremy Ruston 3d485f0706 Expose tiddler_ids in bag and recipe listings 2024-03-17 16:34:45 +00:00
Jeremy Ruston 7eaa9b8aec Database methods that mutate tables should return IDs 2024-03-17 15:06:36 +00:00
Jeremy Ruston faa4b9700a More consistent variable naming 2024-03-17 14:54:06 +00:00
Jeremy Ruston dea739ff07 Introduce delete markers for deleted tiddlers 2024-03-17 14:18:47 +00:00
Jeremy Ruston 69cc45bf5c Refactor the database engine specific code 2024-03-17 13:27:00 +00:00
Jeremy Ruston 347aa4d546 Tests should cover tiddler_ids 2024-03-17 13:07:24 +00:00
Jeremy Ruston b4855b25c4 Merge branch 'master' into multi-wiki-support 2024-03-16 22:06:02 +00:00
Jeremy Ruston d518675e03 Get rid of some console.logs 2024-03-15 16:49:24 +00:00
Jeremy Ruston 9b59ae2b73 Add an HTTP test for multipart form data upload 2024-03-15 16:40:22 +00:00
Jeremy Ruston f67573315e POST /wiki/:bag_name/bags/:bag_name/tiddlers/ should also return JSON 2024-03-15 16:39:59 +00:00
Jeremy Ruston 501f57499e Update readme 2024-03-14 12:25:11 +00:00
Jeremy Ruston 2916cb6fd9 Clarify comment
Thanks @pmario
2024-03-14 11:55:55 +00:00
Jeremy Ruston e553539b2a Clarify that process.exit() is a hack 2024-03-13 22:12:41 +00:00
Jeremy Ruston 3da773c27f Add HTTP tests to npm test command 2024-03-13 18:39:42 +00:00
Jeremy Ruston b923be5e94 Merge branch 'master' into multi-wiki-support 2024-03-13 18:03:35 +00:00
Jeremy Ruston c9ab184c65 TODOs before merging 2024-03-12 21:32:22 +00:00
Jeremy Ruston bc45a16f40 Fix readme build 2024-03-12 21:21:49 +00:00
Jeremy Ruston 8b6642b56d Update the root readme that is visible on GitHub 2024-03-12 17:33:07 +00:00
Jeremy Ruston d6807cb471 Update MWS plugin readme 2024-03-12 17:32:54 +00:00
Jeremy Ruston f9064428c5 Add npm start command 2024-03-12 17:32:35 +00:00
Jeremy Ruston a443e5f0ad Add new command to test local or remote server via HTTP 2024-03-11 21:52:40 +00:00
Jeremy Ruston 24413c53dd The listen command shouldn't return until the server is listening 2024-03-11 21:45:27 +00:00
Jeremy Ruston 8b5c3746f8 Refactor command module filenames 2024-03-11 09:40:40 +00:00
Jeremy Ruston 9df625c44d Reorganise JS modules into subdirectories 2024-03-11 09:10:01 +00:00
Rob Hoelz 54ff0446c6 MWS: store level tests (#8021)
* Fix a few typos

The "database instead of store" change isn't a typo fix, per se, but
these tests are testing the lower-level database layer, and I'm about
to introduce some tests for the higher-level store layer, so I want to
avoid any confusion in the test names

* Start on SQL store-level tests

* Add some tests for createBag

* Add test for getBagTiddler and getBagTiddlers

* Add basic recipe tests

* Add a test for saving a tiddler within a recipe

* Add store test for deleteTiddler
2024-03-11 09:01:43 +00:00
Jeremy Ruston 0f5dfb89ad Refactor multipart form handling for more reusability 2024-03-10 20:20:06 +00:00
Jeremy Ruston e3b27768d2 Update comment 2024-03-10 20:19:41 +00:00
Jeremy Ruston 580283433e Remove unneeded highlight plugin from multiwikiserver edition 2024-03-10 18:07:02 +00:00
Jeremy Ruston f4ac2b92e7 Remove unneeded plugins from tw5.com edition 2024-03-10 18:06:44 +00:00
Jeremy Ruston e35584843d Fix mws-save-archive command name 2024-03-10 18:06:26 +00:00
Jeremy Ruston 3335e87ef4 Remove unneeded option 2024-03-10 17:49:52 +00:00
Jeremy Ruston abde67e5df MWS: Add support for large tiddlers to be stored as attachment files
Fixes #8022
2024-03-10 17:45:33 +00:00
Jeremy Ruston 2ba3643a0c Merge branch 'master' into multi-wiki-support 2024-03-07 08:26:10 +00:00
Jeremy Ruston 89ae2012c7 Merge branch 'master' into multi-wiki-support 2024-03-04 14:14:06 +00:00
Jeremy Ruston 8a209d643f Fix typo 2024-03-01 18:58:17 +00:00
Jeremy Ruston 1a28ec7ea4 Rename upload manager to multipart form manager 2024-02-28 18:36:19 +00:00
Jeremy Ruston 5fe41fc896 Reorder test tiddlers 2024-02-28 18:19:22 +00:00
Jeremy Ruston 4f9ff1ae81 Missed closing tag 2024-02-28 18:19:01 +00:00
Jeremy Ruston d97ddf1eec Merge branch 'master' into multi-wiki-support 2024-02-28 18:04:45 +00:00
Jeremy Ruston 9facf4a067 Fix incorrect encoding of description when creating bags and recipes 2024-02-28 17:46:19 +00:00
Rob Hoelz de4fe132a7 Unconditionally decrement transaction depth (#8008)
…otherwise we may end up in a situation where we're always stuck in an
"already in a transaction" state and often neglect to actually enter a
real transaction!
2024-02-26 11:02:55 +00:00
Jeremy Ruston d7d0733177 Remove accidentally committed database file
Thanks @hoelzro
2024-02-25 22:27:36 +00:00
Jeremy Ruston e614e291a2 Default to better-sqlite3 2024-02-25 18:08:59 +00:00
Jeremy Ruston dd9a3bfeeb Re-enable loading the tw5.com docs tiddlers 2024-02-25 18:04:54 +00:00
Jeremy Ruston 83229ace63 Add a config tiddler for selecting the SQLite engine 2024-02-25 18:04:43 +00:00
Jeremy Ruston 6724fa804b Run the tests on both node-sqlite3-wasm and better-sqlite3 2024-02-25 17:58:53 +00:00
Jeremy Ruston 630b98520f Add note about transaction handling 2024-02-25 09:48:24 +00:00
Jeremy Ruston 1c0341de51 Merge branch 'master' into multi-wiki-support 2024-02-24 09:38:42 +00:00
Jeremy Ruston d5aa74d9af Improve manual transaction handling
Thanks @hoelzro
2024-02-24 09:31:59 +00:00
Jeremy Ruston 343cc33bbe Add a test tiddler with emoji title
Useful for testing
2024-02-24 09:30:20 +00:00
Jeremy Ruston b1edbed6a5 Fix bag static HTML to show emoji correctly 2024-02-23 17:47:00 +00:00
Jeremy Ruston 066e553f84 Introduce command to load tiddler folders into a bag 2024-02-23 12:51:29 +00:00
Jeremy Ruston 61b54125be Rename mws-load|save to mws-load|save-archive 2024-02-23 12:51:07 +00:00
Jeremy Ruston 3276703edd Fix failing tests 2024-02-23 09:30:12 +00:00
Jeremy Ruston f9265169fd Commands to load and save tiddlers, bags and recipes as a JSON archive
@linonetwo the resulting archive should be suitable for storing in git
2024-02-23 09:27:53 +00:00
Jeremy Ruston 2361880c45 createBag should optionally set access control data 2024-02-23 09:26:45 +00:00
Jeremy Ruston 3ad87df154 Allow wiki engine to be configured 2024-02-22 17:58:30 +00:00
Jeremy Ruston 3c58788e37 Merge branch 'master' into multi-wiki-support 2024-02-22 17:39:51 +00:00
Jeremy Ruston 310b5f058a Whitespace 2024-02-22 12:13:53 +00:00
Jeremy Ruston a33705e348 Fix error
It appears that not all statements can be finalized.
2024-02-22 12:09:36 +00:00
Jeremy Ruston 3fca82321e MWS: Add support for node-sqlite-wasm alongside better-sqlite3 (#7996)
* Switch from better-sqlite3 to node-sqlite3-wasm

Seems to be slower, but might make cloud deployments easier by not having any binary dependencies

* More logging

* Temporarily use a memory database

We will make this configurable

* Revert "More logging"

* Resume loading demo tiddlers

* Cache prepared statements

Gives a 20% reduction in startup time on my machine

* Some more logging

* Update package-lock

* More logging

* Route regexps should allow for proxies that automatically decode URLs

Astonishingly, Azure does this

* Go back to a file-based database

* Less logging

* Update package-lock.json

* Simplify startup by not loading the docs edition

* Tiddler database layer should mark statements as having been removed

* Re-introduce better-sqlite3

* Make the SQLite provider be switchable

* Support switchable SQL engines

I am not intending to make this a long term feature. We will choose one engine and stick with it until we choose to change to another.

* Adjust dependency versions

* Setting up default engine

* Make transaction handling compatible with node-sqlite3-wasm

https://github.com/tndrle/node-sqlite3-wasm doesn't have transaction support so I've tried to implement it using SQL statements directly.

@hoelzro do you think this is right? Should we be rolling back the transaction in the finally clause? It would be nice to have tests in this area...

I looked at better-sqlite3's implementation - https://github.com/WiseLibs/better-sqlite3/blob/master/lib/methods/transaction.js

* Default to better-sqlite3 for compatibility after merging
2024-02-22 11:57:41 +00:00
Rob Hoelz 790f431df0 MWS: Use transactions when modifying multiple resources (#7991)
* Use transactions when modifying multiple resources

This prevents partial changes from entering the database, and also
nets a nice speed-up.

* Keep track of transaction depth

…so we could someday potentially leverage SQL implementations that don't
implement nested transactions
2024-02-22 10:48:39 +00:00
Jeremy Ruston 0d22bf8418 Update to latest better-sqlite3 2024-02-22 10:47:08 +00:00
Jeremy Ruston 1eecfb6b3a Less logging 2024-02-21 17:55:13 +00:00
Jeremy Ruston b8c1c6c8de Allow backslashes in trailing API path components
To make us more tolerant of proxies that "helpfully" decodeuricomponent for us (looking at you Azure)
2024-02-21 17:54:56 +00:00
Jeremy Ruston 6503fb4a04 Simple performance logging 2024-02-20 09:33:39 +00:00
Jeremy Ruston bab14b7053 Logging 2024-02-19 16:29:14 +00:00
Jeremy Ruston 2d4b3341f6 Merge branch 'master' into multi-wiki-support 2024-02-19 09:55:57 +00:00
Jeremy Ruston 6f8a3b9261 mws-load command: more validation tiddler files 2024-02-16 16:41:39 +00:00
Jeremy Ruston 8edefffbc5 WIP: Support for streaming multipart form data to the file system 2024-02-16 16:02:40 +00:00
Jeremy Ruston 59b425fd5c Update to better-sqlite3 v9.4.1 2024-02-16 16:00:46 +00:00
Jeremy Ruston f2267e2af0 Merge branch 'master' into multi-wiki-support 2024-02-16 15:58:08 +00:00
Jeremy Ruston c26acfdb42 Add NOT NULL constraint to all columns
Thanks @hoelzro
2024-02-05 16:15:35 +00:00
Jeremy Ruston f925f036c9 Introduce $tw.mws for MWS globals 2024-02-05 14:49:08 +00:00
Jeremy Ruston 2c810faeeb Add barebones support for timing HTTP response generation times 2024-02-02 15:42:47 +00:00
Jeremy Ruston 6675358e85 WIP: Add a multipart/form-data convenience function
This is the start of adding support for large attachments.

We have a new endpoint for POSTing tiddler data. The idea is that it will take any kind of data and figure out how to extract tiddlers from the upload and save them in the nominated bag.

The next step is to move the attachment files into a special folder and reference them from the database so that we can construct _canonical_uris for them.
2024-02-02 15:42:02 +00:00
Jeremy Ruston 262a730534 Move the database file into a "store" directory inside the wiki folder 2024-01-29 18:11:50 +00:00
Jeremy Ruston 4b6872aa42 Fix typo 2024-01-29 08:29:26 +00:00
Jeremy Ruston 3283d38867 First draft of a command to read tiddlers, bags and recipes from an archive
The archive format is a legacy format that I used with Xememex, and will need to be updated to iron out the wrinkles
2024-01-28 21:27:12 +00:00
Jeremy Ruston 4204ff367e A few more tests 2024-01-28 17:11:53 +00:00
Jeremy Ruston 51e646690c Allow tilde character in bag and recipe names 2024-01-28 17:11:44 +00:00
Jeremy Ruston 85607f7846 Query fixes 2024-01-28 17:11:23 +00:00
Jeremy Ruston 41a5bcc3a1 Fix canonical URI handling 2024-01-26 15:48:39 +00:00
Jeremy Ruston 84c8a9be9b Fix typo 2024-01-26 15:01:07 +00:00
Jeremy Ruston 62b2fe3e2f Add an error when creating a recipe with no bags 2024-01-26 14:42:43 +00:00
Jeremy Ruston f5fdd79c7f Refresh when creating bags and recipes to get the change instantly 2024-01-26 14:42:17 +00:00
Jeremy Ruston 14752ccb0c Missing comma 2024-01-26 14:04:54 +00:00
Jeremy Ruston 541c166863 Error handling for bag and recipe handling 2024-01-26 14:03:32 +00:00
Jeremy Ruston 270f62bbb2 Merge branch 'master' into multi-wiki-support 2024-01-26 13:38:02 +00:00
Jeremy Ruston 8290d853c9 Merge branch 'master' into multi-wiki-support 2024-01-26 12:54:40 +00:00
Jeremy Ruston b0a67300cc Add support for bag descriptions, validate bags and recipes, and complete the UI for creating bags and recipes
Styling to come
2024-01-24 22:24:24 +00:00
Jeremy Ruston 0b9749f3a4 Add favicon to bag listing page 2024-01-24 20:58:57 +00:00
Jeremy Ruston 3ad3e19392 Merge branch 'master' into multi-wiki-support 2024-01-24 16:21:58 +00:00
Jeremy Ruston ed71adac7e Merge branch 'master' into multi-wiki-support 2024-01-24 12:54:39 +00:00
Jeremy Ruston 8d95c92bfb Use $:/state/ tiddler for server entity state tiddlers 2024-01-24 10:56:23 +00:00
Jeremy Ruston 41ab94978f Renaming tiddlers for consistency 2024-01-24 10:55:14 +00:00
Jeremy Ruston 26e198b7d8 First pass at MWS icon 2024-01-23 22:05:58 +00:00
Jeremy Ruston d16746ab38 Extend image widget to support alternate content if a remote image fails to load
...and use it to add an image for bags/recipes that do not have a $:/favicon.ico
2024-01-23 22:05:22 +00:00
Jeremy Ruston 627c3e20cc Add docs bags 2024-01-23 16:53:12 +00:00
Jeremy Ruston 4d42d4a190 Escape less than sign
Otherwise tiddlers containing `</script>` will break TiddlyWiki
2024-01-23 16:52:49 +00:00
Jeremy Ruston ff184822ca Don't wikify recipe descriptions 2024-01-23 16:52:11 +00:00
Jeremy Ruston ddbd6d1e82 Fix favicon aspect ratio 2024-01-23 16:51:57 +00:00
Jeremy Ruston f6d6478944 Add support for recipe descriptions 2024-01-23 14:29:50 +00:00
Jeremy Ruston 138c7f2665 Don't use the filesystem plugin
Otherwise changes to _multiwikiserver/ tiddlers are saved back to the file system

Perhaps it would work better to configure the wiki to sync state tiddlers to the browser.
2024-01-23 14:29:08 +00:00
Jeremy Ruston 239ace0c07 Avoid clients of sqlTiddlerStore having to call updateAdminWIki() explicitly 2024-01-23 12:53:06 +00:00
Jeremy Ruston c1312100aa Admin UI styling 2024-01-23 12:52:40 +00:00
Jeremy Ruston e343eccdc3 Refactor _canonical_uri handling out of route handlers 2024-01-23 10:51:12 +00:00
Jeremy Ruston da5b316358 Split SqlTiddlerStore into SqlTiddlerStore and SqlTiddlerDatabase
The motivation is to encapsulate knowledge of the SQL queries
2024-01-22 22:08:55 +00:00
Jeremy Ruston dc8692044c Use SQLite's AUTOINCREMENT to give us tiddler version identifiers
This commit fixes sync within hosted wikis
2024-01-21 18:18:29 +00:00
Jeremy Ruston 4f9ba11489 Update to newest better-sqlite3 2024-01-21 18:17:23 +00:00
Jeremy Ruston f7914db019 Add bag and recipe favicons to dashboard 2024-01-20 21:50:12 +00:00
Jeremy Ruston 11ecaff7db Fix typo 2024-01-20 21:48:40 +00:00
Jeremy Ruston d832bbcc70 Order the results of getRecipeTiddlers 2024-01-20 21:48:33 +00:00
Jeremy Ruston 59aed49e98 Make getRecipeTiddlers return the bagname as well 2024-01-20 20:22:46 +00:00
Jeremy Ruston e9f83ca735 Add missing LIMIT 1 2024-01-19 22:03:07 +00:00
Jeremy Ruston afa9ad3cde Update store.getRecipeTiddler to also return the bag from which the tiddler came 2024-01-19 20:35:47 +00:00
Jeremy Ruston 01d29ed11e get bag tiddler and put recipe tiddler should return the bag name 2024-01-19 20:12:29 +00:00
Jeremy Ruston 8f9ae7e4d5 Clarify method name 2024-01-19 19:52:57 +00:00
Jeremy Ruston 70b048f356 Fix bag links 2024-01-19 19:36:36 +00:00
Jeremy Ruston 5fddd3b104 Add support for retrieving tiddlers from bags 2024-01-19 19:33:58 +00:00
Jeremy Ruston 02afbb4000 Rename some of the routes more logically 2024-01-19 19:27:54 +00:00
Jeremy Ruston 54432485e7 Add an HTML view of bag listings 2024-01-19 19:25:58 +00:00
Jeremy Ruston 26ede2839b Add support for _canonical_uri tiddlers
Currently hard wired to kick in for tiddlers over 10MB (in base64 representation for binary tiddlers)
2024-01-19 14:46:21 +00:00
Jeremy Ruston 4b0df1a7ae Basic support for creating bags and recipes
Cannot yet specify the bags for the new recipe
2024-01-19 11:03:58 +00:00
Jeremy Ruston 9767e7d3b7 Update entity state tiddlers on startup to read bag and recipe info 2024-01-19 11:03:27 +00:00
Jeremy Ruston 4133e7d6d6 Stream wiki generation
Avoids "string too long" errors when working with big tiddlers (>100MB)
2024-01-19 10:52:12 +00:00
Jeremy Ruston 4f37355a9f Tests should use a dummy admin wiki 2024-01-19 10:28:04 +00:00
Jeremy Ruston 82fae45656 Admin styling 2024-01-18 21:48:09 +00:00
Jeremy Ruston 2f09c32d2d Fix getTiddler query 2024-01-18 21:47:57 +00:00
Jeremy Ruston a16338ce11 Merge branch 'master' into multi-wiki-support 2024-01-18 16:43:43 +00:00
Jeremy Ruston 50d0b1412d Fix CI tests 2024-01-18 09:02:41 +00:00
Jeremy Ruston 8941bd1747 Server extension framework
May not actually be needed
2024-01-17 22:42:01 +00:00
Jeremy Ruston 615dc0c4a3 First pass at admin user interface 2024-01-17 22:41:41 +00:00
Jeremy Ruston 1fb8b2e279 Fix broken test 2024-01-05 15:45:40 +00:00
Jeremy Ruston 0799177cf4 Add another recipe, improve docs 2024-01-05 15:40:39 +00:00
Jeremy Ruston 1eed61397b Fix create recipe SQL bug 2024-01-05 15:37:48 +00:00
Jeremy Ruston 3f1f7c7ef7 Remove debugging code 2024-01-05 11:08:33 +00:00
Jeremy Ruston 8543dda4aa Fix broken test 2024-01-05 11:01:10 +00:00
Jeremy Ruston 68a89b615d Use a persistent disk-based database 2024-01-05 10:58:07 +00:00
Jeremy Ruston e9d3f67c5c Add new multiwikiserver edition 2024-01-03 16:47:20 +00:00
Jeremy Ruston a980390870 Implement APIs for client wikis to sync with the server
It is now possible to create and edit tiddlers, using the existing tiddlywebadaptor syncing mechanism. There are a lot of hacks and lumpiness to make things compatible, so I think I will end up with an independent implementation
2024-01-03 16:27:13 +00:00
Jeremy Ruston 299781bdba Update docs 2024-01-02 21:47:08 +00:00
Jeremy Ruston f42d3e0536 Update usage instructions 2024-01-02 21:41:25 +00:00
Jeremy Ruston 993eb5c90d Tests need npm install 2024-01-02 21:41:06 +00:00
Jeremy Ruston f8f8319324 Add dependencies to package.json
This is needed in order for our CI to be able to run the tests
2024-01-02 14:47:32 +00:00
Jeremy Ruston 12d84c43c9 Initial Commit 2024-01-02 14:39:14 +00:00
171 changed files with 11869 additions and 13 deletions
+12
View File
@@ -4,6 +4,7 @@ on:
branches:
- master
- tiddlywiki-com
- multi-wiki-support
env:
NODE_VERSION: "22"
jobs:
@@ -82,3 +83,14 @@ 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
View File
@@ -5,7 +5,9 @@
tmp/
output/
node_modules/
store/
/test-results/
/playwright-report/
/playwright/.cache/
$__StoryList.tid
/editions/test/test-store/*
+4
View File
@@ -0,0 +1,4 @@
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`)
+97
View File
@@ -0,0 +1,97 @@
#!/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
View File
@@ -2,6 +2,8 @@
# test TiddlyWiki5 for tiddlywiki.com
npm install
node ./tiddlywiki.js \
./editions/test \
--verbose \
+1
View File
@@ -10,6 +10,7 @@ fi
# tw5.com readmes
node $TW5_BUILD_TIDDLYWIKI \
+plugins/tiddlywiki/multiwikiserver \
editions/tw5.com \
--verbose \
--output . \
+7
View File
@@ -38,6 +38,13 @@ 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
*/
+6 -2
View File
@@ -16,7 +16,7 @@ var Server = require("$:/core/modules/server/server.js").Server;
exports.info = {
name: "listen",
synchronous: true,
synchronous: false,
namedParameterMode: true,
mandatoryParameters: []
};
@@ -38,7 +38,11 @@ Command.prototype.execute = function() {
wiki: this.commander.wiki,
variables: self.params
});
var nodeServer = this.server.listen();
var nodeServer = this.server.listen(null,null,null,{
callback: function() {
self.callback();
}
});
$tw.hooks.invokeHook("th-server-command-post-start",this.server,nodeServer,"tiddlywiki");
return null;
};
+37
View File
@@ -0,0 +1,37 @@
/*\
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;
})();
+5
View File
@@ -364,6 +364,11 @@ 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");
+9 -1
View File
@@ -257,7 +257,11 @@ 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);
this.wiki.addTiddler(tiddler);
// Only save the tiddler if it has changed
var existingTiddler = this.wiki.getTiddler(tiddlerFields.title);
if(!existingTiddler || !existingTiddler.isEqual(tiddler)) {
this.wiki.addTiddler(tiddler);
}
// Save the tiddler revision and changeCount details
this.tiddlerInfo[tiddlerFields.title] = {
revision: this.getTiddlerRevision(tiddlerFields.title),
@@ -556,6 +560,7 @@ SaveTiddlerTask.prototype.run = function(callback) {
// Invoke the callback
callback(null);
},{
syncer: self.syncer,
tiddlerInfo: self.syncer.tiddlerInfo[self.title]
});
} else {
@@ -586,6 +591,7 @@ DeleteTiddlerTask.prototype.run = function(callback) {
// Invoke the callback
callback(null);
},{
syncer: self.syncer,
tiddlerInfo: self.syncer.tiddlerInfo[this.title]
});
};
@@ -614,6 +620,8 @@ LoadTiddlerTask.prototype.run = function(callback) {
}
// Invoke the callback
callback(null);
},{
syncer: self.syncer
});
};
+1 -1
View File
@@ -163,4 +163,4 @@ ImageWidget.prototype.refresh = function(changedTiddlers) {
exports.image = ImageWidget;
})();
})();
+10 -1
View File
@@ -44,7 +44,8 @@ NavigatorWidget.prototype.render = function(parent,nextSibling) {
{type: "tm-fold-tiddler", handler: "handleFoldTiddlerEvent"},
{type: "tm-fold-other-tiddlers", handler: "handleFoldOtherTiddlersEvent"},
{type: "tm-fold-all-tiddlers", handler: "handleFoldAllTiddlersEvent"},
{type: "tm-unfold-all-tiddlers", handler: "handleUnfoldAllTiddlersEvent"}
{type: "tm-unfold-all-tiddlers", handler: "handleUnfoldAllTiddlersEvent"},
{type: "tm-manage-acl", handler: "handleManageACLTiddlersEvent"}
]);
this.parentDomNode = parent;
this.computeAttributes();
@@ -635,6 +636,14 @@ NavigatorWidget.prototype.handleUnfoldAllTiddlersEvent = function(event) {
});
};
NavigatorWidget.prototype.handleManageACLTiddlersEvent = function() {
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
};
exports.navigator = NavigatorWidget;
})();
@@ -0,0 +1,41 @@
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
@@ -0,0 +1,15 @@
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">>
@@ -0,0 +1,6 @@
title: $:/DefaultTiddlers
HelloThere
Installation
Usage
Reference
@@ -0,0 +1,17 @@
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.
Planned features include:
* Hosting multiple wikis at once, using the [[Bags and Recipes]] mechanism for sharing data between them
* Robust 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.
@@ -0,0 +1,19 @@
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">>
@@ -0,0 +1,17 @@
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">>
# 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 in the future with newer changes will require re-downloading the code, taking care not to lose any changes you might have made.
@@ -0,0 +1,230 @@
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
* More permissive role takes precedence in conflicts
* Guest access is overridden by recipe ACLs
* System automatically enforces most restrictive access when conflicts occur
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 /)
@@ -0,0 +1,19 @@
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">>
@@ -0,0 +1,4 @@
title: Troubleshooting
tags: TableOfContents
<<list-links "[tag[Troubleshooting]]">>
+13
View File
@@ -0,0 +1,13 @@
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.

After

Width:  |  Height:  |  Size: 173 KiB

@@ -0,0 +1,4 @@
title: MWS Banner.png
type: image/png
tags: picture
alt-text: Banner for the new Multi Wiki Server plugin for TiddlyWiki
@@ -0,0 +1,16 @@
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>
@@ -0,0 +1,3 @@
title: $:/SiteDomain
mws.tiddlywiki.com
@@ -0,0 +1,3 @@
title: $:/SitePreviewUrl
https://tiddlywiki.com/images/Introduction%2520Video%2520Thumbnail.jpg
@@ -0,0 +1,3 @@
title: $:/SiteSubtitle
~TiddlyWiki for the People
@@ -0,0 +1,4 @@
title: $:/SiteTitle
type: text/vnd.tiddlywiki
~TiddlyWiki ~MultiWikiServer
@@ -0,0 +1,3 @@
title: $:/SiteUrl
https://mws.tiddlywiki.com/
@@ -0,0 +1,129 @@
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>
@@ -0,0 +1,3 @@
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>
@@ -0,0 +1,49 @@
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;
}
@@ -0,0 +1,9 @@
tags: $:/tags/SideBar
title: TableOfContents
list-before:
<div class="tc-table-of-contents">
<<toc-selective-expandable 'TableOfContents'>>
</div>
@@ -0,0 +1,3 @@
title: $:/config/DefaultSidebarTab
TableOfContents
Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

@@ -0,0 +1,2 @@
title: $:/favicon.ico
type: image/png
@@ -0,0 +1,11 @@
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
@@ -0,0 +1,149 @@
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
@@ -0,0 +1,3 @@
title: $:/palette
$:/palettes/MWS
@@ -0,0 +1,3 @@
title: $:/themes/tiddlywiki/vanilla/options/sidebarlayout
fluid-fixed
@@ -0,0 +1,18 @@
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>
+26
View File
@@ -0,0 +1,26 @@
{
"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
}
}
@@ -0,0 +1,2 @@
title: $:/config/MultiWikiServer/Engine
text: better
+38
View File
@@ -0,0 +1,38 @@
{
"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/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 create tiddlers",
"--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"
]
}
}
+2 -2
View File
@@ -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(page.locator('.tc-site-title'), "Expected correct page title to verify the test page was loaded").toHaveText('TiddlyWiki5');
await expect(title, "Expected correct page title to verify the test page was loaded").toContain('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});
+1
View File
@@ -2,6 +2,7 @@
"description": "TiddlyWiki core tests",
"plugins": [
"tiddlywiki/jasmine",
"tiddlywiki/multiwikiserver",
"tiddlywiki/geospatial"
],
"themes": [
+17 -1
View File
@@ -6,7 +6,23 @@ type: text/vnd.tiddlywiki
\define tv-wikilink-template() https://tiddlywiki.com/static/$uri_doubleencoded$.html
\import [subfilter{$:/core/config/GlobalImportFilter}]
! Welcome
---
! ~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 to TiddlyWiki, a non-linear personal web notebook that anyone can use and keep forever, independently of any corporation.
+1474
View File
File diff suppressed because it is too large Load Diff
+12 -4
View File
@@ -24,18 +24,26 @@
"wiki"
],
"devDependencies": {
"@playwright/test": "^1.47.2",
"eslint": "^9.12.0",
"@eslint/js": "^9.12.0"
"@eslint/js": "^9.12.0",
"playwright": "^1.47.2"
},
"bundleDependencies": [],
"license": "BSD",
"engines": {
"node": ">=0.8.2"
},
"scripts": {
"dev": "node ./tiddlywiki.js ./editions/tw5.com-server --listen",
"test": "node ./tiddlywiki.js ./editions/test --verbose --version --build index",
"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",
"lint:fix": "eslint . --fix",
"lint": "eslint ."
},
"dependencies": {
"better-sqlite3": "^11.5.0",
"node-sqlite3-wasm": "^0.8.25"
}
}
@@ -0,0 +1,9 @@
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.
@@ -0,0 +1,3 @@
title: $:/config/SaveWikiButton/Template
$:/plugins/tiddlywiki/multiwikiclient/save/offline
@@ -0,0 +1,2 @@
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]]
@@ -0,0 +1,2 @@
title: $:/config/Server/ExternalFilters/[all[tiddlers]] -[[$:/isEncrypted]] -[prefix[$:/temp/]] -[prefix[$:/status/]] -[[$:/boot/boot.js]] -[[$:/boot/bootprefix.js]] -[[$:/library/sjcl.js]] -[[$:/core]]
text: yes
@@ -0,0 +1,2 @@
title: $:/config/multiwikiclient/use-server-sent-events
text: no
@@ -0,0 +1,7 @@
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]].
@@ -0,0 +1,4 @@
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>
@@ -0,0 +1,8 @@
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>
@@ -0,0 +1,16 @@
title: $:/core/ui/Buttons/manage-acl
tags: $:/tags/PageControls
caption: {{$:/core/images/globe}} manage access control
description: Manage access control configuration for this wiki
list-after: $:/core/ui/Buttons/encryption
\whitespace trim
<$button tooltip="Manage access control configuration for this wiki" aria-label="manage access control" class=<<tv-config-toolbar-class>>>
<$action-managetiddler tiddler=<<currentTiddler>>/>
{{$:/core/images/globe}}
<%if [<tv-config-toolbar-text>match[yes]] %>
<span class="tc-btn-text">
<$text text={{$:/language/Buttons/ManageACL/Caption}}/>
</span>
<%endif%>
</$button>
@@ -0,0 +1,65 @@
/*\
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;
})();
@@ -0,0 +1,399 @@
/*\
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;
}
})();
@@ -0,0 +1,8 @@
{
"title": "$:/plugins/tiddlywiki/multiwikiclient",
"name": "MultiWikiClient",
"description": "Synchronise changes from the browser to TiddlyWiki ~MultiWikiServer",
"list": "readme",
"plugin-priority": 10,
"stability": "STABILITY_1_EXPERIMENTAL"
}
@@ -0,0 +1,8 @@
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.
@@ -0,0 +1,27 @@
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>>
@@ -0,0 +1,7 @@
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}}
@@ -0,0 +1,26 @@
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>
@@ -0,0 +1,9 @@
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%>
@@ -0,0 +1,44 @@
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);
}
}
@@ -0,0 +1,6 @@
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>
@@ -0,0 +1,9 @@
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>
@@ -0,0 +1,8 @@
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>
@@ -0,0 +1,8 @@
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>
@@ -0,0 +1,9 @@
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>
@@ -0,0 +1,9 @@
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>
@@ -0,0 +1,2 @@
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
@@ -0,0 +1,43 @@
/*\
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.createOrUpdateUserSession(userId, sessionId);
return sessionId;
};
exports.Authenticator = Authenticator;
})();
@@ -0,0 +1,19 @@
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>
@@ -0,0 +1,7 @@
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>
@@ -0,0 +1,8 @@
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>
@@ -0,0 +1,3 @@
title: $:/plugins/tiddlywiki/multiwikiserver/auth/form/login/head
<title>TiddlyWiki Login</title>
@@ -0,0 +1,3 @@
title: $:/plugins/tiddlywiki/multiwikiserver/auth/form/login/header
<h1>TiddlyWiki Login</h1>
@@ -0,0 +1,48 @@
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>
@@ -0,0 +1,4 @@
title: $:/config/MultiWikiServer/AllowAnonymousReads
text: no
description: Controls whether anonymous users can read wiki content
type: text/plain
@@ -0,0 +1,4 @@
title: $:/config/MultiWikiServer/AllowAnonymousWrites
text: no
description: Controls whether anonymous users can write to the wiki
type: text/plain
@@ -0,0 +1,2 @@
title: $:/config/MultiWikiServer/AttachmentSizeLimit
text: 204800
@@ -0,0 +1,2 @@
title: $:/config/MultiWikiServer/EnableAttachments
text: yes
@@ -0,0 +1,5 @@
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.
@@ -0,0 +1,4 @@
title: $:/plugins/tiddlywiki/multiwikiserver/icon
type: image/svg+xml
<svg style="clip-rule:evenodd;fill-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2" version="1.1" viewBox="0 0 635 635" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><path d="m336 19.7a16 16 0 0 1 20.6-9.59l132 48a16 16 0 0 1 9.6 20.6l-3.1 8.51a16 16 0 0 0 23.1 19.4l7.85-4.53a16 16 0 0 1 21.9 5.87l70.2 122a16.1 16.1 0 0 1-5.87 21.9l-7.85 4.53a16.1 16.1 0 0 0 5.24 29.7l8.92 1.57a16 16 0 0 1 13 18.6l-24.4 138a16 16 0 0 1-18.6 13l-8.92-1.58a16.1 16.1 0 0 0-15.1 26.1l5.82 6.94a16 16 0 0 1-1.97 22.6l-108 90.2a16 16 0 0 1-22.6-1.97l-5.82-6.93a16 16 0 0 0-28.3 10.3v9.05c0 8.87-7.19 16-16 16h-140a16 16 0 0 1-16-16v-9.05a16.1 16.1 0 0 0-28.3-10.3l-5.82 6.93a16 16 0 0 1-22.6 1.97l-108-90.2a16 16 0 0 1-1.97-22.6l5.82-6.94a16.1 16.1 0 0 0-15.1-26.1l-8.92 1.58a16.1 16.1 0 0 1-18.6-13l-24.4-138a16 16 0 0 1 13-18.6l8.92-1.57a16 16 0 0 0 5.24-29.7l-7.85-4.53a16.1 16.1 0 0 1-5.87-21.9l70.2-122a16 16 0 0 1 21.9-5.87l7.85 4.53a16.1 16.1 0 0 0 23.1-19.4l-3.1-8.51a16 16 0 0 1 9.6-20.6l132-48a16 16 0 0 1 20.6 9.59l3.1 8.51a16.1 16.1 0 0 0 30.2 0zm108 459 4.29-0.55c3.15-1.16 6.11-2.22 8.04-5.19 6.32-9-9.97-12.6-15.8-22.6-5.83-9.97-29.9-64.7-11.7-80 18.2-15.3 37.9-21.6 45.5-36 3.26-6.47 6.65-13 7.5-20.3 4.44 3.61 7.92 6.99 13.9 7.19 15.8 0.972 29.4-18 36.5-36 3.77-11 6.82-22.7 6.1-33.5-1.93-20.8 0.363-43.3 16.8-56.5 0.157-0.177 0.432-0.265 0.481-0.53-16.7 8.26-32.6 18.9-51.5 21.6-10.4-3.11-20.9-5.91-32-5.48l-0.0196 0.01c-5.42-6.92-12.7-19.9-22.2-21.4-16.2 5.68-16.7 30.2-23.5 63.5-10.5-2.82-21.3-4.79-32.2-5.29-15.1-0.972-39.6 3.65-56.9 3.16-17.3-0.481-51.2-15.4-79.5-12.1-11.4 1.37-22.8 3.27-33.4 7.96-6.45 2.86-15.8 11.5-20.5 12.8-47.2 12.9-124 12.4-108-57.5 1.23-5.37 1.57-10.2-0.854-11.4-3.03-1.5-4.73 1.65-6.52 5.11-27.5 58.5 21.3 96.1 76.8 96.3 17.4-1.32 9.03-0.196 25.2-3.22l-0.412 2.24c-1.14 19.4 7.68 36.5 24.1 47-7.55 21.1-22.5 38-32.7 57.4 3.86 24.1 28.8 56.3 39.1 60.8 9.07 4.04 28.9 4.08 33.5-2.42 1.64-3.05 0.56-9.61-1.25-13 7.75 8.42 18.2 20.1 28.3 25.8 6.05 2.83 12.1 4.66 18.9 4.89l8.62-0.412c4.31-0.904 9.39-1.59 12.3-5.34 3.26-5.44 0.216-20.7-13.3-21.8-13.5-1.09-22.2-6.09-34.6-24.1-10.2-17.5 2.14-41.5 6.3-58.7 21.3 3.36 42.8 6.01 64.5 6.07 9.38-0.471 18.7-0.805 27.9-2.54 2.95 7.39 11.2 28 11.3 33.9 0.206 16.5-6.11 33-3.39 49.4 1.7 4.84 0.147 14 9.8 14.4 5.76 0.285 2.16 0.108 10.8 0.491l7.43-0.206c9.54 15.7 17.8 15.6 36.5 16.2zm-208-40.9-1.66-1.69c-4.84-5.98-17.3-16.4-15-26.1 1.95-7.17 5.6-13.8 8.47-20.6l-0.0982 0.707c-0.521 8.5-1.54 16.9 0.854 25.2 7.28 6.88 17.4 17.8 24.1 25.2-3.64-1.16-10.5-3.52-13.9-1.99z"/></svg>
@@ -0,0 +1,47 @@
/*\
title: $:/plugins/tiddlywiki/multiwikiserver/commands/mws-add-permission.js
type: application/javascript
module-type: command
Command to create a permission
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
exports.info = {
name: "mws-add-permission",
synchronous: false
};
var Command = function(params,commander,callback) {
this.params = params;
this.commander = commander;
this.callback = callback;
};
Command.prototype.execute = function() {
var self = this;
if(this.params.length < 2) {
return "Usage: --mws-add-permission <permission_name> <description>";
}
if(!$tw.mws || !$tw.mws.store || !$tw.mws.store.sqlTiddlerDatabase) {
return "Error: MultiWikiServer or SQL database not initialized.";
}
var permission_name = this.params[0];
var description = this.params[1];
$tw.mws.store.sqlTiddlerDatabase.createPermission(permission_name, description);
self.callback();
return null;
};
exports.Command = Command;
})();
@@ -0,0 +1,47 @@
/*\
title: $:/plugins/tiddlywiki/multiwikiserver/commands/mws-add-role.js
type: application/javascript
module-type: command
Command to create a role
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
exports.info = {
name: "mws-add-role",
synchronous: false
};
var Command = function(params,commander,callback) {
this.params = params;
this.commander = commander;
this.callback = callback;
};
Command.prototype.execute = function() {
var self = this;
if(this.params.length < 2) {
return "Usage: --mws-add-role <role_name> <description>";
}
if(!$tw.mws || !$tw.mws.store || !$tw.mws.store.sqlTiddlerDatabase) {
return "Error: MultiWikiServer or SQL database not initialized.";
}
var role_name = this.params[0];
var description = this.params[1];
$tw.mws.store.sqlTiddlerDatabase.createRole(role_name, description);
self.callback(null, "Role Created Successfully!");
return null;
};
exports.Command = Command;
})();
@@ -0,0 +1,56 @@
/*\
title: $:/plugins/tiddlywiki/multiwikiserver/commands/mws-add-user.js
type: application/javascript
module-type: command
Command to create users and grant permission
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
if($tw.node) {
var crypto = require("crypto");
}
exports.info = {
name: "mws-add-user",
synchronous: false
};
var Command = function(params,commander,callback) {
this.params = params;
this.commander = commander;
this.callback = callback;
};
Command.prototype.execute = function() {
var self = this;
if(this.params.length < 2) {
return "Usage: --mws-add-user <username> <password> [email]";
}
if(!$tw.mws || !$tw.mws.store || !$tw.mws.store.sqlTiddlerDatabase) {
return "Error: MultiWikiServer or SQL database not initialized.";
}
var username = this.params[0];
var password = this.params[1];
var email = this.params[2] || username + "@example.com";
var hashedPassword = crypto.createHash("sha256").update(password).digest("hex");
var user = $tw.mws.store.sqlTiddlerDatabase.getUserByUsername(username);
if(!user) {
$tw.mws.store.sqlTiddlerDatabase.createUser(username, email, hashedPassword);
console.log("User Account Created Successfully with username: " + username + " and password: " + password);
self.callback();
}
return null;
};
exports.Command = Command;
})();
@@ -0,0 +1,60 @@
/*\
title: $:/plugins/tiddlywiki/multiwikiserver/commands/mws-assign-role-permission.js
type: application/javascript
module-type: command
Command to assign permission to a role
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
exports.info = {
name: "mws-assign-role-permission",
synchronous: false
};
var Command = function(params,commander,callback) {
this.params = params;
this.commander = commander;
this.callback = callback;
};
Command.prototype.execute = function() {
var self = this;
if(this.params.length < 2) {
return "Usage: --mws-assign-role-permission <role_name> <permission_name>";
}
if(!$tw.mws || !$tw.mws.store || !$tw.mws.store.sqlTiddlerDatabase) {
return "Error: MultiWikiServer or SQL database not initialized.";
}
var role_name = this.params[0];
var permission_name = this.params[1];
var role = $tw.mws.store.sqlTiddlerDatabase.getRoleByName(role_name);
var permission = $tw.mws.store.sqlTiddlerDatabase.getPermissionByName(permission_name);
if(!role) {
return "Error: Unable to find Role: "+role_name;
}
if(!permission) {
return "Error: Unable to find Permission: "+permission_name;
}
var permission = $tw.mws.store.sqlTiddlerDatabase.getPermissionByName(permission_name);
$tw.mws.store.sqlTiddlerDatabase.addPermissionToRole(role.role_id, permission.permission_id);
self.callback();
return null;
};
exports.Command = Command;
})();
@@ -0,0 +1,59 @@
/*\
title: $:/plugins/tiddlywiki/multiwikiserver/commands/mws-assign-user-role.js
type: application/javascript
module-type: command
Command to assign a role to a user
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
exports.info = {
name: "mws-assign-user-role",
synchronous: false
};
var Command = function(params,commander,callback) {
this.params = params;
this.commander = commander;
this.callback = callback;
};
Command.prototype.execute = function() {
var self = this;
if(this.params.length < 2) {
return "Usage: --mws-assign-user-role <username> <role_name>";
}
if(!$tw.mws || !$tw.mws.store || !$tw.mws.store.sqlTiddlerDatabase) {
return "Error: MultiWikiServer or SQL database not initialized.";
}
var username = this.params[0];
var role_name = this.params[1];
var role = $tw.mws.store.sqlTiddlerDatabase.getRoleByName(role_name);
var user = $tw.mws.store.sqlTiddlerDatabase.getUserByUsername(username);
if(!role) {
return "Error: Unable to find Role: "+role_name;
}
if(!user) {
return "Error: Unable to find user with the username "+username;
}
$tw.mws.store.sqlTiddlerDatabase.addRoleToUser(user.user_id, role.role_id);
console.log(role_name+" role has been assigned to user with username "+username)
self.callback();
return null;
};
exports.Command = Command;
})();
@@ -0,0 +1,47 @@
/*\
title: $:/plugins/tiddlywiki/multiwikiserver/commands/mws-create-bag.js
type: application/javascript
module-type: command
Command to load archive of recipes, bags and tiddlers from a directory
--mws-create-bag <name> <description>
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
exports.info = {
name: "mws-create-bag",
synchronous: true
};
var Command = function(params,commander,callback) {
this.params = params;
this.commander = commander;
this.callback = callback;
};
Command.prototype.execute = function() {
var self = this;
// Check parameters
if(this.params.length < 1) {
return "Missing bag name";
}
var bagName = this.params[0],
bagDescription = this.params[1] || bagName;
// Create bag
var result = $tw.mws.store.createBag(bagName,bagDescription);
if(result) {
return result.message;
} else {
return null;
}
};
exports.Command = Command;
})();
@@ -0,0 +1,50 @@
/*\
title: $:/plugins/tiddlywiki/multiwikiserver/commands/mws-create-recipe.js
type: application/javascript
module-type: command
Command to load archive of recipes, bags and tiddlers from a directory
--mws-create-recipe <name> <bag-list> <description>
The parameter "bag-list" should be a space delimited list of bags
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
exports.info = {
name: "mws-create-recipe",
synchronous: true
};
var Command = function(params,commander,callback) {
this.params = params;
this.commander = commander;
this.callback = callback;
};
Command.prototype.execute = function() {
var self = this;
// Check parameters
if(this.params.length < 1) {
return "Missing recipe name";
}
var recipeName = this.params[0],
bagList = (this.params[1] || "").split(" "),
recipeDescription = this.params[2] || recipeNameName;
// Create recipe
var result = $tw.mws.store.createRecipe(recipeName,bagList,recipeDescription);
if(result) {
return result.message;
} else {
return null;
}
};
exports.Command = Command;
})();
@@ -0,0 +1,49 @@
/*\
title: $:/plugins/tiddlywiki/multiwikiserver/commands/mws-listen.js
type: application/javascript
module-type: command
Listen for HTTP requests and serve tiddlers
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
exports.info = {
name: "mws-listen",
synchronous: false,
namedParameterMode: true,
mandatoryParameters: []
};
var Command = function(params,commander,callback) {
var self = this;
this.params = params;
this.commander = commander;
this.callback = callback;
};
Command.prototype.execute = function() {
var self = this;
if(!$tw.boot.wikiTiddlersPath) {
$tw.utils.warning("Warning: Wiki folder '" + $tw.boot.wikiPath + "' does not exist or is missing a tiddlywiki.info file");
}
// Set up server
this.server = $tw.mws.serverManager.createServer({
wiki: $tw.wiki,
variables: self.params
});
this.server.listen(null,null,null,{
callback: function() {
self.callback();
}
});
return null;
};
exports.Command = Command;
})();
@@ -0,0 +1,93 @@
/*\
title: $:/plugins/tiddlywiki/multiwikiserver/commands/mws-load-archive.js
type: application/javascript
module-type: command
Command to load archive of recipes, bags and tiddlers from a directory
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
exports.info = {
name: "mws-load-archive",
synchronous: true
};
var Command = function(params,commander,callback) {
this.params = params;
this.commander = commander;
this.callback = callback;
};
Command.prototype.execute = function() {
var self = this;
// Check parameters
if(this.params.length < 1) {
return "Missing pathname";
}
var archivePath = this.params[0];
loadBackupArchive(archivePath);
return null;
};
function loadBackupArchive(archivePath) {
const fs = require("fs"),
path = require("path");
// Iterate the bags
const bagNames = fs.readdirSync(path.resolve(archivePath,"bags")).filter(filename => filename !== ".DS_Store");
for(const bagFilename of bagNames) {
const bagName = decodeURIComponent(bagFilename);
console.log(`Reading bag ${bagName}`);
const bagInfo = JSON.parse(fs.readFileSync(path.resolve(archivePath,"bags",bagFilename,"meta.json"),"utf8"));
$tw.mws.store.createBag(bagName,bagInfo.description,bagInfo.accesscontrol);
if(fs.existsSync(path.resolve(archivePath,"bags",bagFilename,"tiddlers"))) {
const tiddlerFilenames = fs.readdirSync(path.resolve(archivePath,"bags",bagFilename,"tiddlers"));
for(const tiddlerFilename of tiddlerFilenames) {
if(tiddlerFilename.endsWith(".json")) {
const tiddlerPath = path.resolve(archivePath,"bags",bagFilename,"tiddlers",tiddlerFilename),
jsonTiddler = fs.readFileSync(tiddlerPath,"utf8"),
tiddler = sanitiseTiddler(JSON.parse(jsonTiddler));
if(tiddler && tiddler.title) {
$tw.mws.store.saveBagTiddler(tiddler,bagName);
} else {
console.log(`Malformed JSON tiddler in file ${tiddlerPath}`);
}
}
}
}
}
// Iterate the recipes
const recipeNames = fs.readdirSync(path.resolve(archivePath,"recipes"));
for(const recipeFilename of recipeNames) {
if(recipeFilename.endsWith(".json")) {
const recipeName = decodeURIComponent(recipeFilename.substring(0,recipeFilename.length - ".json".length));
const jsonInfo = JSON.parse(fs.readFileSync(path.resolve(archivePath,"recipes",recipeFilename),"utf8"));
$tw.mws.store.createRecipe(recipeName,jsonInfo.bag_names,jsonInfo.description,jsonInfo.accesscontrol);
}
}
};
function sanitiseTiddler(tiddler) {
var sanitisedFields = Object.create(null);
for(const fieldName in tiddler) {
const fieldValue = tiddler[fieldName];
let sanitisedValue = "";
if(typeof fieldValue === "string") {
sanitisedValue = fieldValue;
} else if($tw.utils.isDate(fieldValue)) {
sanitisedValue = $tw.utils.stringifyDate(fieldValue);
} else if($tw.utils.isArray(fieldValue)) {
sanitisedValue = $tw.utils.stringifyList(fieldValue);
}
sanitisedFields[fieldName] = sanitisedValue;
}
return sanitisedFields;
}
exports.Command = Command;
})();
@@ -0,0 +1,81 @@
/*\
title: $:/plugins/tiddlywiki/multiwikiserver/commands/mws-load-plugin-bags.js
type: application/javascript
module-type: command
Command to create and load a bag for each plugin in the repo
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
exports.info = {
name: "mws-load-plugin-bags",
synchronous: true
};
var Command = function(params,commander,callback) {
this.params = params;
this.commander = commander;
this.callback = callback;
};
Command.prototype.execute = function() {
var self = this;
loadPluginBags();
return null;
};
function loadPluginBags() {
const path = require("path"),
fs = require("fs");
// Copy plugins
var makePluginBagName = function(type,publisher,name) {
return "$:/" + type + "/" + (publisher ? publisher + "/" : "") + name;
},
savePlugin = function(pluginFields,type,publisher,name) {
const bagName = makePluginBagName(type,publisher,name);
const result = $tw.mws.store.createBag(bagName,pluginFields.description || "(no description)",{allowPrivilegedCharacters: true});
if(result) {
console.log(`Error creating plugin bag ${bagname}: ${JSON.stringify(result)}`);
}
$tw.mws.store.saveBagTiddler(pluginFields,bagName);
},
collectPlugins = function(folder,type,publisher) {
var pluginFolders = $tw.utils.getSubdirectories(folder) || [];
for(var p=0; p<pluginFolders.length; p++) {
const pluginFolderName = pluginFolders[p];
if(!$tw.boot.excludeRegExp.test(pluginFolderName)) {
var pluginFields = $tw.loadPluginFolder(path.resolve(folder,pluginFolderName));
if(pluginFields && pluginFields.title) {
savePlugin(pluginFields,type,publisher,pluginFolderName);
}
}
}
},
collectPublisherPlugins = function(folder,type) {
var publisherFolders = $tw.utils.getSubdirectories(folder) || [];
for(var t=0; t<publisherFolders.length; t++) {
const publisherFolderName = publisherFolders[t];
if(!$tw.boot.excludeRegExp.test(publisherFolderName)) {
collectPlugins(path.resolve(folder,publisherFolderName),type,publisherFolderName);
}
}
};
$tw.utils.each($tw.getLibraryItemSearchPaths($tw.config.pluginsPath,$tw.config.pluginsEnvVar),function(folder) {
collectPublisherPlugins(folder,"plugins");
});
$tw.utils.each($tw.getLibraryItemSearchPaths($tw.config.themesPath,$tw.config.themesEnvVar),function(folder) {
collectPublisherPlugins(folder,"themes");
});
$tw.utils.each($tw.getLibraryItemSearchPaths($tw.config.languagesPath,$tw.config.languagesEnvVar),function(folder) {
collectPlugins(folder,"languages");
});
}
exports.Command = Command;
})();
@@ -0,0 +1,40 @@
/*\
title: $:/plugins/tiddlywiki/multiwikiserver/commands/mws-load-tiddlers.js
type: application/javascript
module-type: command
Command to recursively load a directory of tiddler files into a bag
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
exports.info = {
name: "mws-load-tiddlers",
synchronous: true
};
var Command = function(params,commander,callback) {
this.params = params;
this.commander = commander;
this.callback = callback;
};
Command.prototype.execute = function() {
var self = this;
// Check parameters
if(this.params.length < 2) {
return "Missing pathname and/or bag name";
}
var tiddlersPath = this.params[0],
bagName = this.params[1];
$tw.mws.store.saveTiddlersFromPath(tiddlersPath,bagName);
return null;
};
exports.Command = Command;
})();

Some files were not shown because too many files have changed in this diff Show More