1
0
mirror of https://github.com/Jermolene/TiddlyWiki5 synced 2026-07-10 13:52:45 +00:00

Compare commits

..

126 Commits

Author SHA1 Message Date
Jeremy Ruston 7a80bd4a6f Serializer should preserve created and modified fields 2026-04-22 10:53:49 +01:00
Jeremy Ruston 82bde9bffc Merge branch 'master' into bidirectional-filesystem 2026-04-21 20:25:02 +01:00
Jeremy Ruston ea84baa5a3 Merge branch 'tiddlywiki-com' 2026-04-21 20:24:23 +01:00
Mario Pietsch a4e4d36bf6 Fix Typo in Link in Multi-Valued Variables (#9824) 2026-04-20 21:31:52 +01:00
Mario Pietsch 3ed481b2e2 Update the Archive and Release Notes Description (#9823) 2026-04-20 21:31:09 +01:00
Jeremy Ruston 27c60ff58d Prepare for v5.5.0 2026-04-20 20:03:12 +01:00
Jeremy Ruston 748ef8aa8d New release should be first thumbnail 2026-04-20 19:56:08 +01:00
Jeremy Ruston 9cfa5a29fb Version number update for 5.4.0 2026-04-20 19:36:07 +01:00
Jeremy Ruston 5ea43ce212 Revert package.json 2026-04-20 19:35:55 +01:00
Jeremy Ruston df6bbbdedf More v5.4.0 preparations 2026-04-20 19:33:07 +01:00
Jeremy Ruston 37a461323e Preparing for release of v5.4.0 2026-04-20 19:31:36 +01:00
Cameron Fischer b29da7baac I didn't capitalize one of the instances of my name (#9822) 2026-04-19 16:42:52 +01:00
Jeremy Ruston 9830d4338c Merge branch 'master' into bidirectional-filesystem 2026-04-19 09:41:09 +01:00
Mario Pietsch 75b54457ed German translations update (#9821)
* German translations update

* fix typo
2026-04-18 15:16:32 +01:00
superuser-does 51f322c3c6 [5.4.0] Add release note for Greek translation update (#9818)
* [5.4.0] Add release note for Greek translation update

Add release note for #9782

* Remove trailing newline
2026-04-18 14:03:27 +01:00
lin onetwo 853af2d848 Fix: limit macro call parser to need >> to work, prevent > in regex (#9813)
* fix: limit macro call parser to need >> to work, prevent > in regex

* test: add malformed macro parameter regression coverage

The parser fix on this branch only changes parseMacroParameterAsAttribute() when an unquoted value starts with <<, so the previous broader parser tests did not prove the regression. Add a focused structural test that fails without the guard and passes with it.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>

* Revert "fix: limit macro call parser to need >> to work, prevent > in regex"

This reverts commit f96b062902.

* lint: test

* Reapply "fix: limit macro call parser to need >> to work, prevent > in regex"

This reverts commit 075f7cc282.

---------

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-04-18 09:59:22 +01:00
Jeremy Ruston 2c1cb33081 Restore v5.3.8 handling of malformed attribute syntax (#9812)
Also add a bunch of tests

Fixes #9808
2026-04-18 09:56:22 +01:00
Jeremy Ruston 75267b730a Merge branch 'master' into bidirectional-filesystem 2026-04-16 15:40:00 +01:00
Keiran Harcombe 2f63abc12a Merge pull request #9809 from kjharcombe/master
Remove CNAME
2026-04-15 10:34:50 +01:00
Jeremy Ruston 3bc78e6641 Register for text/x-markdown as well as text/markdown 2026-04-14 14:49:14 +01:00
Keiran Harcombe 5f80256576 Remove CNAME
I've removed the CNAME file which is blocking access to Yunohost
2026-04-14 12:44:30 +01:00
Jeremy Ruston 765386a4f9 Fix security warning from github-advanced-security's CodeQL scan 2026-04-14 11:37:04 +01:00
Jeremy Ruston ca8dcb690a Fix Netlify build failure 2026-04-14 11:25:02 +01:00
Jeremy Ruston 99a00ab1b4 Add Markdown serializer/deserializer
Not strictly related to dynamic stores, but very useful in connection with them
2026-04-14 10:48:40 +01:00
Jeremy Ruston f95966d5bb Genuflection to Eslint 2026-04-14 09:48:45 +01:00
Jeremy Ruston 3a20837c96 Fix dynamic-store save path and add tiddlerserializer module type
For tiddlers loaded from a dynamic store, compute originalpath relative
to the store directory so save-time path resolution lands at the correct
location (previously produced URL-encoded absolute-path filenames).

Add a tiddlerserializer module type mirroring tiddlerdeserializer: when
a serializer is registered for a tiddler's content type, save as a
single self-contained file with no .meta sidecar.
2026-04-14 09:45:03 +01:00
Jeremy Ruston e3e49bb61e Try to fix hang on netlify build 2026-04-13 18:09:21 +01:00
Jeremy Ruston 748969322b Another attempt to get tests passing on Netlify 2026-04-13 17:55:58 +01:00
Jeremy Ruston 4611e3569f New release banner 2026-04-13 17:51:29 +01:00
Jeremy Ruston c1e145ca12 Attempt to fix test errors in CI 2026-04-13 17:46:17 +01:00
Jeremy Ruston 882504c8d1 Fix change note 2026-04-13 17:39:00 +01:00
Jeremy Ruston b8f542458b Initial Commit 2026-04-13 17:14:12 +01:00
Jeremy Ruston aa837300d0 Update Chinese translation change note 2026-04-13 17:10:03 +01:00
Bram Chen 7f2baa17a9 Update chinese translations (#9804)
* Update chinese translations

* Update languages/zh-Hans/ControlPanel.multids (commit 33b2f51)
* Update languages/zh-Hant/ControlPanel.multids (commit 33b2f51)
* Update languages/zh-Hans/Import.multids (commit b673651)
* Update languages/zh-Hant/Import.multids (commit b673651)
* Update languages/zh-Hans/TiddlerInfo.multids (commit 0177f09, cd8b1fa )
* Update languages/zh-Hant/TiddlerInfo.multids (commit 0177f09, cd8b1fa )
* Add languages/zh-Hant/Draft.multids (commit 9c09841)

* Update chinese translations

* Clarify wording of tiddler info cascade view
2026-04-13 17:08:23 +01:00
Jeremy Ruston 39c6d8fb53 Missing change notes
Fixes #9773
2026-04-12 14:50:40 +01:00
Jeremy Ruston 9afe5226f1 Clarify wording of tiddler info cascade view 2026-04-12 14:33:52 +01:00
Jeremy Ruston 94aa37fe7e Merge branch 'tiddlywiki-com' 2026-04-11 08:38:49 +01:00
superuser-does 1e9c1ef82a [DOCS] Fix MWS Banner image display (#9801) 2026-04-11 08:45:27 +02:00
Saq Imtiaz 6cde5e94ff Update pr-validation.yml to use Cerebrus v8.1 2026-04-11 08:45:01 +02:00
Saq Imtiaz fd0d3ccc27 Revert "Replace fill rules in tw5.com edition (#9624)" (#9800)
This reverts commit dc764b3a4a.
2026-04-10 12:23:37 +02:00
Saq Imtiaz 1ad2e66cbc Revert "Use currentColor to style svg (#9316)" (#9799)
This reverts commit dc7f2a57bb.
2026-04-10 12:23:07 +02:00
Saq Imtiaz 4699db9f89 Revert "Fix side effects of PR 9316 (#9568)" (#9798)
This reverts commit bf7c0b575c.
2026-04-10 12:22:41 +02:00
lin onetwo d977b0ac5c Fix #9788: only allow colon-separator named params with strict identifiers (#9791)
The reAttributeName regexp in parseMacroParameterAsAttribute was too
permissive, allowing single-character names like dollar sign followed by
colon. This caused values such as dollar-colon-slash-plugins-foo to be
mis-parsed as a named parameter dollar with value slash-plugins-foo.

Fix: when the separator is colon (legacy syntax), require the parameter
name to match [A-Za-z0-9-_]+ (same as the historic parseMacroParameter).
When the separator is equals (new-style dynamic parameters from #9055),
the wide character set is preserved so that names like double-dollar-one
continue to work.

Adds regression test Transclude/Procedures/Dollar/Param.
2026-04-10 11:06:07 +02:00
Saq Imtiaz 7298684951 Update package.json to resolve CI issues 2026-04-10 08:35:32 +02:00
Mario Pietsch f78d8252af Fix issue #9795 tree view regression (#9796) 2026-04-10 08:29:54 +02:00
Saq Imtiaz 54260b5be8 Updates Cerebrus action to v8.1 (#9793)
* Update pr-check-build-size.yml

* Update pr-comment-build-size.yml

* Update pr-validation.yml
2026-04-09 09:13:45 +02:00
IchijikuIchigo 95f12eb857 [ja_JP] Update of Japanese translations (#9792)
* [ja_JP] Japanese translation update from commit: a71ac56

* [ja_JP] Japanese translation update from commit: 32a3a3e

* [ja_JP] Japanese translation update from commit: 65fcded

* [ja_JP] Japanese translation update from commit: a3acbaa

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\functions\Functions.tid'

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\functions\Functions.tid'

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\definitions\Base64.tid'

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\definitions\Definitions.tid'

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\definitions\Cascading Style Sheets.tid'

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\definitions\Tiddlyhost.tid'
2026-04-07 14:56:55 +02:00
IchijikuIchigo 207a68cc59 [ja_JP] Japanese translation update - 'Reference' section of tiddlywiki.com (#9790)
* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\reference\Reference.tid'

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\concepts\WikiText.tid'

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\wikitext\Horizontal Rules in WikiText.tid'

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\_tw_shared\doc-utilities\wikitext-macros.tid'

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\wikitext\Block Quotes in WikiText.tid'

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\wikitext\Code Blocks in WikiText.tid'

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\wikitext\Conditional Shortcut Syntax.tid'

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\wikitext\Block Quotes in WikiText.tid'

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\wikitext\Block Quotes in WikiText.tid'

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\wikitext\Dashes in WikiText.tid'

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\wikitext\Description Lists in WikiText.tid'

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\wikitext\Filtered Attribute Values.tid'

* [ja-JP] A little correction to the Japanese translation

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\wikitext\Formatting in WikiText.tid'

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\wikitext\Headings in WikiText.tid'

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\wikitext\Paragraphs in WikiText.tid'

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\wikitext\Hard Linebreaks in WikiText.tid'

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\howtos\Hard_Linebreaks_with_CSS.tid'

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\howtos\Hard_Linebreaks_with_CSS_-_Example.tid'

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\wikitext\HTML Entities.tid'

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\wikitext\Literal Attribute Values.tid'

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\wikitext\Lists in WikiText.tid'

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\wikitext\Tables in WikiText.tid'

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\wikitext\Table Classes Captions Headers and Footers.tid'

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\wikitext\Transcluded Attribute Values.tid'

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\wikitext\Utility Classes.tid'

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\wikitext\Widgets in WikiText.tid'

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\wikitext\parser\WikiText Parser Modes.tid'

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\wikitext\HTML in WikiText.tid'

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\wikitext\HTML_Links_in_WikiText.tid'

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\wikitext\Typed Blocks in WikiText.tid'

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\wikitext\Anchor Links using HTML.tid'

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\wikitext\Linking in WikiText.tid'

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\wikitext\Macro Calls.tid'

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\wikitext\Macro Definitions.tid'

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\wikitext\Macro Parameter Handling.tid'

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\wikitext\Substituted Attribute Values.tid'

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\wikitext\Substituted Attribute Values.tid'

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\Concatenating variables to create a URL.tid'

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\Concatenating a text reference to create a URL.tid'

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\procedures\Procedure Calls.tid'

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\procedures\Procedure Definitions.tid'

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\procedures\Procedure Parameter Handling.tid'

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\wikitext\Transclusion and Substitution.tid'

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\wikitext\Transclusion in WikiText.tid'

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\wikitext\Transclusion Basic Usage.tid'

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\wikitext\Transclusion Basic Usage.tid'

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\wikitext\Transclusion with Templates.tid'

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\wikitext\Variable Attribute Values.tid'

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\variables\Behaviour of variables invoked via widget attributes.tid'

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\wikitext\Tables in WikiText CSS Utility Classes.tid'

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\wikitext\Widget Attributes.tid'

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\concepts\TemplateTiddlers.tid'

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\concepts\Transclusion.tid'

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\wikitext\parser\table-example.tid'

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\wikitext\parser\WikiText parser mode_ transclusion examples.tid'

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\concepts\TextReference.tid'
2026-04-06 11:34:48 +02:00
superuser-does b434b9d7ea [DOCS] Include description lists in lists tiddler (#9786) 2026-04-05 10:55:36 +01:00
superuser-does b0013333de [DOCS] Expand description list documentation (#9784)
* [DOCS] Expand description list documentation

* remove stray word
2026-04-05 10:15:15 +01:00
superuser-does bcc30e305a Update Greek translation for 5.4.0 (#9782)
* Update Greek translation for 5.4.0

* Add Greek translation for Draft

* Correction to 'Saving'
2026-04-04 14:08:54 +01:00
Jeremy Ruston 34f4dfb903 Merge branch 'tiddlywiki-com' 2026-04-04 09:23:58 +01:00
superuser-does 32a3a3e478 [DOCS] Clean up Forums tiddler (#9780) 2026-04-04 08:49:55 +01:00
Bram Chen c6520c4429 Update chinese translations (#9781)
* Update languages/zh-Hans/ControlPanel.multids (commit 33b2f51)
* Update languages/zh-Hant/ControlPanel.multids (commit 33b2f51)
* Update languages/zh-Hans/Import.multids (commit b673651)
* Update languages/zh-Hant/Import.multids (commit b673651)
* Update languages/zh-Hans/TiddlerInfo.multids (commit 0177f09, cd8b1fa )
* Update languages/zh-Hant/TiddlerInfo.multids (commit 0177f09, cd8b1fa )
* Add languages/zh-Hant/Draft.multids (commit 9c09841)
2026-04-04 08:48:34 +01:00
Jeremy Ruston 83d242fb2c Merge branch 'tiddlywiki-com' 2026-04-03 11:10:58 +01:00
Jeremy Ruston ecafb7a4c0 A very minor tweak to the donation graphic
Spot the difference; the wonky text was offending me
2026-04-03 11:10:45 +01:00
Jeremy Ruston 0640e0abe2 Merge branch 'tiddlywiki-com' 2026-04-03 11:00:11 +01:00
Jeremy Ruston a71ac56cc9 More prominent donations link 2026-04-03 10:49:26 +01:00
Jeremy Ruston f518bdf27c Hire Jeremy banner now defaults to just being in the sidebar 2026-04-03 10:49:14 +01:00
Jeremy Ruston 0387b1100b Update CI dependencies 2026-04-01 17:13:32 +01:00
Jeremy Ruston c3a73fccd1 Fixed typo in v5.3.8 change note 2026-04-01 17:00:29 +01:00
Jeremy Ruston c25659d87c Restore compact call syntax (#9778)
* Initial commit

* Oops broken test
2026-04-01 15:22:42 +02:00
Saq Imtiaz 0c26a75472 Updates cerebrus action to v8 (#9779)
* Update pr-validation.yml

* Update pr-comment-build-size.yml

* Update pr-check-build-size.yml
2026-04-01 15:21:16 +02:00
Saq Imtiaz a24dc0e9e8 Update package.json to allow all current LTS versions of nodejs 2026-04-01 15:13:27 +02:00
Jeremy Ruston a499ed0906 Fix CI
Broken by 3cfb6b1202
2026-04-01 13:22:42 +01:00
Jeremy Ruston 3cfb6b1202 Update minimum Node version for as discussed for v5.4.0 2026-04-01 11:51:43 +01:00
Jeremy Ruston ac769e5dcd Commit package-lock.json
Benefit is that it pins dependencies and sub-dependencies, helping mitigate supply chain attacks
2026-04-01 11:42:17 +01:00
Jeremy Ruston ed01af8e47 Update release note with new release banner 2026-03-31 18:43:00 +01:00
Jeremy Ruston 92caa7312e Update new release banner
See https://talk.tiddlywiki.org/t/discussion-ai-and-vote-for-the-banner-for-5-4-0/15108/10
2026-03-31 18:25:20 +01:00
Mario Pietsch 478dce7009 fix problem with idDate() duplication in boot.js (#9771) 2026-03-31 18:50:31 +02:00
IchijikuIchigo f3c9cb2310 [ja_JP] Update Japanese translation (#9768)
* [ja_JP] Japanese translation update from commit: 5d0d2b1, e8fe6b9, 33b2f51

* [ja_JP] Japanese translation update from commit: 3543fe5

* [ja_JP] Japanese translation update from commit: 9c09841

* [ja_JP] Japanese translation update from commit: 390b755, 3e966d4, d17525e

* [ja_JP] Japanese translation update from commit: 4007610, 65afdac

* [ja_JP] Japanese translation update from commit: 5e8420a, 2adf091

* [ja_JP] Japanese translation update from commit: b673651

* [ja_JP] Japanese translation update from commit: a494361, 3af2a0a, 0e765bd

* [ja_JP] Japanese translation update from commit: daa24a5

* [ja_JP] Japanese translation update from commit: daa24a5, 881aed8

* [ja_JP] Japanese translation update from commit: 98e7255

* [ja_JP] Japanese translation update from commit: 4d9e683, cd8b1fa, 0177f09

* [ja_JP] Japanese translation update from commit: a494361

* [ja_JP] Japanese translation update from commit: eee3a0c, 169a381, f4fd5e7, 6332089, 1ce9973, c05c0d3, 9268a8c, e16635a

* [ja_JP] Delete duplicate Japanese translation files

* [ja_JP] Japanese translation update from commit: c02c3a0

* [ja_JP] Japanese translation update from commit: 147d758

* [ja_JP] Japanese translation update from commit: c6e4b7a, c4e2793, 12630d4

* [ja_JP] Japanese translation update from commit: 11b09be

* [ja_JP] Japanese translation update from commit: f7e50e0, e9557b5, 4ccdaf3, 6f93ce6

* [ja_JP] Japanese translation update from commit: f7e50e0, e9557b5, e87dadd, 814353d, 35a7a66, 5d91c27

* [ja_JP] Japanese translation update from commit: f7e50e0, e9557b5, 35a7a66

* [ja_JP] Japanese translation update from commit: d880cb1, 5176f00, 814353d, 35a7a66

* [ja_JP] Japanese translation update from commit: 5e8420a, 2adf091
2026-03-30 07:52:57 +02:00
Cameron Fischer 85fb634cae Moved bulkops relink behavior into dedicated module (#9703)
* Moved bulkops relink behavior into dedicated module

* Release notes

* Fixes for lint
2026-03-28 12:06:27 +00:00
Saq Imtiaz 280701fbde Fixes MVV's so that getVariableInfo returns undefined (#9765)
* wip: non-existent variables as attributes should be undefined

* wip: non-existent variables as attributes should be undefined

* tests: added missing test

* fix: restore mistakenly commited files

* fix: whitespace
2026-03-28 12:04:29 +00:00
Saq Imtiaz 01d3cde964 Fixes an issue where blockquotes trigger parsing as macros (#9695)
* fix: blockquotes trigger parsing as macros

* fix: blockquotes trigger parsing as macros

* fix: remove backwards incompatible change

* fix: whitespace

* fix: whitespace

* merge improvents from @hoelzro
2026-03-25 12:24:32 +01:00
Jeremy Ruston 2ea3663ea7 Merge branch 'tiddlywiki-com' 2026-03-24 17:14:24 +00:00
Jeremy Ruston 78b6f6f442 Docs: clarify parameters within calls in filters 2026-03-24 12:23:58 +00:00
Jeremy Ruston 3a4a8a206f Hotfix for broken test for dates
Fixes #9726
2026-03-24 11:19:17 +00:00
Mario Pietsch aff5be7195 fix flaky test runner (#9718) 2026-03-24 09:48:32 +01:00
Mario Pietsch c4991a54a0 Some minor changes to the filter docs (#9709)
* Some minor changes to the filter docs

* Fix wording in Intersection Filter Run Prefix documentation
2026-03-24 09:46:03 +01:00
Mario Pietsch 1d915389d9 Gitignore for automated tests (#9761)
* Add elements to gitignore, that are used for automated future test

* Add playwright configuration for Edge users that have no Chrome installed

* update tw-mcp setting
2026-03-22 08:42:13 +00:00
Mario Pietsch f5b4b1781e Fix edit toolbar button spaceing (#9763) 2026-03-22 08:31:38 +00:00
Mario Pietsch 65fcded29f Fix name clashes - definition tiddlers with images (#9759) 2026-03-20 07:54:03 +00:00
Urban Alič 962692c90c FIX: restore mhchem require for \ce and \pu macro support (#9756) 2026-03-19 22:34:56 +00:00
Mario Pietsch 533414b1df Change codewrapping option to pre-wrap
Fixes: #9757

Changed at: https://github.com/TiddlyWiki/TiddlyWiki5/pull/9107/changes#diff-9c7b0f26dc347b90265ea42dd89565ecf1d7b85ac8b5ff3f7dc49eeec42003b5

Talk:  https://talk.tiddlywiki.org/t/5-4-0-prerelease-code-lines-pre-wrap-setting-as-default/15066
2026-03-19 19:47:26 +01:00
Himmel ec27a4bf20 Add new contributor Himmel to CLA (#9755) 2026-03-19 07:12:33 +00:00
Jeremy Ruston e1cf523e2c Merge branch 'tiddlywiki-com' 2026-03-17 13:20:55 +00:00
Keiran Harcombe 1ece822d7a Update kjharcombe.tid (#9750)
I've created my community card for tiddlywiki.com
2026-03-16 23:27:50 +00:00
Keiran Harcombe b7a3418823 Signing CLA (#9749) 2026-03-16 21:58:42 +00:00
Xavier Cazin bb6d88f144 fr-FR translation catchup before 5.4.0 (#9748)
* fr-FR translation for the Alerts aria string

* fr-FR translation for WikiInformation-related strings

* fr-FR updates for TiddlySpot/TiddlyHost-related strings

* fr-FR translation for Stability- and InfoTab-related strings
2026-03-16 17:08:06 +00:00
Jeremy Ruston 57c72756ef Fix releasenote avatars
Fixes #9746

Thanks @pmario
2026-03-16 16:57:55 +00:00
Jeremy Ruston dda4c7fb10 Update to latest version of dom-to-image-more library
Include it in tw5.com for testing
2026-03-15 16:53:51 +00:00
Jeremy Ruston fd3b96e2dd Merge branch 'tiddlywiki-com' 2026-03-15 10:46:16 +00:00
Jeremy Ruston c07396c453 Community: Add @kjharcombe as infrastructure team lead
Please add your community card when you can, many thanks.
2026-03-15 10:46:02 +00:00
Jeremy Ruston 7fc255f90c Merge branch 'tiddlywiki-com' 2026-03-14 18:18:09 +00:00
Jeremy Ruston 4cd84a6ba1 Geospatial plugin: remove residue of dom-to-image plugin
Fixes #9683
2026-03-11 17:30:23 +00:00
Jeremy Ruston fdfcd66c9b dom-to-image fix indentation 2026-03-11 17:14:47 +00:00
Andrew Gregory 3983086b96 Document alternating table row styling (#9713)
* Document alternating table row styling

Resolves issue #8738 (and explains why my old wiki row styles were swapped).

* Make note more prominent

Use note macro for note

Co-authored-by: Mario Pietsch <pmariojo@gmail.com>

---------

Co-authored-by: Mario Pietsch <pmariojo@gmail.com>
2026-03-11 14:22:49 +00:00
Jeremy Ruston a61331e6ed Fix readme for tw5.com-docs plugin
Fixes #9721
2026-03-11 13:50:37 +00:00
Jeremy Ruston 8773a0433c Dynaview: Fix RSOE with Update address bar while scrolling
See https://talk.tiddlywiki.org/t/your-help-is-needed-to-test-v5-4-0/14945/52
2026-03-11 12:22:14 +00:00
Mario Pietsch 846deb3039 Git Ignore Claude AI local settings (#9719) 2026-03-09 17:09:07 +00:00
Saq Imtiaz 111a168b0c feat: added community card for saqimtiaz (#9701) 2026-03-06 22:47:35 +00:00
Cameron Fischer 02b4c04a56 Release notes for PRs I've made, as requested (#9707)
* Release notes for PRs I've made, as requested

* Update #9297.tid

blog -> blob
2026-03-03 09:23:27 +01:00
Saq Imtiaz 9d5b5111d0 Revert "Add locale support for sort operator (#9400)" (#9702)
This reverts commit 98a61f01bb.
2026-03-01 08:02:18 +01:00
btheado 48cff401b2 Add tests/examples for transclusion shortcut syntax (#9705) 2026-03-01 07:58:59 +01:00
Saq Imtiaz 7a866b93e3 fix: alt support for images in markdown (#9700) 2026-02-27 18:53:58 +01:00
s793016 b0d99f3bd3 Fix Freelinks Aho-Corasick: failure links, cache invalidation, longest-match, and Unicode safety (#9676)
* Update aho-corasick.js

 fix transition logic; ensure complete outputs (via failure-output merge); clean up stats/build scoping; clarify CJK boundary behavior.

* Update text.js

implement global longest-match priority with overlap suppression; fix refresh invalidation to ignore $:/state and drafts; handle deletions precisely to avoid rebuilding on draft deletion; add defensive check for cached automaton presence.

* Update text.js

remove comment

* Update aho-corasick.js

remove comment

* Create #9672.tid

* Create #2026-0222.tid

* Delete editions/tw5.com/tiddlers/releasenotes/5.4.0/#2026-0222.tid

* Update text.js

remove \"

* Update and rename #9672.tid to #9676.tid

change to right number

* Update #9397.tid

update the existing release note with the new PR link instead of creating a new release note.

* Delete editions/tw5.com/tiddlers/releasenotes/5.4.0/#9676.tid

update the existing release note with the new PR link instead of creating a new release note.

* Rename #9397.tid to #9676.tid

update the existing release note with the new PR link instead of creating a new release note.

* Update and rename #9676.tid to #9397.tid

add link

* Rename #9397.tid to #9676.tid

* Update tiddlywiki.info

add plugin for test build

* Update tiddlywiki.info

reverse change, ready to be merge.
2026-02-25 12:07:32 +01:00
Andrew Gregory 91e7a62c13 docs: Tabs examples dynamically setting default tab (#9693)
Where tabs have been selected using a filter it is sometimes useful to set the default tab dynamically from the filter results. In most of these cases it is also desirable that the state not be persistent.
2026-02-25 08:14:38 +01:00
Saq Imtiaz 9f7763710e fix: MVV handling should distinguish between blank and undefined attributes (#9687) 2026-02-24 11:12:21 +01:00
lin onetwo b673651103 Fixes slow when import html on upgrade (#9689)
* Short-circuit the import option; only show it when you click a button

* Add aira-label

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* perf(import): lazy evaluate import options panel, shortcircuit when no options, and flatten let widgets

- Only parse $:/Import JSON when user expands the import options panel
- Skip all import option logic if no $:/tags/ImportOptions tiddlers exist
- Merge nested let widgets into single multi-variable let for better performance and readability
- Add lingo for import options panel and no-match message

This greatly improves import speed for large wikis, especially when no import options are present.

* fix(import): use correct transclusion for aria-label on import options panel buttons\n\nBind aria-label using {{$:/language/Import/Listing/ImportOptions/Caption}} instead of macro literal, matching TiddlyWiki convention and ensuring accessibility tools read the correct label.

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-23 11:58:24 +01:00
Saq Imtiaz 49faefcca5 fix: zoomin storyview should not remove CSS properties when element is already removed (#9686) 2026-02-23 08:59:32 +01:00
Saq Imtiaz 75c26b46c6 fix: missing attributes must be undefined (#9682) 2026-02-22 22:16:07 +01:00
btheado add3d425b5 Add let filter run examples (#9681)
* Add :let filter run prefix examples

Includes one example from @yaisog at #8972

* Added link to the new :let filter run examples

* Fixed broken link
2026-02-22 21:10:10 +01:00
Mario Pietsch c23a0bedfd Add some links and info text to release notes (#9680) 2026-02-22 17:44:42 +00:00
Saq Imtiaz 36b20e02a3 ci: update to v7 cerebrus 2026-02-22 17:43:47 +01:00
Saq Imtiaz ab6172981c Update cerebrus action to version 7 2026-02-22 17:40:41 +01:00
lin onetwo 431b7fa3bf Update ImportListing.tid (#9674) 2026-02-21 17:37:37 +00:00
Jeremy Ruston 1c1f157079 More release note tweaks 2026-02-20 21:26:44 +00:00
Jeremy Ruston ac19f300a9 Fix changenote 2026-02-20 12:05:51 +00:00
Jeremy Ruston 9012b00806 Search changenotes and impact notes (#9670)
* Tweak some changenotes

* Tweak release note tabs

* Simplify summary tab

* Remove #8702 changes/impacts

* Search change notes and impacts

* Fix release note category search

* Tweak headline changenotes
2026-02-20 12:02:14 +00:00
Jeremy Ruston a3acbaa2f1 Update road map 2026-02-20 09:30:25 +00:00
Jeremy Ruston 07634d6595 Merge branch 'tiddlywiki-com' 2026-02-20 09:28:07 +00:00
Saq Imtiaz 785086e0a5 Fixes ESLint errors (#9668)
* fix: apply automatic eslint fixes

* lint: allow hashbang comment for tiddlywiki.js

* lint: first back of manual lint fixes for unused vars

* lint: added more fixes for unused vars

* lint: missed files

* lint: updated eslint config with selected rules from #9669
2026-02-20 08:38:42 +00:00
IchijikuIchigo 8cd6bbc075 [ja_JP] Japanese translation update - 'Community' section of tiddlywiki.com (#9662)
* [ja_JP] Japanese translation update from commit: 4dc89f6

* [ja_JP] Japanese translation update from commit: 4dc89f6

* [ja_JP] Japanese translation update from commit: 381388f, 4dc89f6, 5fa1098

* [ja_JP] Japanese translation update from commit: 3c1d658

* [ja_JP] Japanese translation update from commit: 3c1d658

* [ja_JP] Japanese translation update from commit: 29a567f, 6aee5eb

* [ja_JP] Japanese translation update from commit: 0ac2b6c

* [ja_JP] Japanese translation update from commit: 1994574

* [ja_JP] Japanese translation update from commit: 5dfdbc8

* [ja_JP] Japanese translation update from commit: 1994574

* [ja_JP] Japanese translation update from commit: 81d8d67

* [ja_JP] Japanese translation update from commit: 81d8d67

* [ja_JP] Japanese translation update from commit: 4196d96

* [ja_JP] Japanese translation update from commit: 1994574

* [ja_JP] Japanese translation update from commit: 1994574

* [ja-JP] Japanese translation of 'community\project\TiddlyWiki Project.tid'

* [ja-JP] Japanese translation of 'community\docs\Community Cards Caveats.tid'

* [ja-JP] Japanese translation of 'community\project\TiddlyWiki People.tid'

* [ja-JP] Japanese translation of 'community\project\Vacant Positions.tid'

* [ja-JP] Japanese translation of 'community\docs\Community Cards.tid'

* [ja-JP] Japanese translation of 'community\docs\Displaying Community Cards.tid'

* [ja-JP] Japanese translation of 'community\docs\Submitting a Community Card.tid'

* [ja-JP] Japanese translation of 'community\tools\cards\Procedures.tid'

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\community\Resources.tid'

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\community\Resources.tid'

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\community\Community Links Aggregator.tid'

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\community\TiddlyWiki Newsletter.tid'

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\marketplace\TiddlyWiki Marketplace.tid'

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\hellothere\HelloThumbnail.tid'

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\community\Improving TiddlyWiki Documentation.tid'

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\community\Translate TiddlyWiki into your language.tid'

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\community\TiddlyWiki_European_Meetup_2016.tid'

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\community\TiddlyWiki_European_Meetup_2017.tid'

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\community\editions\Tidme by oflg.tid'

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\community\editions\_Cardo-A Task and Project Management Wiki_ by David_Szego.tid'

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\community\editions\Drift by Tony K.tid'

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\community\editions\Stroll by David Gifford.tid'

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\community\plugins\GSD5.tid'

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\community\plugins\TiddlyWiki for Scholars.tid'

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\_TiddlyStudy_ by Kebi.tid'

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\community\resources\Wikilabs by PMario.tid'

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\community\resources\Projectify by Nicolas Petton.tid'

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\community\plugins\TW5-Graph by Flibbles.tid'

* [ja-JP] Japanese translation has been modified to display the ja-title.

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\community\articles\_A free, open source wiki revisited_ by Mark Gibbs, NetworkWorld.tid'

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\community\articles\_Notizen mit TiddlyWiki systemubergreifend nutzen_ by Michael Sonntag.tid'

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\community\articles\_Setting Up a Personal TiddlyWiki Server on OS X_ by Kris Johnson.tid'

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\community\articles\_TiddlyWiki 5 im Betatest_ by besim.tid'

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\community\articles\_TiddlyWiki_ by Sander de Boer.tid'

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\community\resources\TiddlyWiki Posts.tid'

* [ja-JP] A little correction to the Japanese translation
2026-02-15 11:56:40 +01:00
Mario Pietsch 6fc65c1560 [DOCS] Slightly Improve "How to create keyboard shortcuts" Readability (#9478) 2026-02-05 17:01:40 +01:00
Mario Pietsch ab29f17d35 [DOCS] Add Repo-link to "Installing official plugins on Node.js" (#9483)
* [DOCS] Add Repo-link to "Installing official plugins on Node.js"

* Update Installing official plugins on Node.js.tid
2026-02-04 21:26:48 +01:00
IchijikuIchigo 5ae4770317 [ja_JP] Update of Japanese translations (#9581)
* [ja_JP] Japanese translation update from commit: 5ff4e02

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\hire-jeremy\Hire Jeremy Sidebar Segment.tid'

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\hire-jeremy\Hire Jeremy Sidebar Segment.tid'

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\hire-jeremy\HireJeremy.tid'

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\hellothere\thumbnails\HelloThumbnail - Community Survey.tid'

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\hellothere\thumbnails\HelloThumbnail - TW5-Graph.tid'

* [ja_JP] Japanese translation update from commit: e3af967

* [ja_JP] Japanese translation update from commit: bdc0fe1

* [ja_JP] Japanese translation update from commit: e3af967

* [ja_JP] Japanese translation update from commit: bdc0fe1, e3af967

* [ja_JP] Japanese translation update from commit: fdafdba, e3af967, ecba671, 935e89b

* [ja_JP] Japanese translation update from commit: bdc0fe1, e3af967

* [ja_JP] Japanese translation update from commit: 0763ee5

* [ja_JP] Japanese translation update from commit: bdc0fe1

* [ja_JP] Japanese translation update from commit: b0d950f, 4dc89f6

* [ja_JP] Japanese translation update from commit: 4dc89f6

* [ja_JP] Japanese translation update from commit: 4dc89f6

* [ja_JP] Japanese translation update from commit: 4dc89f6

* [ja_JP] Japanese translation update from commit: b0d950f, 4dc89f6

* [ja_JP] Japanese translation update from commit: 4dc89f6

* [ja_JP] Japanese translation update from commit: 4dc89f6

* [ja_JP] Japanese translation update from commit: 4dc89f6

* [ja_JP] Japanese translation update from commit: 4dc89f6

* [ja_JP] Japanese translation update from commit: b0d950f

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\commands\Commands.tid'

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\commands\BuildCommand.tid'

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\commands\ClearPasswordCommand.tid'

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\commands\CommandsCommand.tid'

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\commands\DeleteTiddlersCommand.tid'

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\commands\EditionsCommand.tid'

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\commands\FetchCommand.tid'

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\commands\HelpCommand.tid'

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\commands\ImportCommand.tid'

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\commands\InitCommand.tid'

* [ja-JP] Japanese translation of 'editions\ja-JP\tiddlers\commands\ListenCommand.tid'

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\commands\LoadCommand.tid'

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\commands\MakeLibraryCommand.tid'

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\commands\OutputCommand.tid'

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\commands\PasswordCommand.tid'

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\commands\RenderCommand.tid'

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\commands\SaveCommand.tid'

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\commands\SaveWikiFolderCommand.tid'

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\commands\SetFieldCommand.tid'

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\commands\UnpackPluginCommand.tid'

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\commands\VerboseCommand.tid'

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\commands\VersionCommand.tid'

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\commands\RenderTiddlerCommand.tid'

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\commands\RenderTiddlersCommand.tid'

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\commands\SaveTiddlerCommand.tid'

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\commands\SaveTiddlersCommand.tid'

* [ja-JP] Japanese translation of 'editions\tw5.com\tiddlers\commands\ServerCommand.tid'
2026-02-04 21:24:55 +01:00
905 changed files with 17025 additions and 5769 deletions
+8 -8
View File
@@ -5,17 +5,17 @@ on:
- master - master
- tiddlywiki-com - tiddlywiki-com
env: env:
NODE_VERSION: "22" NODE_VERSION: "22.22"
jobs: jobs:
test: test:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v5 - uses: actions/checkout@v6
- uses: actions/setup-node@v4 - uses: actions/setup-node@v6
with: with:
node-version: "${{ env.NODE_VERSION }}" node-version: "${{ env.NODE_VERSION }}"
- run: "./bin/ci-test.sh" - run: "./bin/ci-test.sh"
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@v7
if: always() if: always()
with: with:
name: playwright-report name: playwright-report
@@ -30,8 +30,8 @@ jobs:
TW5_BUILD_MAIN_EDITION: "./editions/prerelease" TW5_BUILD_MAIN_EDITION: "./editions/prerelease"
TW5_BUILD_OUTPUT: "./output/prerelease" TW5_BUILD_OUTPUT: "./output/prerelease"
steps: steps:
- uses: actions/checkout@v5 - uses: actions/checkout@v6
- uses: actions/setup-node@v4 - uses: actions/setup-node@v6
with: with:
node-version: "${{ env.NODE_VERSION }}" node-version: "${{ env.NODE_VERSION }}"
- run: "./bin/ci-pre-build.sh" - run: "./bin/ci-pre-build.sh"
@@ -62,8 +62,8 @@ jobs:
TW5_BUILD_OUTPUT: "./output" TW5_BUILD_OUTPUT: "./output"
TW5_BUILD_ARCHIVE: "./output" TW5_BUILD_ARCHIVE: "./output"
steps: steps:
- uses: actions/checkout@v5 - uses: actions/checkout@v6
- uses: actions/setup-node@v4 - uses: actions/setup-node@v6
with: with:
node-version: "${{ env.NODE_VERSION }}" node-version: "${{ env.NODE_VERSION }}"
- run: "./bin/ci-pre-build.sh" - run: "./bin/ci-pre-build.sh"
+2 -2
View File
@@ -20,7 +20,7 @@ jobs:
steps: steps:
- name: build-size-check - name: build-size-check
id: get_sizes id: get_sizes
uses: TiddlyWiki/cerebrus@v6 uses: TiddlyWiki/cerebrus@v8.1
with: with:
pr_number: ${{ github.event.pull_request.number }} pr_number: ${{ github.event.pull_request.number }}
repo: ${{ github.repository }} repo: ${{ github.repository }}
@@ -52,4 +52,4 @@ jobs:
pr_size: '${{ needs.calculate-build-size.outputs.pr_size }}', pr_size: '${{ needs.calculate-build-size.outputs.pr_size }}',
base_size: '${{ needs.calculate-build-size.outputs.base_size }}' base_size: '${{ needs.calculate-build-size.outputs.base_size }}'
} }
}); });
+1 -1
View File
@@ -25,7 +25,7 @@ jobs:
steps: steps:
- name: Build and check size - name: Build and check size
uses: TiddlyWiki/cerebrus@v6 uses: TiddlyWiki/cerebrus@v8.1
with: with:
pr_number: ${{ inputs.pr_number }} pr_number: ${{ inputs.pr_number }}
repo: ${{ github.repository }} repo: ${{ github.repository }}
+2 -2
View File
@@ -15,7 +15,7 @@ jobs:
steps: steps:
# Step 1: Validate PR paths # Step 1: Validate PR paths
- name: Validate PR Paths - name: Validate PR Paths
uses: TiddlyWiki/cerebrus@v6 uses: TiddlyWiki/cerebrus@v8.1
with: with:
pr_number: ${{ github.event.pull_request.number }} pr_number: ${{ github.event.pull_request.number }}
repo: ${{ github.repository }} repo: ${{ github.repository }}
@@ -26,7 +26,7 @@ jobs:
# Step 2: Validate change notes # Step 2: Validate change notes
- name: Validate Change Notes - name: Validate Change Notes
uses: TiddlyWiki/cerebrus@v6 uses: TiddlyWiki/cerebrus@v8.1
with: with:
pr_number: ${{ github.event.pull_request.number }} pr_number: ${{ github.event.pull_request.number }}
repo: ${{ github.repository }} repo: ${{ github.repository }}
+11 -1
View File
@@ -2,10 +2,20 @@
.c9/ .c9/
.vs/ .vs/
.vscode/ .vscode/
.claude/
# TiddlyWiki
tmp/ tmp/
output/ output/
node_modules/ node_modules/
$__StoryList.tid
# Playwright
/test-results/ /test-results/
/playwright-report/ /playwright-report/
/blob-report/
/playwright/.cache/ /playwright/.cache/
$__StoryList.tid /playwright/.auth/
test-screenshots/
test-output.txt
.playwright-mcp
# TiddlyWiki MPC
.tw-mcp
+1 -1
View File
@@ -5,7 +5,7 @@
# Default to the current version number for building the plugin library # Default to the current version number for building the plugin library
if [ -z "$TW5_BUILD_VERSION" ]; then if [ -z "$TW5_BUILD_VERSION" ]; then
TW5_BUILD_VERSION=v5.4.0 TW5_BUILD_VERSION=v5.5.0
fi fi
echo "Using TW5_BUILD_VERSION as [$TW5_BUILD_VERSION]" echo "Using TW5_BUILD_VERSION as [$TW5_BUILD_VERSION]"
+1 -1
View File
@@ -45,7 +45,7 @@ git clone --depth=1 --branch=main "https://github.com/TiddlyWiki/tiddlywiki.org-
# Make the CNAME file that GitHub Pages requires # Make the CNAME file that GitHub Pages requires
echo "tiddlywiki.org" > $TWORG_BUILD_OUTPUT/CNAME # echo "tiddlywiki.org" > $TWORG_BUILD_OUTPUT/CNAME
# Delete any existing static content # Delete any existing static content
+32 -32
View File
@@ -15,40 +15,40 @@ var fs = require("fs"),
{ optimize } = require("svgo"), { optimize } = require("svgo"),
config = { config = {
plugins: [ plugins: [
'cleanupAttrs', "cleanupAttrs",
'removeDoctype', "removeDoctype",
'removeXMLProcInst', "removeXMLProcInst",
'removeComments', "removeComments",
'removeMetadata', "removeMetadata",
'removeTitle', "removeTitle",
'removeDesc', "removeDesc",
'removeUselessDefs', "removeUselessDefs",
'removeEditorsNSData', "removeEditorsNSData",
'removeEmptyAttrs', "removeEmptyAttrs",
'removeHiddenElems', "removeHiddenElems",
'removeEmptyText', "removeEmptyText",
'removeEmptyContainers', "removeEmptyContainers",
// 'removeViewBox', // 'removeViewBox',
'cleanupEnableBackground', "cleanupEnableBackground",
'convertStyleToAttrs', "convertStyleToAttrs",
'convertColors', "convertColors",
'convertPathData', "convertPathData",
'convertTransform', "convertTransform",
'removeUnknownsAndDefaults', "removeUnknownsAndDefaults",
'removeNonInheritableGroupAttrs', "removeNonInheritableGroupAttrs",
'removeUselessStrokeAndFill', "removeUselessStrokeAndFill",
'removeUnusedNS', "removeUnusedNS",
'cleanupIDs', "cleanupIDs",
'cleanupNumericValues', "cleanupNumericValues",
'moveElemsAttrsToGroup', "moveElemsAttrsToGroup",
'moveGroupAttrsToElems', "moveGroupAttrsToElems",
'collapseGroups', "collapseGroups",
// 'removeRasterImages', // 'removeRasterImages',
'mergePaths', "mergePaths",
'convertShapeToPath', "convertShapeToPath",
'sortAttrs', "sortAttrs",
//'removeDimensions', //'removeDimensions',
{name: 'removeAttrs', params: { attrs: '(stroke|fill)' } } {name: "removeAttrs", params: { attrs: "(stroke|fill)" } }
] ]
}; };
@@ -72,7 +72,7 @@ files.forEach(function(filename) {
var newSVG = header.join("\n") + "\n\n" + result.data.replace("&lt;&lt;now &quot;DD&quot;&gt;&gt;","<<now \"DD\">>"); var newSVG = header.join("\n") + "\n\n" + result.data.replace("&lt;&lt;now &quot;DD&quot;&gt;&gt;","<<now \"DD\">>");
fs.writeFileSync(filepath,newSVG); fs.writeFileSync(filepath,newSVG);
} else { } else {
console.log("Error " + err + " with " + filename) console.log("Error " + err + " with " + filename);
process.exit(); process.exit();
}; };
} }
+96 -46
View File
@@ -12,8 +12,6 @@ On the server this file is executed directly to boot TiddlyWiki. In the browser,
var _boot = (function($tw) { var _boot = (function($tw) {
/*jslint node: true, browser: true */
/*global modules: false, $tw: false */
"use strict"; "use strict";
// Include bootprefix if we're not given module data // Include bootprefix if we're not given module data
@@ -37,7 +35,7 @@ if($tw.node) {
$tw.boot.log = function(str) { $tw.boot.log = function(str) {
$tw.boot.logMessages = $tw.boot.logMessages || []; $tw.boot.logMessages = $tw.boot.logMessages || [];
$tw.boot.logMessages.push(str); $tw.boot.logMessages.push(str);
} };
/* /*
Check if an object has a property Check if an object has a property
@@ -47,7 +45,14 @@ $tw.utils.hop = function(object,property) {
}; };
/** @deprecated Use Array.isArray instead */ /** @deprecated Use Array.isArray instead */
$tw.utils.isArray = value => Array.isArray(value); $tw.utils.isArray = (value) => Array.isArray(value);
/*
Determine if a value is a date, even across VM boundaries
*/
$tw.utils.isDate = function(value) {
return Object.prototype.toString.call(value) === "[object Date]";
};
/* /*
Check if an array is equal by value and by reference. Check if an array is equal by value and by reference.
@@ -126,9 +131,6 @@ $tw.utils.pushTop = function(array,value) {
return array; return array;
}; };
/** @deprecated Use instanceof Date instead */
$tw.utils.isDate = value => value instanceof Date;
/** @deprecated Use array iterative methods instead */ /** @deprecated Use array iterative methods instead */
$tw.utils.each = function(object,callback) { $tw.utils.each = function(object,callback) {
if(object) { if(object) {
@@ -138,7 +140,7 @@ $tw.utils.each = function(object,callback) {
return next !== false; return next !== false;
}); });
} else { } else {
Object.entries(object).every(entry => { Object.entries(object).every((entry) => {
const next = callback(entry[1], entry[0], object); const next = callback(entry[1], entry[0], object);
return next !== false; return next !== false;
}); });
@@ -565,7 +567,7 @@ using a lowercase extension only.
*/ */
$tw.utils.getFileExtensionInfo = function(ext) { $tw.utils.getFileExtensionInfo = function(ext) {
return ext ? $tw.config.fileExtensionInfo[ext.toLowerCase()] : null; return ext ? $tw.config.fileExtensionInfo[ext.toLowerCase()] : null;
} };
/* /*
Given an extension, get the correct encoding for that file. Given an extension, get the correct encoding for that file.
@@ -588,7 +590,7 @@ var globalCheck =[
" delete Object.prototype.__temp__;", " delete Object.prototype.__temp__;",
" }", " }",
" delete Object.prototype.__temp__;", " delete Object.prototype.__temp__;",
].join('\n'); ].join("\n");
/* /*
Run code globally with specified context variables in scope Run code globally with specified context variables in scope
@@ -616,7 +618,7 @@ $tw.utils.evalGlobal = function(code,context,filename,sandbox,allowGlobals) {
fn = Function("return " + code + "\n\n//# sourceURL=" + filename)(); // See https://github.com/TiddlyWiki/TiddlyWiki5/issues/6839 fn = Function("return " + code + "\n\n//# sourceURL=" + filename)(); // See https://github.com/TiddlyWiki/TiddlyWiki5/issues/6839
} else { } else {
if(sandbox){ if(sandbox){
fn = vm.runInContext(code,sandbox,filename) fn = vm.runInContext(code,sandbox,filename);
} else { } else {
fn = vm.runInThisContext(code,filename); fn = vm.runInThisContext(code,filename);
} }
@@ -727,7 +729,7 @@ $tw.utils.PasswordPrompt.prototype.createPrompt = function(options) {
var self = this; var self = this;
form.addEventListener("submit",function(event) { form.addEventListener("submit",function(event) {
// Collect the form data // Collect the form data
var data = {},t; var data = {};
$tw.utils.each(form.elements,function(element) { $tw.utils.each(form.elements,function(element) {
if(element.name && element.value) { if(element.name && element.value) {
data[element.name] = element.value; data[element.name] = element.value;
@@ -773,7 +775,7 @@ $tw.utils.PasswordPrompt.prototype.removePrompt = function(promptInfo) {
promptInfo.form.parentNode.removeChild(promptInfo.form); promptInfo.form.parentNode.removeChild(promptInfo.form);
this.setWrapperDisplay(); this.setWrapperDisplay();
} }
} };
/* /*
Crypto helper object for encrypted content. It maintains the password text in a closure, and provides methods to change Crypto helper object for encrypted content. It maintains the password text in a closure, and provides methods to change
@@ -812,7 +814,7 @@ $tw.utils.Crypto = function() {
}; };
this.hasPassword = function() { this.hasPassword = function() {
return !!currentPassword; return !!currentPassword;
} };
this.encrypt = function(text,password) { this.encrypt = function(text,password) {
// set default ks:256 -- see: http://bitwiseshiftleft.github.io/sjcl/doc/convenience.js.html // set default ks:256 -- see: http://bitwiseshiftleft.github.io/sjcl/doc/convenience.js.html
return callSjcl("encrypt",text,password,{v:1,iter:10000,ks:256,ts:64,mode:"ccm",adata:"",cipher:"aes"}); return callSjcl("encrypt",text,password,{v:1,iter:10000,ks:256,ts:64,mode:"ccm",adata:"",cipher:"aes"});
@@ -830,7 +832,7 @@ Execute the module named 'moduleName'. The name can optionally be relative to th
$tw.modules.execute = function(moduleName,moduleRoot) { $tw.modules.execute = function(moduleName,moduleRoot) {
var name = moduleName; var name = moduleName;
if(moduleName.charAt(0) === ".") { if(moduleName.charAt(0) === ".") {
name = $tw.utils.resolvePath(moduleName,moduleRoot) name = $tw.utils.resolvePath(moduleName,moduleRoot);
} }
if(!$tw.modules.titles[name]) { if(!$tw.modules.titles[name]) {
if($tw.modules.titles[name + ".js"]) { if($tw.modules.titles[name + ".js"]) {
@@ -891,7 +893,6 @@ $tw.modules.execute = function(moduleName,moduleRoot) {
if(!moduleInfo) { if(!moduleInfo) {
// We could not find the module on this path // We could not find the module on this path
// Try to defer to browserify etc, or node // Try to defer to browserify etc, or node
var deferredModule;
if($tw.browser) { if($tw.browser) {
if(window.require) { if(window.require) {
try { try {
@@ -1146,8 +1147,7 @@ enableIndexers - Array of indexer names to enable, or null to use all available
*/ */
$tw.Wiki = function(options) { $tw.Wiki = function(options) {
options = options || {}; options = options || {};
var self = this, var tiddlers = Object.create(null), // Hashmap of tiddlers
tiddlers = Object.create(null), // Hashmap of tiddlers
tiddlerTitles = null, // Array of tiddler titles tiddlerTitles = null, // Array of tiddler titles
getTiddlerTitles = function() { getTiddlerTitles = function() {
if(!tiddlerTitles) { if(!tiddlerTitles) {
@@ -1201,7 +1201,7 @@ $tw.Wiki = function(options) {
shadow: this.isShadowTiddler(title), shadow: this.isShadowTiddler(title),
exists: this.tiddlerExists(title) exists: this.tiddlerExists(title)
} }
} };
// Save the new tiddler // Save the new tiddler
tiddlers[title] = tiddler; tiddlers[title] = tiddler;
// Check we've got the title // Check we've got the title
@@ -1211,7 +1211,7 @@ $tw.Wiki = function(options) {
tiddler: tiddler, tiddler: tiddler,
shadow: this.isShadowTiddler(title), shadow: this.isShadowTiddler(title),
exists: this.tiddlerExists(title) exists: this.tiddlerExists(title)
} };
// Update indexes // Update indexes
this.clearCache(title); this.clearCache(title);
this.clearGlobalCache(); this.clearGlobalCache();
@@ -1236,7 +1236,7 @@ $tw.Wiki = function(options) {
shadow: this.isShadowTiddler(title), shadow: this.isShadowTiddler(title),
exists: this.tiddlerExists(title) exists: this.tiddlerExists(title)
} }
} };
// Delete the tiddler // Delete the tiddler
delete tiddlers[title]; delete tiddlers[title];
// Delete it from the list of titles // Delete it from the list of titles
@@ -1251,7 +1251,7 @@ $tw.Wiki = function(options) {
tiddler: this.getTiddler(title), tiddler: this.getTiddler(title),
shadow: this.isShadowTiddler(title), shadow: this.isShadowTiddler(title),
exists: this.tiddlerExists(title) exists: this.tiddlerExists(title)
} };
// Update indexes // Update indexes
this.clearCache(title); this.clearCache(title);
this.clearGlobalCache(); this.clearGlobalCache();
@@ -1439,8 +1439,7 @@ $tw.Wiki = function(options) {
// Unregister the plugin tiddlers of a particular type, or null/undefined for any type, optionally restricting unregistering to an array of tiddler titles. Returns an array of the titles affected // Unregister the plugin tiddlers of a particular type, or null/undefined for any type, optionally restricting unregistering to an array of tiddler titles. Returns an array of the titles affected
this.unregisterPluginTiddlers = function(pluginType,titles) { this.unregisterPluginTiddlers = function(pluginType,titles) {
var self = this, var unregisteredTitles = [];
unregisteredTitles = [];
// Remove any previous registered plugins of this type // Remove any previous registered plugins of this type
for(var t=pluginTiddlers.length-1; t>=0; t--) { for(var t=pluginTiddlers.length-1; t>=0; t--) {
var tiddler = pluginTiddlers[t]; var tiddler = pluginTiddlers[t];
@@ -1454,16 +1453,15 @@ $tw.Wiki = function(options) {
// Unpack the currently registered plugins, creating shadow tiddlers for their constituent tiddlers // Unpack the currently registered plugins, creating shadow tiddlers for their constituent tiddlers
this.unpackPluginTiddlers = function() { this.unpackPluginTiddlers = function() {
var self = this;
// Sort the plugin titles by the `plugin-priority` field, if this field is missing, default to 1 // Sort the plugin titles by the `plugin-priority` field, if this field is missing, default to 1
pluginTiddlers.sort(function(a, b) { pluginTiddlers.sort(function(a, b) {
var priorityA = "plugin-priority" in a.fields ? a.fields["plugin-priority"] : 1; var priorityA = "plugin-priority" in a.fields ? a.fields["plugin-priority"] : 1;
var priorityB = "plugin-priority" in b.fields ? b.fields["plugin-priority"] : 1; var priorityB = "plugin-priority" in b.fields ? b.fields["plugin-priority"] : 1;
if (priorityA !== priorityB) { if(priorityA !== priorityB) {
return priorityA - priorityB; return priorityA - priorityB;
} else if (a.fields.title < b.fields.title) { } else if(a.fields.title < b.fields.title) {
return -1; return -1;
} else if (a.fields.title === b.fields.title) { } else if(a.fields.title === b.fields.title) {
return 0; return 0;
} else { } else {
return +1; return +1;
@@ -1541,8 +1539,8 @@ Register all the module tiddlers that have a module type
$tw.Wiki.prototype.defineShadowModules = function() { $tw.Wiki.prototype.defineShadowModules = function() {
var self = this; var self = this;
this.eachShadow(function(tiddler,title) { this.eachShadow(function(tiddler,title) {
// Don't define the module if it is overidden by an ordinary tiddler // Don't define the module if it is overidden by an ordinary tiddler, or has already been defined
if(!self.tiddlerExists(title) && tiddler.hasField("module-type")) { if(!self.tiddlerExists(title) && tiddler.hasField("module-type") && !$tw.utils.hop($tw.modules.titles,title)) {
if(tiddler.hasField("draft.of")) { if(tiddler.hasField("draft.of")) {
// Report a fundamental problem // Report a fundamental problem
console.warn(`TiddlyWiki: Plugins should not contain tiddlers with a 'draft.of' field: ${tiddler.fields.title}`); console.warn(`TiddlyWiki: Plugins should not contain tiddlers with a 'draft.of' field: ${tiddler.fields.title}`);
@@ -1570,7 +1568,7 @@ $tw.Wiki.prototype.processSafeMode = function() {
// Assemble a report tiddler // Assemble a report tiddler
var titleReportTiddler = "TiddlyWiki Safe Mode", var titleReportTiddler = "TiddlyWiki Safe Mode",
report = []; report = [];
report.push("TiddlyWiki has been started in [[safe mode|https://tiddlywiki.com/static/SafeMode.html]]. All plugins are temporarily disabled. Most customisations have been disabled by renaming the following tiddlers:") report.push("TiddlyWiki has been started in [[safe mode|https://tiddlywiki.com/static/SafeMode.html]]. All plugins are temporarily disabled. Most customisations have been disabled by renaming the following tiddlers:");
// Delete the overrides // Delete the overrides
overrides.forEach(function(title) { overrides.forEach(function(title) {
var tiddler = self.getTiddler(title), var tiddler = self.getTiddler(title),
@@ -1579,7 +1577,7 @@ $tw.Wiki.prototype.processSafeMode = function() {
self.addTiddler(new $tw.Tiddler(tiddler, {title: newTitle})); self.addTiddler(new $tw.Tiddler(tiddler, {title: newTitle}));
report.push("* [[" + title + "|" + newTitle + "]]"); report.push("* [[" + title + "|" + newTitle + "]]");
}); });
report.push() report.push();
this.addTiddler(new $tw.Tiddler({title: titleReportTiddler, text: report.join("\n\n")})); this.addTiddler(new $tw.Tiddler({title: titleReportTiddler, text: report.join("\n\n")}));
// Set $:/DefaultTiddlers to point to our report // Set $:/DefaultTiddlers to point to our report
this.addTiddler(new $tw.Tiddler({title: "$:/DefaultTiddlers", text: "[[" + titleReportTiddler + "]]"})); this.addTiddler(new $tw.Tiddler({title: "$:/DefaultTiddlers", text: "[[" + titleReportTiddler + "]]"}));
@@ -1973,7 +1971,7 @@ $tw.loadTiddlersFromSpecification = function(filepath,excludeRegExp) {
}); });
// Helper to process a file // Helper to process a file
var processFile = function(filename,isTiddlerFile,fields,isEditableFile,rootPath) { var processFile = function(filename,isTiddlerFile,fields,isEditableFile,rootPath,dynamicStoreId) {
var extInfo = $tw.config.fileExtensionInfo[path.extname(filename)], var extInfo = $tw.config.fileExtensionInfo[path.extname(filename)],
type = (extInfo || {}).type || fields.type || "text/plain", type = (extInfo || {}).type || fields.type || "text/plain",
typeInfo = $tw.config.contentTypeInfo[type] || {}, typeInfo = $tw.config.contentTypeInfo[type] || {},
@@ -2013,7 +2011,7 @@ $tw.loadTiddlersFromSpecification = function(filepath,excludeRegExp) {
value = $tw.utils.stringifyList(path.relative(rootPath, filename).split(path.sep).slice(0, -1)); value = $tw.utils.stringifyList(path.relative(rootPath, filename).split(path.sep).slice(0, -1));
break; break;
case "filepath": case "filepath":
value = path.relative(rootPath, filename).split(path.sep).join('/'); value = path.relative(rootPath, filename).split(path.sep).join("/");
break; break;
case "filename": case "filename":
value = path.basename(filename); value = path.basename(filename);
@@ -2048,9 +2046,9 @@ $tw.loadTiddlersFromSpecification = function(filepath,excludeRegExp) {
}); });
}); });
if(isEditableFile) { if(isEditableFile) {
tiddlers.push({filepath: pathname, hasMetaFile: !!metadata && !isTiddlerFile, isEditableFile: true, tiddlers: fileTiddlers}); tiddlers.push({filepath: pathname, hasMetaFile: !!metadata && !isTiddlerFile, isEditableFile: true, dynamicStoreId: dynamicStoreId, tiddlers: fileTiddlers});
} else { } else {
tiddlers.push({tiddlers: fileTiddlers}); tiddlers.push({dynamicStoreId: dynamicStoreId, tiddlers: fileTiddlers});
} }
}; };
// Helper to recursively search subdirectories // Helper to recursively search subdirectories
@@ -2066,7 +2064,7 @@ $tw.loadTiddlersFromSpecification = function(filepath,excludeRegExp) {
} }
}); });
return arrayOfFiles; return arrayOfFiles;
} };
// Process the listed tiddlers // Process the listed tiddlers
$tw.utils.each(filesInfo.tiddlers,function(tidInfo) { $tw.utils.each(filesInfo.tiddlers,function(tidInfo) {
if(tidInfo.prefix && tidInfo.suffix) { if(tidInfo.prefix && tidInfo.suffix) {
@@ -2091,6 +2089,31 @@ $tw.loadTiddlersFromSpecification = function(filepath,excludeRegExp) {
// Process directory specifier // Process directory specifier
var dirPath = path.resolve(filepath,dirSpec.path); var dirPath = path.resolve(filepath,dirSpec.path);
if(fs.existsSync(dirPath) && fs.statSync(dirPath).isDirectory()) { if(fs.existsSync(dirPath) && fs.statSync(dirPath).isDirectory()) {
// Register a dynamic store if requested
var dynamicStoreId = null;
if(dirSpec.dynamicStore && $tw.boot.dynamicStores) {
dynamicStoreId = dirPath;
var existing = null;
for(var ds=0; ds<$tw.boot.dynamicStores.length; ds++) {
if($tw.boot.dynamicStores[ds].id === dynamicStoreId) {
existing = $tw.boot.dynamicStores[ds];
break;
}
}
if(!existing) {
$tw.boot.dynamicStores.push({
id: dynamicStoreId,
directory: dirPath,
saveFilter: dirSpec.dynamicStore.saveFilter || "",
watch: dirSpec.dynamicStore.watch !== false,
debounce: dirSpec.dynamicStore.debounce || 400,
filesRegExp: dirSpec.filesRegExp || "^.*$",
searchSubdirectories: !!dirSpec.searchSubdirectories,
isTiddlerFile: !!dirSpec.isTiddlerFile,
fields: dirSpec.fields || {}
});
}
}
var files = getAllFiles(dirPath, dirSpec.searchSubdirectories), var files = getAllFiles(dirPath, dirSpec.searchSubdirectories),
fileRegExp = new RegExp(dirSpec.filesRegExp || "^.*$"), fileRegExp = new RegExp(dirSpec.filesRegExp || "^.*$"),
metaRegExp = /^.*\.meta$/; metaRegExp = /^.*\.meta$/;
@@ -2099,7 +2122,7 @@ $tw.loadTiddlersFromSpecification = function(filepath,excludeRegExp) {
filename = path.basename(thisPath); filename = path.basename(thisPath);
if(filename !== "tiddlywiki.files" && !metaRegExp.test(filename) && fileRegExp.test(filename)) { if(filename !== "tiddlywiki.files" && !metaRegExp.test(filename) && fileRegExp.test(filename)) {
dirSpec.fields = dirSpec.fields || {}; dirSpec.fields = dirSpec.fields || {};
processFile(thisPath,dirSpec.isTiddlerFile,dirSpec.fields,dirSpec.isEditableFile,dirSpec.path); processFile(thisPath,dirSpec.isTiddlerFile,dirSpec.fields,dirSpec.isEditableFile || !!dirSpec.dynamicStore,dirSpec.path,dynamicStoreId);
} }
} }
} else { } else {
@@ -2175,7 +2198,7 @@ Returns the path of the plugin folder
$tw.findLibraryItem = function(name,paths) { $tw.findLibraryItem = function(name,paths) {
var pathIndex = 0; var pathIndex = 0;
do { do {
var pluginPath = path.resolve(paths[pathIndex],"./" + name) var pluginPath = path.resolve(paths[pathIndex],"./" + name);
if(fs.existsSync(pluginPath) && fs.statSync(pluginPath).isDirectory()) { if(fs.existsSync(pluginPath) && fs.statSync(pluginPath).isDirectory()) {
return pluginPath; return pluginPath;
} }
@@ -2286,6 +2309,24 @@ $tw.loadWikiTiddlers = function(wikiPath,options) {
$tw.loadPlugins(wikiInfo.plugins,$tw.config.pluginsPath,$tw.config.pluginsEnvVar); $tw.loadPlugins(wikiInfo.plugins,$tw.config.pluginsPath,$tw.config.pluginsEnvVar);
$tw.loadPlugins(wikiInfo.themes,$tw.config.themesPath,$tw.config.themesEnvVar); $tw.loadPlugins(wikiInfo.themes,$tw.config.themesPath,$tw.config.themesEnvVar);
$tw.loadPlugins(wikiInfo.languages,$tw.config.languagesPath,$tw.config.languagesEnvVar); $tw.loadPlugins(wikiInfo.languages,$tw.config.languagesPath,$tw.config.languagesEnvVar);
// Register plugin-provided tiddlerdeserializer and tiddlerserializer modules now,
// so they are available when the wiki tiddler files are read from disk below.
// We also apply the supporting `utils`, `tiddlerfield`, and `tiddlermethod`
// modules so deserializers can call into them (e.g. core's text/html
// deserializer needs $tw.utils.extractEncryptedStoreArea).
// (All of these steps run again later in execStartup; they are idempotent.)
$tw.wiki.readPluginInfo();
$tw.wiki.registerPluginTiddlers("plugin");
$tw.wiki.unpackPluginTiddlers();
$tw.wiki.defineShadowModules();
$tw.modules.applyMethods("utils",$tw.utils);
if($tw.node) {
$tw.modules.applyMethods("utils-node",$tw.utils);
}
$tw.Tiddler.fieldModules = $tw.modules.getModulesByTypeAsHashmap("tiddlerfield");
$tw.modules.applyMethods("tiddlermethod",$tw.Tiddler.prototype);
$tw.modules.applyMethods("tiddlerdeserializer",$tw.Wiki.tiddlerDeserializerModules);
$tw.modules.applyMethods("tiddlerserializer",$tw.Wiki.tiddlerSerializerModules);
// Load the wiki files, registering them as writable // Load the wiki files, registering them as writable
var resolvedWikiPath = path.resolve(wikiPath,$tw.config.wikiTiddlersSubDir); var resolvedWikiPath = path.resolve(wikiPath,$tw.config.wikiTiddlersSubDir);
$tw.utils.each($tw.loadTiddlersFromPath(resolvedWikiPath),function(tiddlerFile) { $tw.utils.each($tw.loadTiddlersFromPath(resolvedWikiPath),function(tiddlerFile) {
@@ -2295,7 +2336,8 @@ $tw.loadWikiTiddlers = function(wikiPath,options) {
filepath: tiddlerFile.filepath, filepath: tiddlerFile.filepath,
type: tiddlerFile.type, type: tiddlerFile.type,
hasMetaFile: tiddlerFile.hasMetaFile, hasMetaFile: tiddlerFile.hasMetaFile,
isEditableFile: config["retain-original-tiddler-path"] || tiddlerFile.isEditableFile || tiddlerFile.filepath.indexOf($tw.boot.wikiTiddlersPath) !== 0 isEditableFile: config["retain-original-tiddler-path"] || tiddlerFile.isEditableFile || tiddlerFile.filepath.indexOf($tw.boot.wikiTiddlersPath) !== 0,
dynamicStoreId: tiddlerFile.dynamicStoreId || null
}; };
}); });
} }
@@ -2307,7 +2349,10 @@ $tw.loadWikiTiddlers = function(wikiPath,options) {
for(var title in $tw.boot.files) { for(var title in $tw.boot.files) {
fileInfo = $tw.boot.files[title]; fileInfo = $tw.boot.files[title];
if(fileInfo.isEditableFile) { if(fileInfo.isEditableFile) {
relativePath = path.relative($tw.boot.wikiTiddlersPath,fileInfo.filepath); // For tiddlers loaded from a dynamic store, compute originalpath relative to the store's directory
// so that save-time path resolution against that directory yields the correct location.
var basePath = fileInfo.dynamicStoreId || $tw.boot.wikiTiddlersPath;
relativePath = path.relative(basePath,fileInfo.filepath);
fileInfo.originalpath = relativePath; fileInfo.originalpath = relativePath;
output[title] = output[title] =
path.sep === "/" ? path.sep === "/" ?
@@ -2433,6 +2478,8 @@ $tw.boot.initStartup = function(options) {
if(!$tw.boot.tasks.readBrowserTiddlers) { if(!$tw.boot.tasks.readBrowserTiddlers) {
// For writable tiddler files, a hashmap of title to {filepath:,type:,hasMetaFile:} // For writable tiddler files, a hashmap of title to {filepath:,type:,hasMetaFile:}
$tw.boot.files = Object.create(null); $tw.boot.files = Object.create(null);
// Array of {id, directory, saveFilter, watch, debounce} registered via tiddlywiki.files dynamicStore directives
$tw.boot.dynamicStores = [];
// System paths and filenames // System paths and filenames
$tw.boot.bootPath = options.bootPath || path.dirname(module.filename); $tw.boot.bootPath = options.bootPath || path.dirname(module.filename);
$tw.boot.corePath = path.resolve($tw.boot.bootPath,"../core"); $tw.boot.corePath = path.resolve($tw.boot.bootPath,"../core");
@@ -2522,6 +2569,9 @@ $tw.boot.initStartup = function(options) {
// Install the tiddler deserializer modules // Install the tiddler deserializer modules
$tw.Wiki.tiddlerDeserializerModules = Object.create(null); $tw.Wiki.tiddlerDeserializerModules = Object.create(null);
$tw.modules.applyMethods("tiddlerdeserializer",$tw.Wiki.tiddlerDeserializerModules); $tw.modules.applyMethods("tiddlerdeserializer",$tw.Wiki.tiddlerDeserializerModules);
// Install the tiddler serializer modules
$tw.Wiki.tiddlerSerializerModules = Object.create(null);
$tw.modules.applyMethods("tiddlerserializer",$tw.Wiki.tiddlerSerializerModules);
// Call unload handlers in the browser // Call unload handlers in the browser
if($tw.browser) { if($tw.browser) {
window.onbeforeunload = function(event) { window.onbeforeunload = function(event) {
@@ -2534,7 +2584,7 @@ $tw.boot.initStartup = function(options) {
} }
}); });
return result; return result;
} };
} }
}; };
$tw.boot.loadStartup = function(options){ $tw.boot.loadStartup = function(options){
@@ -2551,7 +2601,7 @@ $tw.boot.loadStartup = function(options){
} }
// Give hooks a chance to modify the store // Give hooks a chance to modify the store
$tw.hooks.invokeHook("th-boot-tiddlers-loaded"); $tw.hooks.invokeHook("th-boot-tiddlers-loaded");
} };
$tw.boot.execStartup = function(options){ $tw.boot.execStartup = function(options){
// Unpack plugin tiddlers // Unpack plugin tiddlers
$tw.wiki.readPluginInfo(); $tw.wiki.readPluginInfo();
@@ -2581,7 +2631,7 @@ $tw.boot.execStartup = function(options){
$tw.boot.disabledStartupModules = $tw.boot.disabledStartupModules || []; $tw.boot.disabledStartupModules = $tw.boot.disabledStartupModules || [];
// Repeatedly execute the next eligible task // Repeatedly execute the next eligible task
$tw.boot.executeNextStartupTask(options.callback); $tw.boot.executeNextStartupTask(options.callback);
} };
/* /*
Startup TiddlyWiki Startup TiddlyWiki
*/ */
@@ -2600,7 +2650,7 @@ $tw.addUnloadTask = function(task) {
if($tw.unloadTasks.indexOf(task) === -1) { if($tw.unloadTasks.indexOf(task) === -1) {
$tw.unloadTasks.push(task); $tw.unloadTasks.push(task);
} }
} };
/* /*
Execute the remaining eligible startup tasks Execute the remaining eligible startup tasks
@@ -2647,7 +2697,7 @@ $tw.boot.executeNextStartupTask = function(callback) {
} }
taskIndex++; taskIndex++;
} }
if(typeof callback === 'function') { if(typeof callback === "function") {
callback(); callback();
} }
return false; return false;
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,6 +1,7 @@
created: 20250909171928024 created: 20250909171928024
modified: 20251110133437795 modified: 20251110133437795
tags: Community/Team tags: Community/Team
leader: @kjharcombe
team: @MotovunJack team: @MotovunJack
title: Infrastructure Team title: Infrastructure Team
@@ -12,4 +13,4 @@ The infrastructure includes:
* github.com/TiddlyWiki * github.com/TiddlyWiki
* tiddlywiki.com DNS * tiddlywiki.com DNS
* Netlify account for PR previews * Netlify account for PR previews
* edit.tiddlywiki.com * edit.tiddlywiki.com
+1 -1
View File
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -103,7 +103,7 @@ Commander.prototype.executeNextCommand = function() {
c = new command.Command(params,this); c = new command.Command(params,this);
err = c.execute(); err = c.execute();
if(err && typeof err.then === "function") { if(err && typeof err.then === "function") {
err.then(e => { e ? this.callback(e) : this.executeNextCommand(); }); err.then((e) => { e ? this.callback(e) : this.executeNextCommand(); });
} else if(err) { } else if(err) {
this.callback(err); this.callback(err);
} else { } else {
@@ -120,7 +120,7 @@ Commander.prototype.executeNextCommand = function() {
}); });
err = c.execute(); err = c.execute();
if(err && typeof err.then === "function") { if(err && typeof err.then === "function") {
err.then(e => { if(e) this.callback(e); }); err.then((e) => { if(e) this.callback(e); });
} else if(err) { } else if(err) {
this.callback(err); this.callback(err);
} }
+1 -1
View File
@@ -25,7 +25,7 @@ Command.prototype.execute = function() {
if(!filter) { if(!filter) {
return "No filter specified"; return "No filter specified";
} }
var commands = this.commander.wiki.filterTiddlers(filter) var commands = this.commander.wiki.filterTiddlers(filter);
if(commands.length === 0) { if(commands.length === 0) {
return "No tiddlers found for filter '" + filter + "'"; return "No tiddlers found for filter '" + filter + "'";
} }
+1 -2
View File
@@ -24,8 +24,7 @@ Command.prototype.execute = function() {
if(this.params.length < 1) { if(this.params.length < 1) {
return "Missing filter"; return "Missing filter";
} }
var self = this, var wiki = this.commander.wiki,
wiki = this.commander.wiki,
filter = this.params[0], filter = this.params[0],
tiddlers = wiki.filterTiddlers(filter); tiddlers = wiki.filterTiddlers(filter);
$tw.utils.each(tiddlers,function(title) { $tw.utils.each(tiddlers,function(title) {
+26 -26
View File
@@ -66,7 +66,7 @@ Command.prototype.fetchFiles = function(options) {
// Get the list of URLs // Get the list of URLs
var urls; var urls;
if(options.url) { if(options.url) {
urls = [options.url] urls = [options.url];
} else if(options.urlFilter) { } else if(options.urlFilter) {
urls = this.commander.wiki.filterTiddlers(options.urlFilter); urls = this.commander.wiki.filterTiddlers(options.urlFilter);
} else { } else {
@@ -96,30 +96,30 @@ Command.prototype.fetchFile = function(url,options,callback,redirectCount) {
var self = this, var self = this,
lib = url.substr(0,8) === "https://" ? require("https") : require("http"); lib = url.substr(0,8) === "https://" ? require("https") : require("http");
lib.get(url).on("response",function(response) { lib.get(url).on("response",function(response) {
var type = (response.headers["content-type"] || "").split(";")[0], var type = (response.headers["content-type"] || "").split(";")[0],
data = []; data = [];
self.commander.write("Reading " + url + ": "); self.commander.write("Reading " + url + ": ");
response.on("data",function(chunk) { response.on("data",function(chunk) {
data.push(chunk); data.push(chunk);
self.commander.write("."); self.commander.write(".");
}); });
response.on("end",function() { response.on("end",function() {
self.commander.write("\n"); self.commander.write("\n");
if(response.statusCode === 200) { if(response.statusCode === 200) {
self.processBody(Buffer.concat(data),type,options,url); self.processBody(Buffer.concat(data),type,options,url);
callback(null); callback(null);
} else { } else {
if(response.statusCode === 302 || response.statusCode === 303 || response.statusCode === 307) { if(response.statusCode === 302 || response.statusCode === 303 || response.statusCode === 307) {
return self.fetchFile(response.headers.location,options,callback,redirectCount + 1); return self.fetchFile(response.headers.location,options,callback,redirectCount + 1);
} else { } else {
return callback("Error " + response.statusCode + " retrieving " + url) return callback("Error " + response.statusCode + " retrieving " + url);
} }
} }
}); });
response.on("error",function(e) { response.on("error",function(e) {
console.log("Error on GET request: " + e); console.log("Error on GET request: " + e);
callback(e); callback(e);
}); });
}); });
return null; return null;
}; };
@@ -153,18 +153,18 @@ Command.prototype.processBody = function(body,type,options,url) {
if(options.transformFilter) { if(options.transformFilter) {
var transformedTitle = (incomingWiki.filterTiddlers(options.transformFilter,null,self.commander.wiki.makeTiddlerIterator([title])) || [""])[0]; var transformedTitle = (incomingWiki.filterTiddlers(options.transformFilter,null,self.commander.wiki.makeTiddlerIterator([title])) || [""])[0];
if(transformedTitle) { if(transformedTitle) {
self.commander.log("Importing " + title + " as " + transformedTitle) self.commander.log("Importing " + title + " as " + transformedTitle);
newTiddler = new $tw.Tiddler(tiddler,{title: transformedTitle}); newTiddler = new $tw.Tiddler(tiddler,{title: transformedTitle});
} }
} else { } else {
self.commander.log("Importing " + title) self.commander.log("Importing " + title);
newTiddler = tiddler; newTiddler = tiddler;
} }
self.commander.wiki.importTiddler(newTiddler); self.commander.wiki.importTiddler(newTiddler);
count++; count++;
} }
}); });
self.commander.log("Imported " + count + " tiddlers") self.commander.log("Imported " + count + " tiddlers");
}; };
exports.Command = Command; exports.Command = Command;
+1 -2
View File
@@ -22,8 +22,7 @@ var Command = function(params,commander,callback) {
Command.prototype.execute = function() { Command.prototype.execute = function() {
var self = this, var self = this,
fs = require("fs"), fs = require("fs");
path = require("path");
if(this.params.length < 2) { if(this.params.length < 2) {
return "Missing parameters"; return "Missing parameters";
} }
+1 -2
View File
@@ -20,8 +20,7 @@ var Command = function(params,commander) {
}; };
Command.prototype.execute = function() { Command.prototype.execute = function() {
var fs = require("fs"), var fs = require("fs");
path = require("path");
// Check that we don't already have a valid wiki folder // Check that we don't already have a valid wiki folder
if($tw.boot.wikiTiddlersPath || ($tw.utils.isDirectory($tw.boot.wikiPath) && !$tw.utils.isDirectoryEmpty($tw.boot.wikiPath))) { if($tw.boot.wikiTiddlersPath || ($tw.utils.isDirectory($tw.boot.wikiPath) && !$tw.utils.isDirectoryEmpty($tw.boot.wikiPath))) {
return "Wiki folder is not empty"; return "Wiki folder is not empty";
-1
View File
@@ -19,7 +19,6 @@ exports.info = {
}; };
var Command = function(params,commander,callback) { var Command = function(params,commander,callback) {
var self = this;
this.params = params; this.params = params;
this.commander = commander; this.commander = commander;
this.callback = callback; this.callback = callback;
+1 -3
View File
@@ -21,9 +21,7 @@ var Command = function(params,commander,callback) {
}; };
Command.prototype.execute = function() { Command.prototype.execute = function() {
var self = this, var self = this;
fs = require("fs"),
path = require("path");
if(this.params.length < 1) { if(this.params.length < 1) {
return "Missing filename"; return "Missing filename";
} }
+1 -2
View File
@@ -21,8 +21,7 @@ var Command = function(params,commander,callback) {
}; };
Command.prototype.execute = function() { Command.prototype.execute = function() {
var fs = require("fs"), var path = require("path");
path = require("path");
if(this.params.length < 1) { if(this.params.length < 1) {
return "Missing output path"; return "Missing output path";
} }
+49 -51
View File
@@ -7,59 +7,57 @@ Render individual tiddlers and save the results to the specified files
\*/ \*/
"use strict"; "use strict";
var widget = require("$:/core/modules/widgets/widget.js"); exports.info = {
name: "render",
synchronous: true
};
exports.info = { var Command = function(params,commander,callback) {
name: "render", this.params = params;
synchronous: true this.commander = commander;
}; this.callback = callback;
};
var Command = function(params,commander,callback) { Command.prototype.execute = function() {
this.params = params; if(this.params.length < 1) {
this.commander = commander; return "Missing tiddler filter";
this.callback = callback; }
}; var self = this,
fs = require("fs"),
Command.prototype.execute = function() { path = require("path"),
if(this.params.length < 1) { wiki = this.commander.wiki,
return "Missing tiddler filter"; tiddlerFilter = this.params[0],
filenameFilter = this.params[1] || "[is[tiddler]addsuffix[.html]]",
type = this.params[2] || "text/html",
template = this.params[3],
variableList = this.params.slice(4),
tiddlers = wiki.filterTiddlers(tiddlerFilter),
variables = Object.create(null);
while(variableList.length >= 2) {
variables[variableList[0]] = variableList[1];
variableList = variableList.slice(2);
}
$tw.utils.each(tiddlers,function(title) {
var filenameResults = wiki.filterTiddlers(filenameFilter,$tw.rootWidget,wiki.makeTiddlerIterator([title]));
if(filenameResults.length > 0) {
var filepath = path.resolve(self.commander.outputPath,filenameResults[0]);
if(self.commander.verbose) {
console.log("Rendering \"" + title + "\" to \"" + filepath + "\"");
}
var parser = wiki.parseTiddler(template || title),
widgetNode = wiki.makeWidget(parser,{variables: $tw.utils.extend({},variables,{currentTiddler: title,storyTiddler: title})}),
container = $tw.fakeDocument.createElement("div");
widgetNode.render(container,null);
var text = type === "text/html" ? container.innerHTML : container.textContent;
$tw.utils.createFileDirectories(filepath);
fs.writeFileSync(filepath,text,"utf8");
} else {
console.log("Not rendering \"" + title + "\" because the filename filter returned an empty result");
} }
var self = this, });
fs = require("fs"), return null;
path = require("path"), };
wiki = this.commander.wiki,
tiddlerFilter = this.params[0],
filenameFilter = this.params[1] || "[is[tiddler]addsuffix[.html]]",
type = this.params[2] || "text/html",
template = this.params[3],
variableList = this.params.slice(4),
tiddlers = wiki.filterTiddlers(tiddlerFilter),
variables = Object.create(null);
while(variableList.length >= 2) {
variables[variableList[0]] = variableList[1];
variableList = variableList.slice(2);
}
$tw.utils.each(tiddlers,function(title) {
var filenameResults = wiki.filterTiddlers(filenameFilter,$tw.rootWidget,wiki.makeTiddlerIterator([title]));
if(filenameResults.length > 0) {
var filepath = path.resolve(self.commander.outputPath,filenameResults[0]);
if(self.commander.verbose) {
console.log("Rendering \"" + title + "\" to \"" + filepath + "\"");
}
var parser = wiki.parseTiddler(template || title),
widgetNode = wiki.makeWidget(parser,{variables: $tw.utils.extend({},variables,{currentTiddler: title,storyTiddler: title})}),
container = $tw.fakeDocument.createElement("div");
widgetNode.render(container,null);
var text = type === "text/html" ? container.innerHTML : container.textContent;
$tw.utils.createFileDirectories(filepath);
fs.writeFileSync(filepath,text,"utf8");
} else {
console.log("Not rendering \"" + title + "\" because the filename filter returned an empty result");
}
});
return null;
};
exports.Command = Command; exports.Command = Command;
-2
View File
@@ -9,8 +9,6 @@ Command to render several tiddlers to a folder of files
"use strict"; "use strict";
var widget = require("$:/core/modules/widgets/widget.js");
exports.info = { exports.info = {
name: "rendertiddlers", name: "rendertiddlers",
synchronous: true synchronous: true
+46 -47
View File
@@ -7,57 +7,56 @@ Saves individual tiddlers in their raw text or binary format to the specified fi
\*/ \*/
"use strict"; "use strict";
exports.info = { exports.info = {
name: "save", name: "save",
synchronous: true synchronous: true
}; };
var Command = function(params,commander,callback) { var Command = function(params,commander,callback) {
this.params = params; this.params = params;
this.commander = commander; this.commander = commander;
this.callback = callback; this.callback = callback;
}; };
Command.prototype.execute = function() { Command.prototype.execute = function() {
if(this.params.length < 1) { if(this.params.length < 1) {
return "Missing filename filter"; return "Missing filename filter";
} }
var self = this, var self = this,
fs = require("fs"), path = require("path"),
path = require("path"), result = null,
result = null, wiki = this.commander.wiki,
wiki = this.commander.wiki, tiddlerFilter = this.params[0],
tiddlerFilter = this.params[0], filenameFilter = this.params[1] || "[is[tiddler]]",
filenameFilter = this.params[1] || "[is[tiddler]]", tiddlers = wiki.filterTiddlers(tiddlerFilter);
tiddlers = wiki.filterTiddlers(tiddlerFilter); $tw.utils.each(tiddlers,function(title) {
$tw.utils.each(tiddlers,function(title) { if(!result) {
if(!result) { var tiddler = self.commander.wiki.getTiddler(title);
var tiddler = self.commander.wiki.getTiddler(title); if(tiddler) {
if(tiddler) { var fileInfo = $tw.utils.generateTiddlerFileInfo(tiddler,{
var fileInfo = $tw.utils.generateTiddlerFileInfo(tiddler,{ directory: path.resolve(self.commander.outputPath),
directory: path.resolve(self.commander.outputPath), pathFilters: [filenameFilter],
pathFilters: [filenameFilter], wiki: wiki,
wiki: wiki, fileInfo: {
fileInfo: { overwrite: true
overwrite: true
}
});
if(self.commander.verbose) {
console.log("Saving \"" + title + "\" to \"" + fileInfo.filepath + "\"");
} }
try { });
$tw.utils.saveTiddlerToFileSync(tiddler,fileInfo); if(self.commander.verbose) {
} catch (err) { console.log("Saving \"" + title + "\" to \"" + fileInfo.filepath + "\"");
result = "Error saving tiddler \"" + title + "\", to file: \"" + fileInfo.filepath + "\"";
}
} else {
result = "Tiddler '" + title + "' not found";
} }
try {
$tw.utils.saveTiddlerToFileSync(tiddler,fileInfo);
} catch (err) {
result = "Error saving tiddler \"" + title + "\", to file: \"" + fileInfo.filepath + "\"";
}
} else {
result = "Tiddler '" + title + "' not found";
} }
}); }
return result; });
}; return result;
};
exports.Command = Command; exports.Command = Command;
-2
View File
@@ -9,8 +9,6 @@ Command to save several tiddlers to a folder of files
"use strict"; "use strict";
var widget = require("$:/core/modules/widgets/widget.js");
exports.info = { exports.info = {
name: "savetiddlers", name: "savetiddlers",
synchronous: true synchronous: true
+3 -3
View File
@@ -43,7 +43,7 @@ Command.prototype.execute = function() {
namedParames, namedParames,
tiddlerFilter, tiddlerFilter,
options = {}; options = {};
if (regFilter.test(this.params[1])) { if(regFilter.test(this.params[1])) {
namedParames = this.commander.extractNamedParameters(this.params.slice(1)); namedParames = this.commander.extractNamedParameters(this.params.slice(1));
tiddlerFilter = namedParames.filter || "[all[tiddlers]]"; tiddlerFilter = namedParames.filter || "[all[tiddlers]]";
} else { } else {
@@ -177,13 +177,13 @@ WikiFolderMaker.prototype.saveCustomPlugin = function(pluginTiddler) {
$tw.utils.each(pluginTiddlers,function(tiddler,title) { $tw.utils.each(pluginTiddlers,function(tiddler,title) {
if(!tiddler.title) { if(!tiddler.title) {
tiddler.title = title; tiddler.title = title;
} }
self.saveTiddler(directory,new $tw.Tiddler(tiddler)); self.saveTiddler(directory,new $tw.Tiddler(tiddler));
}); });
}; };
WikiFolderMaker.prototype.saveTiddler = function(directory,tiddler) { WikiFolderMaker.prototype.saveTiddler = function(directory,tiddler) {
var title = tiddler.fields.title, fileInfo, pathFilters, extFilters; var fileInfo, pathFilters, extFilters;
if(this.wiki.tiddlerExists("$:/config/FileSystemPaths")) { if(this.wiki.tiddlerExists("$:/config/FileSystemPaths")) {
pathFilters = this.wiki.getTiddlerText("$:/config/FileSystemPaths","").split("\n"); pathFilters = this.wiki.getTiddlerText("$:/config/FileSystemPaths","").split("\n");
} }
-1
View File
@@ -17,7 +17,6 @@ exports.info = {
}; };
var Command = function(params,commander,callback) { var Command = function(params,commander,callback) {
var self = this;
this.params = params; this.params = params;
this.commander = commander; this.commander = commander;
this.callback = callback; this.callback = callback;
+1 -4
View File
@@ -9,8 +9,6 @@ Command to modify selected tiddlers to set a field to the text of a template tid
"use strict"; "use strict";
var widget = require("$:/core/modules/widgets/widget.js");
exports.info = { exports.info = {
name: "setfield", name: "setfield",
synchronous: true synchronous: true
@@ -26,8 +24,7 @@ Command.prototype.execute = function() {
if(this.params.length < 4) { if(this.params.length < 4) {
return "Missing parameters"; return "Missing parameters";
} }
var self = this, var wiki = this.commander.wiki,
wiki = this.commander.wiki,
filter = this.params[0], filter = this.params[0],
fieldname = this.params[1] || "text", fieldname = this.params[1] || "text",
templatetitle = this.params[2], templatetitle = this.params[2],
+33 -17
View File
@@ -26,7 +26,7 @@ exports.getSubdirectories = function(dirPath) {
} }
}); });
return subdirs; return subdirs;
} };
/* /*
Recursively (and synchronously) copy a directory and all its content Recursively (and synchronously) copy a directory and all its content
@@ -46,8 +46,7 @@ exports.copyDirectory = function(srcPath,dstPath) {
} }
// Function to copy a folder full of files // Function to copy a folder full of files
var copy = function(srcPath,dstPath) { var copy = function(srcPath,dstPath) {
var srcStats = fs.lstatSync(srcPath), var srcStats = fs.lstatSync(srcPath);
dstExists = fs.existsSync(dstPath);
if(srcStats.isFile()) { if(srcStats.isFile()) {
$tw.utils.copyFile(srcPath,dstPath); $tw.utils.copyFile(srcPath,dstPath);
} else if(srcStats.isDirectory()) { } else if(srcStats.isDirectory()) {
@@ -83,7 +82,7 @@ exports.copyFile = function(srcPath,dstPath) {
dstFile = fs.openSync(dstPath,"w"), dstFile = fs.openSync(dstPath,"w"),
bytesRead = 1, bytesRead = 1,
pos = 0; pos = 0;
while (bytesRead > 0) { while(bytesRead > 0) {
bytesRead = fs.readSync(srcFile,fileBuffer,0,FILE_BUFFER_LENGTH,pos); bytesRead = fs.readSync(srcFile,fileBuffer,0,FILE_BUFFER_LENGTH,pos);
fs.writeSync(dstFile,fileBuffer,0,bytesRead); fs.writeSync(dstFile,fileBuffer,0,bytesRead);
pos += bytesRead; pos += bytesRead;
@@ -148,7 +147,7 @@ exports.deleteDirectory = function(dirPath) {
fs.unlinkSync(currPath); fs.unlinkSync(currPath);
} }
} }
fs.rmdirSync(dirPath); fs.rmdirSync(dirPath);
} }
return null; return null;
}; };
@@ -239,6 +238,10 @@ exports.generateTiddlerFileInfo = function(tiddler,options) {
// Save as a .tid file // Save as a .tid file
fileInfo.type = "application/x-tiddler"; fileInfo.type = "application/x-tiddler";
fileInfo.hasMetaFile = false; fileInfo.hasMetaFile = false;
} else if($tw.Wiki.tiddlerSerializerModules && $tw.Wiki.tiddlerSerializerModules[tiddlerType]) {
// A serializer is registered for this content type - save as a single self-contained file
fileInfo.type = tiddlerType;
fileInfo.hasMetaFile = false;
} else { } else {
// Save as a text/binary file and a .meta file // Save as a text/binary file and a .meta file
fileInfo.type = tiddlerType; fileInfo.type = tiddlerType;
@@ -255,7 +258,7 @@ exports.generateTiddlerFileInfo = function(tiddler,options) {
// Overriding to the .tid extension needs special handling // Overriding to the .tid extension needs special handling
fileInfo.type = "application/x-tiddler"; fileInfo.type = "application/x-tiddler";
fileInfo.hasMetaFile = false; fileInfo.hasMetaFile = false;
} else if (metaExt === ".json") { } else if(metaExt === ".json") {
// Overriding to the .json extension needs special handling // Overriding to the .json extension needs special handling
fileInfo.type = "application/json"; fileInfo.type = "application/json";
fileInfo.hasMetaFile = false; fileInfo.hasMetaFile = false;
@@ -345,18 +348,18 @@ exports.generateTiddlerFilepath = function(title,options) {
// Replace any Windows control codes // Replace any Windows control codes
filepath = filepath.replace(/^(con|prn|aux|nul|com[0-9]|lpt[0-9])$/i,"_$1_"); filepath = filepath.replace(/^(con|prn|aux|nul|com[0-9]|lpt[0-9])$/i,"_$1_");
// Replace any leading spaces with the same number of underscores // Replace any leading spaces with the same number of underscores
filepath = filepath.replace(/^ +/,function (u) { return u.replace(/ /g, "_")}); filepath = filepath.replace(/^ +/,function (u) { return u.replace(/ /g, "_");});
//If the path does not start with "." or ".." && a path seperator, then //If the path does not start with "." or ".." && a path seperator, then
if(!/^\.{1,2}[/\\]/g.test(filepath)) { if(!/^\.{1,2}[/\\]/g.test(filepath)) {
// Don't let the filename start with any dots because such files are invisible on *nix // Don't let the filename start with any dots because such files are invisible on *nix
filepath = filepath.replace(/^\.+/g,function (u) { return u.replace(/\./g, "_")}); filepath = filepath.replace(/^\.+/g,function (u) { return u.replace(/\./g, "_");});
} }
// Replace any Unicode control codes // Replace any Unicode control codes
filepath = filepath.replace(/[\x00-\x1f\x80-\x9f]/g,"_"); filepath = filepath.replace(/[\x00-\x1f\x80-\x9f]/g,"_");
// Replace any characters that can't be used in cross-platform filenames // Replace any characters that can't be used in cross-platform filenames
filepath = $tw.utils.transliterate(filepath.replace(/<|>|~|\:|\"|\||\?|\*|\^/g,"_")); filepath = $tw.utils.transliterate(filepath.replace(/<|>|~|\:|\"|\||\?|\*|\^/g,"_"));
// Replace any dots or spaces at the end of the extension with the same number of underscores // Replace any dots or spaces at the end of the extension with the same number of underscores
extension = extension.replace(/[\. ]+$/, function (u) { return u.replace(/[\. ]/g, "_")}); extension = extension.replace(/[\. ]+$/, function (u) { return u.replace(/[\. ]/g, "_");});
// Truncate the extension if it is too long // Truncate the extension if it is too long
if(extension.length > 32) { if(extension.length > 32) {
extension = extension.substr(0,32); extension = extension.substr(0,32);
@@ -382,9 +385,9 @@ exports.generateTiddlerFilepath = function(title,options) {
} }
// Add a uniquifier if the file already exists (default) // Add a uniquifier if the file already exists (default)
var fullPath = path.resolve(directory, filepath + extension); var fullPath = path.resolve(directory, filepath + extension);
if (!overwrite) { if(!overwrite) {
var oldPath = (options.fileInfo) ? options.fileInfo.filepath : undefined, var oldPath = (options.fileInfo) ? options.fileInfo.filepath : undefined,
count = 0; count = 0;
do { do {
fullPath = path.resolve(directory,filepath + (count ? "_" + count : "") + extension); fullPath = path.resolve(directory,filepath + (count ? "_" + count : "") + extension);
if(oldPath && oldPath == fullPath) break; if(oldPath && oldPath == fullPath) break;
@@ -401,7 +404,7 @@ exports.generateTiddlerFilepath = function(title,options) {
writePath.indexOf(path.resolve(directory)) == 0 || writePath.indexOf(path.resolve(directory)) == 0 ||
writePath.indexOf(path.resolve($tw.boot.wikiPath)) == 0 || writePath.indexOf(path.resolve($tw.boot.wikiPath)) == 0 ||
writePath.indexOf(path.resolve($tw.boot.wikiTiddlersPath,originalpath)) == 0 ); writePath.indexOf(path.resolve($tw.boot.wikiTiddlersPath,originalpath)) == 0 );
} }
if(encode) { if(encode) {
writePath = path.resolve(directory,$tw.utils.encodeURIComponentExtended(fullPath)); writePath = path.resolve(directory,$tw.utils.encodeURIComponentExtended(fullPath));
} }
@@ -417,7 +420,16 @@ Save a tiddler to a file described by the fileInfo:
*/ */
exports.saveTiddlerToFile = function(tiddler,fileInfo,callback) { exports.saveTiddlerToFile = function(tiddler,fileInfo,callback) {
$tw.utils.createDirectory(path.dirname(fileInfo.filepath)); $tw.utils.createDirectory(path.dirname(fileInfo.filepath));
if(fileInfo.hasMetaFile) { var serializer = $tw.Wiki.tiddlerSerializerModules && $tw.Wiki.tiddlerSerializerModules[fileInfo.type];
if(serializer && !fileInfo.hasMetaFile && fileInfo.type !== "application/x-tiddler" && fileInfo.type !== "application/json") {
var typeInfo = $tw.config.contentTypeInfo[fileInfo.type] || {encoding: "utf8"};
fs.writeFile(fileInfo.filepath,serializer(tiddler),typeInfo.encoding,function(err) {
if(err) {
return callback(err);
}
return callback(null,fileInfo);
});
} else if(fileInfo.hasMetaFile) {
// Save the tiddler as a separate body and meta file // Save the tiddler as a separate body and meta file
var typeInfo = $tw.config.contentTypeInfo[tiddler.fields.type || "text/plain"] || {encoding: "utf8"}; var typeInfo = $tw.config.contentTypeInfo[tiddler.fields.type || "text/plain"] || {encoding: "utf8"};
fs.writeFile(fileInfo.filepath,tiddler.fields.text || "",typeInfo.encoding,function(err) { fs.writeFile(fileInfo.filepath,tiddler.fields.text || "",typeInfo.encoding,function(err) {
@@ -459,7 +471,11 @@ Save a tiddler to a file described by the fileInfo:
*/ */
exports.saveTiddlerToFileSync = function(tiddler,fileInfo) { exports.saveTiddlerToFileSync = function(tiddler,fileInfo) {
$tw.utils.createDirectory(path.dirname(fileInfo.filepath)); $tw.utils.createDirectory(path.dirname(fileInfo.filepath));
if(fileInfo.hasMetaFile) { var serializer = $tw.Wiki.tiddlerSerializerModules && $tw.Wiki.tiddlerSerializerModules[fileInfo.type];
if(serializer && !fileInfo.hasMetaFile && fileInfo.type !== "application/x-tiddler" && fileInfo.type !== "application/json") {
var typeInfo = $tw.config.contentTypeInfo[fileInfo.type] || {encoding: "utf8"};
fs.writeFileSync(fileInfo.filepath,serializer(tiddler),typeInfo.encoding);
} else if(fileInfo.hasMetaFile) {
// Save the tiddler as a separate body and meta file // Save the tiddler as a separate body and meta file
var typeInfo = $tw.config.contentTypeInfo[tiddler.fields.type || "text/plain"] || {encoding: "utf8"}; var typeInfo = $tw.config.contentTypeInfo[tiddler.fields.type || "text/plain"] || {encoding: "utf8"};
fs.writeFileSync(fileInfo.filepath,tiddler.fields.text || "",typeInfo.encoding); fs.writeFileSync(fileInfo.filepath,tiddler.fields.text || "",typeInfo.encoding);
@@ -521,12 +537,12 @@ Cleanup old files on disk, by comparing the options values:
*/ */
exports.cleanupTiddlerFiles = function(options,callback) { exports.cleanupTiddlerFiles = function(options,callback) {
var adaptorInfo = options.adaptorInfo || {}, var adaptorInfo = options.adaptorInfo || {},
bootInfo = options.bootInfo || {}, bootInfo = options.bootInfo || {},
title = options.title || "undefined"; title = options.title || "undefined";
if(adaptorInfo.filepath && bootInfo.filepath && adaptorInfo.filepath !== bootInfo.filepath) { if(adaptorInfo.filepath && bootInfo.filepath && adaptorInfo.filepath !== bootInfo.filepath) {
$tw.utils.deleteTiddlerFile(adaptorInfo,function(err) { $tw.utils.deleteTiddlerFile(adaptorInfo,function(err) {
if(err) { if(err) {
if ((err.code == "EPERM" || err.code == "EACCES") && err.syscall == "unlink") { if((err.code == "EPERM" || err.code == "EACCES") && err.syscall == "unlink") {
// Error deleting the previous file on disk, should fail gracefully // Error deleting the previous file on disk, should fail gracefully
$tw.syncer.displayError("Server desynchronized. Error cleaning up previous file for tiddler: \""+title+"\"",err); $tw.syncer.displayError("Server desynchronized. Error cleaning up previous file for tiddler: \""+title+"\"",err);
return callback(null,bootInfo); return callback(null,bootInfo);
+1 -3
View File
@@ -10,9 +10,7 @@ Authenticator for WWW basic authentication
"use strict"; "use strict";
if($tw.node) { if($tw.node) {
var util = require("util"), var fs = require("fs"),
fs = require("fs"),
url = require("url"),
path = require("path"); path = require("path");
} }
+2 -2
View File
@@ -19,7 +19,7 @@ exports.info = {
exports.handler = function(request,response,state) { exports.handler = function(request,response,state) {
var text = state.wiki.renderTiddler(state.server.get("root-render-type"),state.server.get("root-tiddler")), var text = state.wiki.renderTiddler(state.server.get("root-render-type"),state.server.get("root-tiddler")),
responseHeaders = { responseHeaders = {
"Content-Type": state.server.get("root-serve-type") "Content-Type": state.server.get("root-serve-type")
}; };
state.sendResponse(200,responseHeaders,text); state.sendResponse(200,responseHeaders,text);
}; };
+1 -1
View File
@@ -18,7 +18,7 @@ exports.info = {
exports.handler = function(request,response,state) { exports.handler = function(request,response,state) {
var title = $tw.utils.decodeURIComponentSafe(state.params[0]), var title = $tw.utils.decodeURIComponentSafe(state.params[0]),
fields = $tw.utils.parseJSONSafe(state.data); fields = $tw.utils.parseJSONSafe(state.data);
// Pull up any subfields in the `fields` object // Pull up any subfields in the `fields` object
if(fields.fields) { if(fields.fields) {
$tw.utils.each(fields.fields,function(field,name) { $tw.utils.each(fields.fields,function(field,name) {
+17 -17
View File
@@ -9,14 +9,15 @@ Serve tiddlers over http
"use strict"; "use strict";
let fs, url, path, querystring, crypto, zlib;
if($tw.node) { if($tw.node) {
var util = require("util"), fs = require("fs"),
fs = require("fs"), url = require("url"),
url = require("url"), path = require("path"),
path = require("path"), querystring = require("querystring"),
querystring = require("querystring"), crypto = require("crypto"),
crypto = require("crypto"), zlib = require("zlib");
zlib = require("zlib");
} }
/* /*
@@ -41,7 +42,7 @@ function Server(options) {
} }
} }
// Setup the default required plugins // Setup the default required plugins
this.requiredPlugins = this.get("required-plugins").split(','); this.requiredPlugins = this.get("required-plugins").split(",");
// Initialise CORS // Initialise CORS
this.corsEnable = this.get("cors-enable") === "yes"; this.corsEnable = this.get("cors-enable") === "yes";
// Initialise CSRF // Initialise CSRF
@@ -62,9 +63,9 @@ function Server(options) {
this.authorizationPrincipals = { this.authorizationPrincipals = {
readers: (this.get("readers") || authorizedUserName).split(",").map($tw.utils.trim), readers: (this.get("readers") || authorizedUserName).split(",").map($tw.utils.trim),
writers: (this.get("writers") || authorizedUserName).split(",").map($tw.utils.trim) writers: (this.get("writers") || authorizedUserName).split(",").map($tw.utils.trim)
} };
if(this.get("admin") || authorizedUserName !== "(anon)") { if(this.get("admin") || authorizedUserName !== "(anon)") {
this.authorizationPrincipals["admin"] = (this.get("admin") || authorizedUserName).split(',').map($tw.utils.trim) this.authorizationPrincipals["admin"] = (this.get("admin") || authorizedUserName).split(",").map($tw.utils.trim);
} }
// Load and initialise authenticators // Load and initialise authenticators
$tw.modules.forEachModuleOfType("authenticator", function(title,authenticatorDefinition) { $tw.modules.forEachModuleOfType("authenticator", function(title,authenticatorDefinition) {
@@ -91,7 +92,7 @@ function Server(options) {
this.listenOptions = { this.listenOptions = {
key: fs.readFileSync(path.resolve(this.boot.wikiPath,tlsKeyFilepath),"utf8"), key: fs.readFileSync(path.resolve(this.boot.wikiPath,tlsKeyFilepath),"utf8"),
cert: fs.readFileSync(path.resolve(this.boot.wikiPath,tlsCertFilepath),"utf8"), cert: fs.readFileSync(path.resolve(this.boot.wikiPath,tlsCertFilepath),"utf8"),
passphrase: tlsPassphrase || '' passphrase: tlsPassphrase || ""
}; };
this.protocol = "https"; this.protocol = "https";
} }
@@ -116,7 +117,7 @@ encoding: the encoding of the data to send (passed to the end method of the resp
*/ */
function sendResponse(request,response,statusCode,headers,data,encoding) { function sendResponse(request,response,statusCode,headers,data,encoding) {
if(this.enableBrowserCache && (statusCode == 200)) { if(this.enableBrowserCache && (statusCode == 200)) {
var hash = crypto.createHash('md5'); var hash = crypto.createHash("md5");
// Put everything into the hash that could change and invalidate the data that // Put everything into the hash that could change and invalidate the data that
// the browser already stored. The headers the data and the encoding. // the browser already stored. The headers the data and the encoding.
hash.update(data); hash.update(data);
@@ -211,7 +212,6 @@ Server.prototype.addAuthenticator = function(AuthenticatorClass) {
Server.prototype.findMatchingRoute = function(request,state) { Server.prototype.findMatchingRoute = function(request,state) {
for(var t=0; t<this.routes.length; t++) { for(var t=0; t<this.routes.length; t++) {
var potentialRoute = this.routes[t], var potentialRoute = this.routes[t],
pathRegExp = potentialRoute.path,
pathname = state.urlInfo.pathname, pathname = state.urlInfo.pathname,
match; match;
if(state.pathPrefix) { if(state.pathPrefix) {
@@ -250,7 +250,7 @@ Check whether a given user is authorized for the specified authorizationType ("r
Server.prototype.isAuthorized = function(authorizationType,username) { Server.prototype.isAuthorized = function(authorizationType,username) {
var principals = this.authorizationPrincipals[authorizationType] || []; var principals = this.authorizationPrincipals[authorizationType] || [];
return principals.indexOf("(anon)") !== -1 || (username && (principals.indexOf("(authenticated)") !== -1 || principals.indexOf(username) !== -1)); return principals.indexOf("(anon)") !== -1 || (username && (principals.indexOf("(authenticated)") !== -1 || principals.indexOf(username) !== -1));
} };
Server.prototype.requestHandler = function(request,response,options) { Server.prototype.requestHandler = function(request,response,options) {
options = options || {}; options = options || {};
@@ -337,7 +337,7 @@ Server.prototype.requestHandler = function(request,response,options) {
request.on("end",function() { request.on("end",function() {
state.data = Buffer.concat(data); state.data = Buffer.concat(data);
route.handler(request,response,state); route.handler(request,response,state);
}) });
} else { } else {
response.writeHead(400,"Invalid bodyFormat " + route.bodyFormat + " in route " + route.method + " " + route.path.source); response.writeHead(400,"Invalid bodyFormat " + route.bodyFormat + " in route " + route.method + " " + route.path.source);
response.end(); response.end();
@@ -362,8 +362,8 @@ Server.prototype.listen = function(port,host,prefix) {
} }
// Warn if required plugins are missing // Warn if required plugins are missing
var missing = []; var missing = [];
for (var index=0; index<this.requiredPlugins.length; index++) { for(var index=0; index<this.requiredPlugins.length; index++) {
if (!this.wiki.getTiddler(this.requiredPlugins[index])) { if(!this.wiki.getTiddler(this.requiredPlugins[index])) {
missing.push(this.requiredPlugins[index]); missing.push(this.requiredPlugins[index]);
} }
} }
+7 -7
View File
@@ -9,22 +9,22 @@ Base64 UTF-8 utlity functions.
"use strict"; "use strict";
const{ TextEncoder, TextDecoder } = require("node:util"); const { TextEncoder, TextDecoder } = require("node:util");
exports.btoa = binstr => Buffer.from(binstr, "binary").toString("base64"); exports.btoa = (binstr) => Buffer.from(binstr, "binary").toString("base64");
exports.atob = b64 => Buffer.from(b64, "base64").toString("binary"); exports.atob = (b64) => Buffer.from(b64, "base64").toString("binary");
function base64ToBytes(base64) { function base64ToBytes(base64) {
const binString = exports.atob(base64); const binString = exports.atob(base64);
return Uint8Array.from(binString, m => m.codePointAt(0)); return Uint8Array.from(binString, (m) => m.codePointAt(0));
}; };
function bytesToBase64(bytes) { function bytesToBase64(bytes) {
const binString = Array.from(bytes, byte => String.fromCodePoint(byte)).join(""); const binString = Array.from(bytes, (byte) => String.fromCodePoint(byte)).join("");
return exports.btoa(binString); return exports.btoa(binString);
}; };
exports.base64EncodeUtf8 = str => bytesToBase64(new TextEncoder().encode(str)); exports.base64EncodeUtf8 = (str) => bytesToBase64(new TextEncoder().encode(str));
exports.base64DecodeUtf8 = str => new TextDecoder().decode(base64ToBytes(str)); exports.base64DecodeUtf8 = (str) => new TextDecoder().decode(base64ToBytes(str));
+1 -2
View File
@@ -16,8 +16,7 @@ ignoreEnvironmentVariables: defaults to false
*/ */
exports.getAllPlugins = function(options) { exports.getAllPlugins = function(options) {
options = options || {}; options = options || {};
var fs = require("fs"), var path = require("path"),
path = require("path"),
tiddlers = {}; tiddlers = {};
// Collect up the library plugins // Collect up the library plugins
var collectPlugins = function(folder) { var collectPlugins = function(folder) {
+2
View File
@@ -15,6 +15,8 @@ Listing/Preview/TextRaw: Text (Raw)
Listing/Preview/Fields: Fields Listing/Preview/Fields: Fields
Listing/Preview/Diff: Diff Listing/Preview/Diff: Diff
Listing/Preview/DiffFields: Diff (Fields) Listing/Preview/DiffFields: Diff (Fields)
Listing/ImportOptions/Caption: Import options
Listing/ImportOptions/NoMatch: No import options apply to these files.
Listing/Rename/Tooltip: Rename tiddler before importing Listing/Rename/Tooltip: Rename tiddler before importing
Listing/Rename/Prompt: Rename to: Listing/Rename/Prompt: Rename to:
Listing/Rename/ConfirmRename: Rename tiddler Listing/Rename/ConfirmRename: Rename tiddler
+1 -1
View File
@@ -10,7 +10,7 @@ Advanced/ShadowInfo/Shadow/Hint: The tiddler <$link to=<<infoTiddler>>><$text te
Advanced/ShadowInfo/Shadow/Source: It is defined in the plugin <$link to=<<pluginTiddler>>><$text text=<<pluginTiddler>>/></$link> Advanced/ShadowInfo/Shadow/Source: It is defined in the plugin <$link to=<<pluginTiddler>>><$text text=<<pluginTiddler>>/></$link>
Advanced/ShadowInfo/OverriddenShadow/Hint: It is overridden by an ordinary tiddler Advanced/ShadowInfo/OverriddenShadow/Hint: It is overridden by an ordinary tiddler
Advanced/CascadeInfo/Heading: Cascade Details Advanced/CascadeInfo/Heading: Cascade Details
Advanced/CascadeInfo/Hint: These are the view template segments (tagged <<tag "$:/tags/ViewTemplate">>) using a cascade filter and their resulting template for the current tiddler. Advanced/CascadeInfo/Hint: These are the view template segments that are resolved for each of the system view template cascades
Advanced/CascadeInfo/Detail/View: View Advanced/CascadeInfo/Detail/View: View
Advanced/CascadeInfo/Detail/ActiveCascadeFilter: Active cascade filter Advanced/CascadeInfo/Detail/ActiveCascadeFilter: Active cascade filter
Advanced/CascadeInfo/Detail/Template: Template Advanced/CascadeInfo/Detail/Template: Template
+14 -4
View File
@@ -2,6 +2,9 @@
title: $:/core/modules/background-actions.js title: $:/core/modules/background-actions.js
type: application/javascript type: application/javascript
module-type: global module-type: global
Class to dispatch actions when filters change
\*/ \*/
"use strict"; "use strict";
@@ -12,16 +15,16 @@ class BackgroundActionDispatcher {
this.wiki = wiki; this.wiki = wiki;
this.nextTrackedFilterId = 1; this.nextTrackedFilterId = 1;
this.trackedFilters = new Map(); // Use Map for better key management this.trackedFilters = new Map(); // Use Map for better key management
// Track the filter for the background actions
this.filterTracker.track({ this.filterTracker.track({
filterString: "[all[tiddlers+shadows]tag[$:/tags/BackgroundAction]!is[draft]]", filterString: "[all[tiddlers+shadows]tag[$:/tags/BackgroundAction]!is[draft]]",
fnEnter: title => this.trackFilter(title), fnEnter: (title) => this.trackFilter(title),
fnLeave: (title, enterValue) => this.untrackFilter(enterValue), fnLeave: (title, enterValue) => this.untrackFilter(enterValue),
fnChange: (title, enterValue) => { fnChange: (title, enterValue) => {
this.untrackFilter(enterValue); this.untrackFilter(enterValue);
return this.trackFilter(title); return this.trackFilter(title);
}, },
fnProcess: changes => this.process(changes) fnProcess: (changes) => this.process(changes)
}); });
} }
@@ -53,6 +56,13 @@ class BackgroundActionDispatcher {
} }
} }
/*
Represents an individual tracked filter. Options include:
wiki: wiki to use
title: title of the tiddler being tracked
trackFilter: filter string to track changes
actions: actions to be executed when the filter changes
*/
class BackgroundActionTracker { class BackgroundActionTracker {
constructor({wiki, title, trackFilter, actions}) { constructor({wiki, title, trackFilter, actions}) {
this.wiki = wiki; this.wiki = wiki;
@@ -65,7 +75,7 @@ class BackgroundActionTracker {
filterString: this.trackFilter, filterString: this.trackFilter,
fnEnter: () => { this.hasChanged = true; }, fnEnter: () => { this.hasChanged = true; },
fnLeave: () => { this.hasChanged = true; }, fnLeave: () => { this.hasChanged = true; },
fnProcess: changes => { fnProcess: (changes) => {
if(this.hasChanged) { if(this.hasChanged) {
this.hasChanged = false; this.hasChanged = false;
console.log("Processing background action", this.title); console.log("Processing background action", this.title);
+3
View File
@@ -2,6 +2,9 @@
title: $:/core/modules/config.js title: $:/core/modules/config.js
type: application/javascript type: application/javascript
module-type: config module-type: config
Core configuration constants
\*/ \*/
"use strict"; "use strict";
+26 -5
View File
@@ -2,6 +2,9 @@
title: $:/core/modules/deserializers.js title: $:/core/modules/deserializers.js
type: application/javascript type: application/javascript
module-type: tiddlerdeserializer module-type: tiddlerdeserializer
Functions to deserialise tiddlers from a block of text
\*/ \*/
"use strict"; "use strict";
@@ -34,6 +37,12 @@ exports["application/json"] = function(text,fields) {
return results; return results;
}; };
/*
Parse an HTML file into tiddlers. There are three possibilities:
# A TiddlyWiki classic HTML file containing `text/x-tiddlywiki` tiddlers
# A TiddlyWiki5 HTML file containing `text/vnd.tiddlywiki` tiddlers
# An ordinary HTML file
*/
exports["text/html"] = function(text,fields) { exports["text/html"] = function(text,fields) {
var results = []; var results = [];
// Check if we've got an old-style store area // Check if we've got an old-style store area
@@ -51,11 +60,11 @@ exports["text/html"] = function(text,fields) {
results.push.apply(results,deserializeNewStoreArea(text,newStoreAreaMarkerRegExp.lastIndex,newStoreAreaMatch[1],fields)); results.push.apply(results,deserializeNewStoreArea(text,newStoreAreaMarkerRegExp.lastIndex,newStoreAreaMatch[1],fields));
newStoreAreaMatch = newStoreAreaMarkerRegExp.exec(text); newStoreAreaMatch = newStoreAreaMarkerRegExp.exec(text);
} }
// Return if we had either an old-style or a new-style store area
if(storeAreaMatch || haveHadNewStoreArea) { if(storeAreaMatch || haveHadNewStoreArea) {
return results; return results;
} }
// Otherwise, check whether we've got an encrypted file
var encryptedStoreArea = $tw.utils.extractEncryptedStoreArea(text); var encryptedStoreArea = $tw.utils.extractEncryptedStoreArea(text);
if(encryptedStoreArea) { if(encryptedStoreArea) {
// If so, attempt to decrypt it using the current password // If so, attempt to decrypt it using the current password
@@ -115,18 +124,30 @@ function deserializeStoreArea(text,storeAreaEnd,isTiddlyWiki5,fields) {
return results; return results;
} }
var deserializeTiddlerDiv = function(text) { /*
Utility function to parse an old-style tiddler DIV in a *.tid file. It looks like this:
<div title="Title" creator="JoeBloggs" modifier="JoeBloggs" created="201102111106" modified="201102111310" tags="myTag [[my long tag]]">
<pre>The text of the tiddler (without the expected HTML encoding).
</pre>
</div>
Note that the field attributes are HTML encoded, but that the body of the <PRE> tag is not encoded.
When these tiddler DIVs are encountered within a TiddlyWiki HTML file then the body is encoded in the usual way.
*/
var deserializeTiddlerDiv = function(text /* [,fields] */) {
// Slot together the default results // Slot together the default results
var result = {}; var result = {};
if(arguments.length > 1) { if(arguments.length > 1) {
for(var f=1; f<arguments.length; f++) { for(var f=1; f<arguments.length; f++) {
var fields = arguments[f]; var fields = arguments[f];
for(var t in fields) { for(var t in fields) {
result[t] = fields[t]; result[t] = fields[t];
} }
} }
} }
// Parse the DIV body
var startRegExp = /^\s*<div\s+([^>]*)>(\s*<pre>)?/gi, var startRegExp = /^\s*<div\s+([^>]*)>(\s*<pre>)?/gi,
endRegExp, endRegExp,
match = startRegExp.exec(text); match = startRegExp.exec(text);
+41 -5
View File
@@ -2,6 +2,9 @@
title: $:/core/modules/editor/engines/framed.js title: $:/core/modules/editor/engines/framed.js
type: application/javascript type: application/javascript
module-type: library module-type: library
Text editor engine based on a simple input or textarea within an iframe. This is done so that the selection is preserved even when clicking away from the textarea
\*/ \*/
"use strict"; "use strict";
@@ -53,7 +56,7 @@ function FramedEngine(options) {
} else { } else {
this.domNode.value = this.value; this.domNode.value = this.value;
} }
// Set the attributes
if(this.widget.editType && this.widget.editTag !== "textarea") { if(this.widget.editType && this.widget.editTag !== "textarea") {
this.domNode.setAttribute("type",this.widget.editType); this.domNode.setAttribute("type",this.widget.editType);
} }
@@ -75,7 +78,7 @@ function FramedEngine(options) {
if(this.widget.isDisabled === "yes") { if(this.widget.isDisabled === "yes") {
this.domNode.setAttribute("disabled",true); this.domNode.setAttribute("disabled",true);
} }
// Copy the styles from the dummy textarea
this.copyStyles(); this.copyStyles();
// Add event listeners // Add event listeners
$tw.utils.addEventListeners(this.domNode,[ $tw.utils.addEventListeners(this.domNode,[
@@ -96,10 +99,13 @@ function FramedEngine(options) {
{name: "click",handlerObject: this.widget,handlerMethod: "handleClickEvent"} {name: "click",handlerObject: this.widget,handlerMethod: "handleClickEvent"}
]); ]);
} }
// Insert the element into the DOM
this.iframeDoc.body.appendChild(this.domNode); this.iframeDoc.body.appendChild(this.domNode);
} }
/*
Copy styles from the dummy text area to the textarea in the iframe
*/
FramedEngine.prototype.copyStyles = function() { FramedEngine.prototype.copyStyles = function() {
// Copy all styles // Copy all styles
$tw.utils.copyStyles(this.dummyTextArea,this.domNode); $tw.utils.copyStyles(this.dummyTextArea,this.domNode);
@@ -121,11 +127,14 @@ FramedEngine.prototype.setText = function(text,type) {
if(this.domNode.ownerDocument.activeElement !== this.domNode) { if(this.domNode.ownerDocument.activeElement !== this.domNode) {
this.updateDomNodeText(text); this.updateDomNodeText(text);
} }
// Fix the height if needed
this.fixHeight(); this.fixHeight();
} }
}; };
/*
Update the DomNode with the new text
*/
FramedEngine.prototype.updateDomNodeText = function(text) { FramedEngine.prototype.updateDomNodeText = function(text) {
try { try {
this.domNode.value = text; this.domNode.value = text;
@@ -134,10 +143,16 @@ FramedEngine.prototype.updateDomNodeText = function(text) {
} }
}; };
/*
Get the text of the engine
*/
FramedEngine.prototype.getText = function() { FramedEngine.prototype.getText = function() {
return this.domNode.value; return this.domNode.value;
}; };
/*
Fix the height of textarea to fit content
*/
FramedEngine.prototype.fixHeight = function() { FramedEngine.prototype.fixHeight = function() {
// Make sure styles are updated // Make sure styles are updated
this.copyStyles(); this.copyStyles();
@@ -157,6 +172,9 @@ FramedEngine.prototype.fixHeight = function() {
} }
}; };
/*
Focus the engine node
*/
FramedEngine.prototype.focus = function() { FramedEngine.prototype.focus = function() {
if(this.domNode.focus) { if(this.domNode.focus) {
this.domNode.focus(); this.domNode.focus();
@@ -166,25 +184,37 @@ FramedEngine.prototype.focus = function() {
} }
}; };
/*
Handle a focus event
*/
FramedEngine.prototype.handleFocusEvent = function(event) { FramedEngine.prototype.handleFocusEvent = function(event) {
if(this.widget.editCancelPopups) { if(this.widget.editCancelPopups) {
$tw.popup.cancel(0); $tw.popup.cancel(0);
} }
}; };
/*
Handle a keydown event
*/
FramedEngine.prototype.handleKeydownEvent = function(event) { FramedEngine.prototype.handleKeydownEvent = function(event) {
if ($tw.keyboardManager.handleKeydownEvent(event, {onlyPriority: true})) { if($tw.keyboardManager.handleKeydownEvent(event, {onlyPriority: true})) {
return true; return true;
} }
return this.widget.handleKeydownEvent(event); return this.widget.handleKeydownEvent(event);
}; };
/*
Handle a click
*/
FramedEngine.prototype.handleClickEvent = function(event) { FramedEngine.prototype.handleClickEvent = function(event) {
this.fixHeight(); this.fixHeight();
return true; return true;
}; };
/*
Handle a dom "input" event which occurs when the text has changed
*/
FramedEngine.prototype.handleInputEvent = function(event) { FramedEngine.prototype.handleInputEvent = function(event) {
this.widget.saveChanges(this.getText()); this.widget.saveChanges(this.getText());
this.fixHeight(); this.fixHeight();
@@ -194,6 +224,9 @@ FramedEngine.prototype.handleInputEvent = function(event) {
return true; return true;
}; };
/*
Create a blank structure representing a text operation
*/
FramedEngine.prototype.createTextOperation = function() { FramedEngine.prototype.createTextOperation = function() {
var operation = { var operation = {
text: this.domNode.value, text: this.domNode.value,
@@ -209,6 +242,9 @@ FramedEngine.prototype.createTextOperation = function() {
return operation; return operation;
}; };
/*
Execute a text operation
*/
FramedEngine.prototype.executeTextOperation = function(operation) { FramedEngine.prototype.executeTextOperation = function(operation) {
// Perform the required changes to the text area and the underlying tiddler // Perform the required changes to the text area and the underlying tiddler
var newText = operation.text; var newText = operation.text;
+33 -3
View File
@@ -2,6 +2,9 @@
title: $:/core/modules/editor/engines/simple.js title: $:/core/modules/editor/engines/simple.js
type: application/javascript type: application/javascript
module-type: library module-type: library
Text editor engine based on a simple input or textarea tag
\*/ \*/
"use strict"; "use strict";
@@ -27,7 +30,7 @@ function SimpleEngine(options) {
} else { } else {
this.domNode.value = this.value; this.domNode.value = this.value;
} }
// Set the attributes
if(this.widget.editType && this.widget.editTag !== "textarea") { if(this.widget.editType && this.widget.editTag !== "textarea") {
this.domNode.setAttribute("type",this.widget.editType); this.domNode.setAttribute("type",this.widget.editType);
} }
@@ -52,7 +55,7 @@ function SimpleEngine(options) {
if(this.widget.isDisabled === "yes") { if(this.widget.isDisabled === "yes") {
this.domNode.setAttribute("disabled",true); this.domNode.setAttribute("disabled",true);
} }
// Add an input event handler
$tw.utils.addEventListeners(this.domNode,[ $tw.utils.addEventListeners(this.domNode,[
{name: "focus", handlerObject: this, handlerMethod: "handleFocusEvent"}, {name: "focus", handlerObject: this, handlerMethod: "handleFocusEvent"},
{name: "input", handlerObject: this, handlerMethod: "handleInputEvent"} {name: "input", handlerObject: this, handlerMethod: "handleInputEvent"}
@@ -62,16 +65,22 @@ function SimpleEngine(options) {
this.widget.domNodes.push(this.domNode); this.widget.domNodes.push(this.domNode);
} }
/*
Set the text of the engine if it doesn't currently have focus
*/
SimpleEngine.prototype.setText = function(text,type) { SimpleEngine.prototype.setText = function(text,type) {
if(!this.domNode.isTiddlyWikiFakeDom) { if(!this.domNode.isTiddlyWikiFakeDom) {
if(this.domNode.ownerDocument.activeElement !== this.domNode || text === "") { if(this.domNode.ownerDocument.activeElement !== this.domNode || text === "") {
this.updateDomNodeText(text); this.updateDomNodeText(text);
} }
// Fix the height if needed
this.fixHeight(); this.fixHeight();
} }
}; };
/*
Update the DomNode with the new text
*/
SimpleEngine.prototype.updateDomNodeText = function(text) { SimpleEngine.prototype.updateDomNodeText = function(text) {
try { try {
this.domNode.value = text; this.domNode.value = text;
@@ -80,10 +89,16 @@ SimpleEngine.prototype.updateDomNodeText = function(text) {
} }
}; };
/*
Get the text of the engine
*/
SimpleEngine.prototype.getText = function() { SimpleEngine.prototype.getText = function() {
return this.domNode.value; return this.domNode.value;
}; };
/*
Fix the height of textarea to fit content
*/
SimpleEngine.prototype.fixHeight = function() { SimpleEngine.prototype.fixHeight = function() {
// If .editRows is initialised, it takes precedence // If .editRows is initialised, it takes precedence
if((this.widget.editTag === "textarea") && !this.widget.editRows) { if((this.widget.editTag === "textarea") && !this.widget.editRows) {
@@ -99,6 +114,9 @@ SimpleEngine.prototype.fixHeight = function() {
} }
}; };
/*
Focus the engine node
*/
SimpleEngine.prototype.focus = function() { SimpleEngine.prototype.focus = function() {
if(this.domNode.focus) { if(this.domNode.focus) {
this.domNode.focus(); this.domNode.focus();
@@ -108,6 +126,9 @@ SimpleEngine.prototype.focus = function() {
} }
}; };
/*
Handle a dom "input" event which occurs when the text has changed
*/
SimpleEngine.prototype.handleInputEvent = function(event) { SimpleEngine.prototype.handleInputEvent = function(event) {
this.widget.saveChanges(this.getText()); this.widget.saveChanges(this.getText());
this.fixHeight(); this.fixHeight();
@@ -117,6 +138,9 @@ SimpleEngine.prototype.handleInputEvent = function(event) {
return true; return true;
}; };
/*
Handle a dom "focus" event
*/
SimpleEngine.prototype.handleFocusEvent = function(event) { SimpleEngine.prototype.handleFocusEvent = function(event) {
if(this.widget.editCancelPopups) { if(this.widget.editCancelPopups) {
$tw.popup.cancel(0); $tw.popup.cancel(0);
@@ -132,10 +156,16 @@ SimpleEngine.prototype.handleFocusEvent = function(event) {
return true; return true;
}; };
/*
Create a blank structure representing a text operation
*/
SimpleEngine.prototype.createTextOperation = function() { SimpleEngine.prototype.createTextOperation = function() {
return null; return null;
}; };
/*
Execute a text operation
*/
SimpleEngine.prototype.executeTextOperation = function(operation) { SimpleEngine.prototype.executeTextOperation = function(operation) {
}; };
+47 -14
View File
@@ -2,12 +2,16 @@
title: $:/core/modules/editor/factory.js title: $:/core/modules/editor/factory.js
type: application/javascript type: application/javascript
module-type: library module-type: library
Factory for constructing text editor widgets with specified engines for the toolbar and non-toolbar cases
\*/ \*/
"use strict"; "use strict";
var DEFAULT_MIN_TEXT_AREA_HEIGHT = "100px"; // Minimum height of textareas in pixels var DEFAULT_MIN_TEXT_AREA_HEIGHT = "100px"; // Minimum height of textareas in pixels
// Configuration tiddlers
var HEIGHT_MODE_TITLE = "$:/config/TextEditor/EditorHeight/Mode"; var HEIGHT_MODE_TITLE = "$:/config/TextEditor/EditorHeight/Mode";
var ENABLE_TOOLBAR_TITLE = "$:/config/TextEditor/EnableToolbar"; var ENABLE_TOOLBAR_TITLE = "$:/config/TextEditor/EnableToolbar";
@@ -51,12 +55,12 @@ function editTextWidgetFactory(toolbarEngine,nonToolbarEngine) {
var editInfo = this.getEditInfo(), var editInfo = this.getEditInfo(),
Engine = this.editShowToolbar ? toolbarEngine : nonToolbarEngine; Engine = this.editShowToolbar ? toolbarEngine : nonToolbarEngine;
this.engine = new Engine({ this.engine = new Engine({
widget: this, widget: this,
value: editInfo.value, value: editInfo.value,
type: editInfo.type, type: editInfo.type,
parentNode: parent, parentNode: parent,
nextSibling: nextSibling nextSibling: nextSibling
}); });
// Call the postRender hook // Call the postRender hook
if(this.postRender) { if(this.postRender) {
this.postRender(); this.postRender();
@@ -137,6 +141,9 @@ function editTextWidgetFactory(toolbarEngine,nonToolbarEngine) {
return {value: value || "", type: type, update: update}; return {value: value || "", type: type, update: update};
}; };
/*
Handle an edit text operation message from the toolbar
*/
EditTextWidget.prototype.handleEditTextOperationMessage = function(event) { EditTextWidget.prototype.handleEditTextOperationMessage = function(event) {
// Prepare information about the operation // Prepare information about the operation
var operation = this.engine.createTextOperation(); var operation = this.engine.createTextOperation();
@@ -145,13 +152,16 @@ function editTextWidgetFactory(toolbarEngine,nonToolbarEngine) {
if(handler) { if(handler) {
handler.call(this,event,operation); handler.call(this,event,operation);
} }
// Execute the operation via the engine
var newText = this.engine.executeTextOperation(operation); var newText = this.engine.executeTextOperation(operation);
// Fix the tiddler height and save changes // Fix the tiddler height and save changes
this.engine.fixHeight(); this.engine.fixHeight();
this.saveChanges(newText); this.saveChanges(newText);
}; };
/*
Compute the internal state of the widget
*/
EditTextWidget.prototype.execute = function() { EditTextWidget.prototype.execute = function() {
// Get our parameters // Get our parameters
this.editTitle = this.getAttribute("tiddler",this.getVariable("currentTiddler")); this.editTitle = this.getAttribute("tiddler",this.getVariable("currentTiddler"));
@@ -191,7 +201,7 @@ function editTextWidgetFactory(toolbarEngine,nonToolbarEngine) {
} }
type = type || "text"; type = type || "text";
} }
// Get the rest of our parameters
this.editTag = this.getAttribute("tag",tag) || "input"; this.editTag = this.getAttribute("tag",tag) || "input";
this.editType = this.getAttribute("type",type); this.editType = this.getAttribute("type",type);
// Make the child widgets // Make the child widgets
@@ -201,13 +211,16 @@ function editTextWidgetFactory(toolbarEngine,nonToolbarEngine) {
this.editShowToolbar = (this.editShowToolbar === "yes") && !!(this.children && this.children.length > 0) && (!this.document.isTiddlyWikiFakeDom); this.editShowToolbar = (this.editShowToolbar === "yes") && !!(this.children && this.children.length > 0) && (!this.document.isTiddlyWikiFakeDom);
}; };
/*
Selectively refreshes the widget if needed. Returns true if the widget or any of its children needed re-rendering
*/
EditTextWidget.prototype.refresh = function(changedTiddlers) { EditTextWidget.prototype.refresh = function(changedTiddlers) {
var changedAttributes = this.computeAttributes(); var changedAttributes = this.computeAttributes();
// Completely rerender if any of our attributes have changed // Completely rerender if any of our attributes have changed
if(changedAttributes.tiddler || changedAttributes.field || changedAttributes.index || changedAttributes["default"] || changedAttributes["class"] || changedAttributes.placeholder || changedAttributes.size || changedAttributes.autoHeight || changedAttributes.minHeight || changedAttributes.focusPopup || changedAttributes.rows || changedAttributes.tabindex || changedAttributes.cancelPopups || changedAttributes.inputActions || changedAttributes.refreshTitle || changedAttributes.autocomplete || changedTiddlers[HEIGHT_MODE_TITLE] || changedTiddlers[ENABLE_TOOLBAR_TITLE] || changedTiddlers["$:/palette"] || changedAttributes.disabled || changedAttributes.fileDrop) { if(changedAttributes.tiddler || changedAttributes.field || changedAttributes.index || changedAttributes["default"] || changedAttributes["class"] || changedAttributes.placeholder || changedAttributes.size || changedAttributes.autoHeight || changedAttributes.minHeight || changedAttributes.focusPopup || changedAttributes.rows || changedAttributes.tabindex || changedAttributes.cancelPopups || changedAttributes.inputActions || changedAttributes.refreshTitle || changedAttributes.autocomplete || changedTiddlers[HEIGHT_MODE_TITLE] || changedTiddlers[ENABLE_TOOLBAR_TITLE] || changedTiddlers["$:/palette"] || changedAttributes.disabled || changedAttributes.fileDrop) {
this.refreshSelf(); this.refreshSelf();
return true; return true;
} else if (changedTiddlers[this.editRefreshTitle]) { } else if(changedTiddlers[this.editRefreshTitle]) {
this.engine.updateDomNodeText(this.getEditInfo().value); this.engine.updateDomNodeText(this.getEditInfo().value);
} else if(changedTiddlers[this.editTitle]) { } else if(changedTiddlers[this.editTitle]) {
var editInfo = this.getEditInfo(); var editInfo = this.getEditInfo();
@@ -221,14 +234,24 @@ function editTextWidgetFactory(toolbarEngine,nonToolbarEngine) {
} }
}; };
/*
Update the editor with new text. This method is separate from updateEditorDomNode()
so that subclasses can override updateEditor() and still use updateEditorDomNode()
*/
EditTextWidget.prototype.updateEditor = function(text,type) { EditTextWidget.prototype.updateEditor = function(text,type) {
this.updateEditorDomNode(text,type); this.updateEditorDomNode(text,type);
}; };
/*
Update the editor dom node with new text
*/
EditTextWidget.prototype.updateEditorDomNode = function(text,type) { EditTextWidget.prototype.updateEditorDomNode = function(text,type) {
this.engine.setText(text,type); this.engine.setText(text,type);
}; };
/*
Save changes back to the tiddler store
*/
EditTextWidget.prototype.saveChanges = function(text) { EditTextWidget.prototype.saveChanges = function(text) {
var editInfo = this.getEditInfo(); var editInfo = this.getEditInfo();
if(text !== editInfo.value) { if(text !== editInfo.value) {
@@ -236,6 +259,9 @@ function editTextWidgetFactory(toolbarEngine,nonToolbarEngine) {
} }
}; };
/*
Handle a dom "keydown" event, which we'll bubble up to our container for the keyboard widgets benefit
*/
EditTextWidget.prototype.handleKeydownEvent = function(event) { EditTextWidget.prototype.handleKeydownEvent = function(event) {
// Check for a keyboard shortcut // Check for a keyboard shortcut
if(this.toolbarNode) { if(this.toolbarNode) {
@@ -248,25 +274,28 @@ function editTextWidgetFactory(toolbarEngine,nonToolbarEngine) {
}); });
if($tw.keyboardManager.checkKeyDescriptors(event,keyInfoArray)) { if($tw.keyboardManager.checkKeyDescriptors(event,keyInfoArray)) {
var clickEvent = this.document.createEvent("Events"); var clickEvent = this.document.createEvent("Events");
clickEvent.initEvent("click",true,false); clickEvent.initEvent("click",true,false);
el.dispatchEvent(clickEvent); el.dispatchEvent(clickEvent);
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
return true; return true;
} }
} }
} }
// Propogate the event to the container
if(this.propogateKeydownEvent(event)) { if(this.propogateKeydownEvent(event)) {
// Ignore the keydown if it was already handled // Ignore the keydown if it was already handled
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
return true; return true;
} }
// Otherwise, process the keydown normally
return false; return false;
}; };
/*
Propogate keydown events to our container for the keyboard widgets benefit
*/
EditTextWidget.prototype.propogateKeydownEvent = function(event) { EditTextWidget.prototype.propogateKeydownEvent = function(event) {
var newEvent = this.cloneEvent(event,["keyCode","code","which","key","metaKey","ctrlKey","altKey","shiftKey"]); var newEvent = this.cloneEvent(event,["keyCode","code","which","key","metaKey","ctrlKey","altKey","shiftKey"]);
return !this.parentDomNode.dispatchEvent(newEvent); return !this.parentDomNode.dispatchEvent(newEvent);
@@ -289,12 +318,16 @@ function editTextWidgetFactory(toolbarEngine,nonToolbarEngine) {
return dispatchNode.dispatchEvent(newEvent); return dispatchNode.dispatchEvent(newEvent);
}; };
/*
Propogate drag and drop events with File data to our container for the dropzone widgets benefit.
If there are no Files, let the browser handle it.
*/
EditTextWidget.prototype.handleDropEvent = function(event) { EditTextWidget.prototype.handleDropEvent = function(event) {
if($tw.utils.dragEventContainsFiles(event)) { if($tw.utils.dragEventContainsFiles(event)) {
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
this.dispatchDOMEvent(this.cloneEvent(event,["dataTransfer"])); this.dispatchDOMEvent(this.cloneEvent(event,["dataTransfer"]));
} }
}; };
EditTextWidget.prototype.handlePasteEvent = function(event) { EditTextWidget.prototype.handlePasteEvent = function(event) {
@@ -2,6 +2,9 @@
title: $:/core/modules/editor/operations/bitmap/clear.js title: $:/core/modules/editor/operations/bitmap/clear.js
type: application/javascript type: application/javascript
module-type: bitmapeditoroperation module-type: bitmapeditoroperation
Bitmap editor operation to clear the image
\*/ \*/
"use strict"; "use strict";
@@ -2,6 +2,9 @@
title: $:/core/modules/editor/operations/bitmap/resize.js title: $:/core/modules/editor/operations/bitmap/resize.js
type: application/javascript type: application/javascript
module-type: bitmapeditoroperation module-type: bitmapeditoroperation
Bitmap editor operation to resize the image
\*/ \*/
"use strict"; "use strict";
@@ -14,7 +17,7 @@ exports["resize"] = function(event) {
if(newWidth > 0 && newHeight > 0 && !(newWidth === this.currCanvas.width && newHeight === this.currCanvas.height)) { if(newWidth > 0 && newHeight > 0 && !(newWidth === this.currCanvas.width && newHeight === this.currCanvas.height)) {
this.changeCanvasSize(newWidth,newHeight); this.changeCanvasSize(newWidth,newHeight);
} }
// Update the input controls
this.refreshToolbar(); this.refreshToolbar();
// Save the image into the tiddler // Save the image into the tiddler
this.saveChanges(); this.saveChanges();
@@ -2,6 +2,9 @@
title: $:/core/modules/editor/operations/bitmap/rotate-left.js title: $:/core/modules/editor/operations/bitmap/rotate-left.js
type: application/javascript type: application/javascript
module-type: bitmapeditoroperation module-type: bitmapeditoroperation
Bitmap editor operation to rotate the image left by 90 degrees
\*/ \*/
"use strict"; "use strict";
@@ -2,12 +2,15 @@
title: $:/core/modules/editor/operations/text/excise.js title: $:/core/modules/editor/operations/text/excise.js
type: application/javascript type: application/javascript
module-type: texteditoroperation module-type: texteditoroperation
Text editor operation to excise the selection to a new tiddler
\*/ \*/
"use strict"; "use strict";
function isMarkdown(mediaType) { function isMarkdown(mediaType) {
return mediaType === 'text/markdown' || mediaType === 'text/x-markdown'; return mediaType === "text/markdown" || mediaType === "text/x-markdown";
} }
exports["excise"] = function(event,operation) { exports["excise"] = function(event,operation) {
@@ -2,6 +2,9 @@
title: $:/core/modules/editor/operations/text/insert-text.js title: $:/core/modules/editor/operations/text/insert-text.js
type: application/javascript type: application/javascript
module-type: texteditoroperation module-type: texteditoroperation
Text editor operation insert text at the caret position. If there is a selection it is replaced.
\*/ \*/
"use strict"; "use strict";
@@ -2,6 +2,9 @@
title: $:/core/modules/editor/operations/text/make-link.js title: $:/core/modules/editor/operations/text/make-link.js
type: application/javascript type: application/javascript
module-type: texteditoroperation module-type: texteditoroperation
Text editor operation to make a link
\*/ \*/
"use strict"; "use strict";
@@ -2,6 +2,9 @@
title: $:/core/modules/editor/operations/text/prefix-lines.js title: $:/core/modules/editor/operations/text/prefix-lines.js
type: application/javascript type: application/javascript
module-type: texteditoroperation module-type: texteditoroperation
Text editor operation to add a prefix to the selected lines
\*/ \*/
"use strict"; "use strict";
@@ -23,16 +26,16 @@ exports["prefix-lines"] = function(event,operation) {
line = line.substring(event.paramObject.character.length); line = line.substring(event.paramObject.character.length);
count++; count++;
} }
// Remove any whitespace
while(line.charAt(0) === " ") { while(line.charAt(0) === " ") {
line = line.substring(1); line = line.substring(1);
} }
// We're done if we removed the exact required prefix, otherwise add it
if(count !== targetCount) { if(count !== targetCount) {
// Apply the prefix // Apply the prefix
line = prefix + " " + line; line = prefix + " " + line;
} }
// Save the modified line
lines[index] = line; lines[index] = line;
}); });
// Stitch the replacement text together and set the selection // Stitch the replacement text together and set the selection
@@ -2,6 +2,9 @@
title: $:/core/modules/editor/operations/text/replace-all.js title: $:/core/modules/editor/operations/text/replace-all.js
type: application/javascript type: application/javascript
module-type: texteditoroperation module-type: texteditoroperation
Text editor operation to replace the entire text
\*/ \*/
"use strict"; "use strict";
@@ -2,6 +2,9 @@
title: $:/core/modules/editor/operations/text/replace-selection.js title: $:/core/modules/editor/operations/text/replace-selection.js
type: application/javascript type: application/javascript
module-type: texteditoroperation module-type: texteditoroperation
Text editor operation to replace the selection
\*/ \*/
"use strict"; "use strict";
@@ -2,6 +2,9 @@
title: $:/core/modules/editor/operations/text/save-selection.js title: $:/core/modules/editor/operations/text/save-selection.js
type: application/javascript type: application/javascript
module-type: texteditoroperation module-type: texteditoroperation
Text editor operation to save the current selection in a specified tiddler
\*/ \*/
"use strict"; "use strict";
@@ -2,6 +2,9 @@
title: $:/core/modules/editor/operations/text/wrap-lines.js title: $:/core/modules/editor/operations/text/wrap-lines.js
type: application/javascript type: application/javascript
module-type: texteditoroperation module-type: texteditoroperation
Text editor operation to wrap the selected lines with a prefix and suffix
\*/ \*/
"use strict"; "use strict";
@@ -12,14 +15,14 @@ exports["wrap-lines"] = function(event,operation) {
if($tw.utils.endsWith(operation.text.substring(0,operation.selStart), prefix + "\n") && if($tw.utils.endsWith(operation.text.substring(0,operation.selStart), prefix + "\n") &&
$tw.utils.startsWith(operation.text.substring(operation.selEnd), "\n" + suffix)) { $tw.utils.startsWith(operation.text.substring(operation.selEnd), "\n" + suffix)) {
// Selected text is already surrounded by prefix and suffix: Remove them // Selected text is already surrounded by prefix and suffix: Remove them
// Cut selected text plus prefix and suffix
operation.cutStart = operation.selStart - (prefix.length + 1); operation.cutStart = operation.selStart - (prefix.length + 1);
operation.cutEnd = operation.selEnd + suffix.length + 1; operation.cutEnd = operation.selEnd + suffix.length + 1;
// Also cut the following newline (if there is any) // Also cut the following newline (if there is any)
if (operation.text[operation.cutEnd] === "\n") { if(operation.text[operation.cutEnd] === "\n") {
operation.cutEnd++; operation.cutEnd++;
} }
// Replace with selection
operation.replacement = operation.text.substring(operation.selStart,operation.selEnd); operation.replacement = operation.text.substring(operation.selStart,operation.selEnd);
// Select text that was in between prefix and suffix // Select text that was in between prefix and suffix
operation.newSelStart = operation.cutStart; operation.newSelStart = operation.cutStart;
@@ -2,6 +2,9 @@
title: $:/core/modules/editor/operations/text/wrap-selection.js title: $:/core/modules/editor/operations/text/wrap-selection.js
type: application/javascript type: application/javascript
module-type: texteditoroperation module-type: texteditoroperation
Text editor operation to wrap the selection with the specified prefix and suffix
\*/ \*/
"use strict"; "use strict";
@@ -14,11 +17,11 @@ exports["wrap-selection"] = function(event,operation) {
selLength = o.selEnd - o.selStart; selLength = o.selEnd - o.selStart;
// This function detects, if trailing spaces are part of the selection __and__ if the user wants to handle them // This function detects, if trailing spaces are part of the selection __and__ if the user wants to handle them
// Returns "yes", "start", "end", "no" (default)
// yes .. there are trailing spaces at both ends // yes .. there are trailing spaces at both ends
// start .. there are trailing spaces at the start
// end .. there are trailing spaces at the end // end .. there are trailing spaces at the end
// no .. no trailing spaces are taken into account
var trailingSpaceAt = function(sel) { var trailingSpaceAt = function(sel) {
var _start, var _start,
_end, _end,
@@ -41,7 +44,7 @@ exports["wrap-selection"] = function(event,operation) {
break; break;
} }
return result; return result;
} };
function togglePrefixSuffix() { function togglePrefixSuffix() {
if(o.text.substring(o.selStart - prefix.length, o.selStart + suffix.length) === prefix + suffix) { if(o.text.substring(o.selStart - prefix.length, o.selStart + suffix.length) === prefix + suffix) {
@@ -61,6 +64,7 @@ exports["wrap-selection"] = function(event,operation) {
} }
} }
// options: lenPrefix, lenSuffix
function removePrefixSuffix(options) { function removePrefixSuffix(options) {
options = options || {}; options = options || {};
var _lenPrefix = options.lenPrefix || 0; var _lenPrefix = options.lenPrefix || 0;
+15 -4
View File
@@ -2,6 +2,9 @@
title: $:/core/modules/filter-tracker.js title: $:/core/modules/filter-tracker.js
type: application/javascript type: application/javascript
module-type: global module-type: global
Class to track the results of a filter string
\*/ \*/
"use strict"; "use strict";
@@ -18,6 +21,14 @@ class FilterTracker {
this.processChanges(changes); this.processChanges(changes);
} }
/*
Add a tracker to the filter tracker. Returns null if any of the parameters are invalid, or a tracker id if the tracker was added successfully. Options include:
filterString: the filter string to track
fnEnter: function to call when a title enters the filter results. Called even if the tiddler does not actually exist. Called as (title), and should return a truthy value that is stored in the tracker as the "enterValue"
fnLeave: function to call when a title leaves the filter results. Called as (title,enterValue)
fnChange: function to call when a tiddler changes in the filter results. Only called for filter results that identify a tiddler or shadow tiddler. Called as (title,enterValue), and may optionally return a replacement enterValue
fnProcess: function to call each time the tracker is processed, after any enter, leave or change functions are called. Called as (changes)
*/
track(options = {}) { track(options = {}) {
const { const {
filterString, filterString,
@@ -58,17 +69,17 @@ class FilterTracker {
if(!tracker) return; if(!tracker) return;
const results = []; const results = [];
// Evaluate the filter and remove duplicate results // Evaluate the filter and remove duplicate results
$tw.utils.each(this.wiki.filterTiddlers(tracker.filterString), title => { $tw.utils.each(this.wiki.filterTiddlers(tracker.filterString), (title) => {
$tw.utils.pushTop(results, title); $tw.utils.pushTop(results, title);
}); });
// Process the newly entered results // Process the newly entered results
results.forEach(title => { results.forEach((title) => {
if(!tracker.previousResults.includes(title) && !tracker.resultValues[title] && tracker.fnEnter) { if(!tracker.previousResults.includes(title) && !tracker.resultValues[title] && tracker.fnEnter) {
tracker.resultValues[title] = tracker.fnEnter(title) || true; tracker.resultValues[title] = tracker.fnEnter(title) || true;
} }
}); });
// Process the results that have just left // Process the results that have just left
tracker.previousResults.forEach(title => { tracker.previousResults.forEach((title) => {
if(!results.includes(title) && tracker.resultValues[title] && tracker.fnLeave) { if(!results.includes(title) && tracker.resultValues[title] && tracker.fnLeave) {
tracker.fnLeave(title, tracker.resultValues[title]); tracker.fnLeave(title, tracker.resultValues[title]);
delete tracker.resultValues[title]; delete tracker.resultValues[title];
@@ -80,7 +91,7 @@ class FilterTracker {
processChanges(changes) { processChanges(changes) {
for(const tracker of this.trackers.values()) { for(const tracker of this.trackers.values()) {
Object.keys(changes).forEach(title => { Object.keys(changes).forEach((title) => {
if(title && tracker.previousResults.includes(title) && tracker.fnChange) { if(title && tracker.previousResults.includes(title) && tracker.fnChange) {
tracker.resultValues[title] = tracker.fnChange(title, tracker.resultValues[title]) || tracker.resultValues[title]; tracker.resultValues[title] = tracker.fnChange(title, tracker.resultValues[title]) || tracker.resultValues[title];
} }
+7
View File
@@ -2,10 +2,17 @@
title: $:/core/modules/filterrunprefixes/all.js title: $:/core/modules/filterrunprefixes/all.js
type: application/javascript type: application/javascript
module-type: filterrunprefix module-type: filterrunprefix
Union of sets without de-duplication.
Equivalent to = filter run prefix.
\*/ \*/
"use strict"; "use strict";
/*
Export our filter prefix function
*/
exports.all = function(operationSubFunction) { exports.all = function(operationSubFunction) {
return function(results,source,widget) { return function(results,source,widget) {
results.push.apply(results, operationSubFunction(source,widget)); results.push.apply(results, operationSubFunction(source,widget));
+7
View File
@@ -2,10 +2,17 @@
title: $:/core/modules/filterrunprefixes/and.js title: $:/core/modules/filterrunprefixes/and.js
type: application/javascript type: application/javascript
module-type: filterrunprefix module-type: filterrunprefix
Intersection of sets.
Equivalent to + filter run prefix.
\*/ \*/
"use strict"; "use strict";
/*
Export our filter prefix function
*/
exports.and = function(operationSubFunction,options) { exports.and = function(operationSubFunction,options) {
return function(results,source,widget) { return function(results,source,widget) {
// This replaces all the elements of the array, but keeps the actual array so that references to it are preserved // This replaces all the elements of the array, but keeps the actual array so that references to it are preserved
+4 -1
View File
@@ -6,6 +6,9 @@ module-type: filterrunprefix
"use strict"; "use strict";
/*
Export our filter prefix function
*/
exports.cascade = function(operationSubFunction,options) { exports.cascade = function(operationSubFunction,options) {
return function(results,source,widget) { return function(results,source,widget) {
if(results.length !== 0) { if(results.length !== 0) {
@@ -31,5 +34,5 @@ exports.cascade = function(operationSubFunction,options) {
results.push(result); results.push(result);
}); });
} }
} };
}; };
+6
View File
@@ -2,10 +2,16 @@
title: $:/core/modules/filterrunprefixes/else.js title: $:/core/modules/filterrunprefixes/else.js
type: application/javascript type: application/javascript
module-type: filterrunprefix module-type: filterrunprefix
Equivalent to ~ filter run prefix.
\*/ \*/
"use strict"; "use strict";
/*
Export our filter prefix function
*/
exports.else = function(operationSubFunction) { exports.else = function(operationSubFunction) {
return function(results,source,widget) { return function(results,source,widget) {
if(results.length === 0) { if(results.length === 0) {
+7
View File
@@ -2,10 +2,17 @@
title: $:/core/modules/filterrunprefixes/except.js title: $:/core/modules/filterrunprefixes/except.js
type: application/javascript type: application/javascript
module-type: filterrunprefix module-type: filterrunprefix
Difference of sets.
Equivalent to - filter run prefix.
\*/ \*/
"use strict"; "use strict";
/*
Export our filter prefix function
*/
exports.except = function(operationSubFunction) { exports.except = function(operationSubFunction) {
return function(results,source,widget) { return function(results,source,widget) {
results.remove(operationSubFunction(source,widget)); results.remove(operationSubFunction(source,widget));
+5 -1
View File
@@ -2,10 +2,14 @@
title: $:/core/modules/filterrunprefixes/filter.js title: $:/core/modules/filterrunprefixes/filter.js
type: application/javascript type: application/javascript
module-type: filterrunprefix module-type: filterrunprefix
\*/ \*/
"use strict"; "use strict";
/*
Export our filter function
*/
exports.filter = function(operationSubFunction,options) { exports.filter = function(operationSubFunction,options) {
return function(results,source,widget) { return function(results,source,widget) {
if(results.length > 0) { if(results.length > 0) {
@@ -26,5 +30,5 @@ exports.filter = function(operationSubFunction,options) {
}); });
results.remove(resultsToRemove); results.remove(resultsToRemove);
} }
} };
}; };
@@ -2,10 +2,14 @@
title: $:/core/modules/filterrunprefixes/intersection.js title: $:/core/modules/filterrunprefixes/intersection.js
type: application/javascript type: application/javascript
module-type: filterrunprefix module-type: filterrunprefix
\*/ \*/
"use strict"; "use strict";
/*
Export our filter prefix function
*/
exports.intersection = function(operationSubFunction) { exports.intersection = function(operationSubFunction) {
return function(results,source,widget) { return function(results,source,widget) {
if(results.length !== 0) { if(results.length !== 0) {
+7 -1
View File
@@ -2,10 +2,16 @@
title: $:/core/modules/filterrunprefixes/let.js title: $:/core/modules/filterrunprefixes/let.js
type: application/javascript type: application/javascript
module-type: filterrunprefix module-type: filterrunprefix
Assign a value to a variable
\*/ \*/
"use strict"; "use strict";
/*
Export our filter prefix function
*/
exports.let = function(operationSubFunction,options) { exports.let = function(operationSubFunction,options) {
// Return the filter run prefix function // Return the filter run prefix function
return function(results,source,widget) { return function(results,source,widget) {
@@ -22,7 +28,7 @@ exports.let = function(operationSubFunction,options) {
if(typeof name !== "string" || name.length === 0) { if(typeof name !== "string" || name.length === 0) {
return; return;
} }
// Assign the result of the subfunction to the variable
var variables = {}; var variables = {};
variables[name] = resultList; variables[name] = resultList;
// Return the variables // Return the variables
+5 -2
View File
@@ -6,6 +6,9 @@ module-type: filterrunprefix
"use strict"; "use strict";
/*
Export our filter prefix function
*/
exports.map = function(operationSubFunction,options) { exports.map = function(operationSubFunction,options) {
return function(results,source,widget) { return function(results,source,widget) {
if(results.length > 0) { if(results.length > 0) {
@@ -25,12 +28,12 @@ exports.map = function(operationSubFunction,options) {
if(filtered.length && flatten) { if(filtered.length && flatten) {
$tw.utils.each(filtered,function(value) { $tw.utils.each(filtered,function(value) {
results.push(value); results.push(value);
}) });
} else { } else {
results.push(filtered[0]||""); results.push(filtered[0]||"");
} }
++index; ++index;
}); });
} }
} };
}; };
+6
View File
@@ -2,10 +2,16 @@
title: $:/core/modules/filterrunprefixes/or.js title: $:/core/modules/filterrunprefixes/or.js
type: application/javascript type: application/javascript
module-type: filterrunprefix module-type: filterrunprefix
Equivalent to a filter run with no prefix.
\*/ \*/
"use strict"; "use strict";
/*
Export our filter prefix function
*/
exports.or = function(operationSubFunction) { exports.or = function(operationSubFunction) {
return function(results,source,widget) { return function(results,source,widget) {
results.pushTop(operationSubFunction(source,widget)); results.pushTop(operationSubFunction(source,widget));
+4 -1
View File
@@ -6,6 +6,9 @@ module-type: filterrunprefix
"use strict"; "use strict";
/*
Export our filter prefix function
*/
exports.reduce = function(operationSubFunction,options) { exports.reduce = function(operationSubFunction,options) {
return function(results,source,widget) { return function(results,source,widget) {
if(results.length > 0) { if(results.length > 0) {
@@ -28,5 +31,5 @@ exports.reduce = function(operationSubFunction,options) {
results.clear(); results.clear();
results.push(accumulator); results.push(accumulator);
} }
} };
}; };
+7 -3
View File
@@ -2,10 +2,14 @@
title: $:/core/modules/filterrunprefixes/sort.js title: $:/core/modules/filterrunprefixes/sort.js
type: application/javascript type: application/javascript
module-type: filterrunprefix module-type: filterrunprefix
\*/ \*/
"use strict"; "use strict";
/*
Export our filter prefix function
*/
exports.sort = function(operationSubFunction,options) { exports.sort = function(operationSubFunction,options) {
return function(results,source,widget) { return function(results,source,widget) {
if(results.length > 0) { if(results.length > 0) {
@@ -29,15 +33,15 @@ exports.sort = function(operationSubFunction,options) {
for(var t=0; t<inputTitles.length; t++) { for(var t=0; t<inputTitles.length; t++) {
indexes[t] = t; indexes[t] = t;
} }
// Sort the indexes
compareFn = $tw.utils.makeCompareFunction(sortType,{defaultType: "string", invert:invert, isCaseSensitive:isCaseSensitive}); compareFn = $tw.utils.makeCompareFunction(sortType,{defaultType: "string", invert:invert, isCaseSensitive:isCaseSensitive});
indexes = indexes.sort(function(a,b) { indexes = indexes.sort(function(a,b) {
return compareFn(sortKeys[a],sortKeys[b]); return compareFn(sortKeys[a],sortKeys[b]);
}); });
// Add to results in correct order // Add to results in correct order
$tw.utils.each(indexes,function(index) { $tw.utils.each(indexes,function(index) {
results.push(inputTitles[index]); results.push(inputTitles[index]);
}); });
} }
} };
}; };
+6
View File
@@ -2,10 +2,16 @@
title: $:/core/modules/filterrunprefixes/then.js title: $:/core/modules/filterrunprefixes/then.js
type: application/javascript type: application/javascript
module-type: filterrunprefix module-type: filterrunprefix
Replace results of previous runs unless empty
\*/ \*/
"use strict"; "use strict";
/*
Export our filter prefix function
*/
exports.then = function(operationSubFunction) { exports.then = function(operationSubFunction) {
return function(results,source,widget) { return function(results,source,widget) {
if(results.length !== 0) { if(results.length !== 0) {
+55 -35
View File
@@ -2,28 +2,39 @@
title: $:/core/modules/filters.js title: $:/core/modules/filters.js
type: application/javascript type: application/javascript
module-type: wikimethod module-type: wikimethod
Adds tiddler filtering methods to the $tw.Wiki object.
\*/ \*/
"use strict"; "use strict";
var widgetClass = require("$:/core/modules/widgets/widget.js").widget; var widgetClass = require("$:/core/modules/widgets/widget.js").widget;
/* Maximum permitted filter recursion depth */
var MAX_FILTER_DEPTH = 300; var MAX_FILTER_DEPTH = 300;
/*
Parses an operation (i.e. a run) within a filter string
operators: Array of array of operator nodes into which results should be inserted
filterString: filter string
p: start position within the string
Returns the new start position, after the parsed operation
*/
function parseFilterOperation(operators,filterString,p) { function parseFilterOperation(operators,filterString,p) {
var nextBracketPos, operator; var nextBracketPos, operator;
// Skip the starting square bracket // Skip the starting square bracket
if(filterString.charAt(p++) !== "[") { if(filterString.charAt(p++) !== "[") {
throw "Missing [ in filter expression"; throw "Missing [ in filter expression";
} }
// Process each operator in turn
do { do {
operator = {}; operator = {};
// Check for an operator prefix // Check for an operator prefix
if(filterString.charAt(p) === "!") { if(filterString.charAt(p) === "!") {
operator.prefix = filterString.charAt(p++); operator.prefix = filterString.charAt(p++);
} }
// Get the operator name
nextBracketPos = filterString.substring(p).search(/[\[\{<\/\(]/); nextBracketPos = filterString.substring(p).search(/[\[\{<\/\(]/);
if(nextBracketPos === -1) { if(nextBracketPos === -1) {
throw "Missing [ in filter expression"; throw "Missing [ in filter expression";
@@ -32,7 +43,7 @@ function parseFilterOperation(operators,filterString,p) {
var bracket = filterString.charAt(nextBracketPos); var bracket = filterString.charAt(nextBracketPos);
operator.operator = filterString.substring(p,nextBracketPos); operator.operator = filterString.substring(p,nextBracketPos);
// Any suffix? // Any suffix?
var colon = operator.operator.indexOf(':'); var colon = operator.operator.indexOf(":");
if(colon > -1) { if(colon > -1) {
// The raw suffix for older filters // The raw suffix for older filters
operator.suffix = operator.operator.substring(colon + 1); operator.suffix = operator.operator.substring(colon + 1);
@@ -44,19 +55,19 @@ function parseFilterOperation(operators,filterString,p) {
$tw.utils.each(subsuffix.split(","),function(entry) { $tw.utils.each(subsuffix.split(","),function(entry) {
entry = $tw.utils.trim(entry); entry = $tw.utils.trim(entry);
if(entry) { if(entry) {
operator.suffixes[operator.suffixes.length - 1].push(entry); operator.suffixes[operator.suffixes.length - 1].push(entry);
} }
}); });
}); });
} }
// Empty operator means: title
else if(operator.operator === "") { else if(operator.operator === "") {
operator.operator = "title"; operator.operator = "title";
} }
operator.operands = []; operator.operands = [];
var parseOperand = function(bracketType) { var parseOperand = function(bracketType) {
var operand = {}; var operand = {};
switch (bracketType) { switch(bracketType) {
case "{": // Curly brackets case "{": // Curly brackets
operand.indirect = true; operand.indirect = true;
nextBracketPos = filterString.indexOf("}",p); nextBracketPos = filterString.indexOf("}",p);
@@ -77,8 +88,8 @@ function parseFilterOperation(operators,filterString,p) {
rexMatch = rex.exec(filterString.substring(p)); rexMatch = rex.exec(filterString.substring(p));
if(rexMatch) { if(rexMatch) {
operator.regexp = new RegExp(rexMatch[1], rexMatch[2]); operator.regexp = new RegExp(rexMatch[1], rexMatch[2]);
// DEPRECATION WARNING // DEPRECATION WARNING
console.log("WARNING: Filter",operator.operator,"has a deprecated regexp operand",operator.regexp); console.log("WARNING: Filter",operator.operator,"has a deprecated regexp operand",operator.regexp);
nextBracketPos = p + rex.lastIndex - 1; nextBracketPos = p + rex.lastIndex - 1;
} }
else { else {
@@ -97,7 +108,7 @@ function parseFilterOperation(operators,filterString,p) {
} }
operator.operands.push(operand); operator.operands.push(operand);
p = nextBracketPos + 1; p = nextBracketPos + 1;
} };
p = nextBracketPos + 1; p = nextBracketPos + 1;
parseOperand(bracket); parseOperand(bracket);
@@ -114,16 +125,20 @@ function parseFilterOperation(operators,filterString,p) {
} }
} }
// Push this operator
operators.push(operator); operators.push(operator);
} while(filterString.charAt(p) !== "]"); } while(filterString.charAt(p) !== "]");
// Skip the ending square bracket // Skip the ending square bracket
if(filterString.charAt(p++) !== "]") { if(filterString.charAt(p++) !== "]") {
throw "Missing ] in filter expression"; throw "Missing ] in filter expression";
} }
// Return the parsing position
return p; return p;
} }
/*
Parse a filter string
*/
exports.parseFilter = function(filterString) { exports.parseFilter = function(filterString) {
filterString = filterString || ""; filterString = filterString || "";
var results = [], // Array of arrays of operator nodes {operator:,operand:} var results = [], // Array of arrays of operator nodes {operator:,operand:}
@@ -132,11 +147,11 @@ exports.parseFilter = function(filterString) {
var whitespaceRegExp = /(\s+)/mg, var whitespaceRegExp = /(\s+)/mg,
// Groups: // Groups:
// 1 - entire filter run prefix // 1 - entire filter run prefix
// 2 - filter run prefix itself
// 3 - filter run prefix suffixes // 3 - filter run prefix suffixes
// 4 - opening square bracket following filter run prefix
// 5 - double quoted string following filter run prefix // 5 - double quoted string following filter run prefix
// 6 - single quoted string following filter run prefix
// 7 - anything except for whitespace and square brackets // 7 - anything except for whitespace and square brackets
operandRegExp = /((?:\+|\-|~|(?:=>?)|\:(\w+)(?:\:([\w\:, ]*))?)?)(?:(\[)|(?:"([^"]*)")|(?:'([^']*)')|([^\s\[\]]+))/mg; operandRegExp = /((?:\+|\-|~|(?:=>?)|\:(\w+)(?:\:([\w\:, ]*))?)?)(?:(\[)|(?:"([^"]*)")|(?:'([^']*)')|([^\s\[\]]+))/mg;
while(p < filterString.length) { while(p < filterString.length) {
@@ -146,7 +161,7 @@ exports.parseFilter = function(filterString) {
if(match && match.index === p) { if(match && match.index === p) {
p = p + match[0].length; p = p + match[0].length;
} }
// Match the start of the operation
if(p < filterString.length) { if(p < filterString.length) {
operandRegExp.lastIndex = p; operandRegExp.lastIndex = p;
var operation = { var operation = {
@@ -163,7 +178,7 @@ exports.parseFilter = function(filterString) {
if(match[2]) { if(match[2]) {
operation.namedPrefix = match[2]; operation.namedPrefix = match[2];
} }
// Suffixes for filter run prefix
if(match[3]) { if(match[3]) {
operation.suffixes = []; operation.suffixes = [];
$tw.utils.each(match[3].split(":"),function(subsuffix) { $tw.utils.each(match[3].split(":"),function(subsuffix) {
@@ -177,7 +192,7 @@ exports.parseFilter = function(filterString) {
}); });
} }
} }
// Opening square bracket
if(match[4]) { if(match[4]) {
p = parseFilterOperation(operation.operators,filterString,p); p = parseFilterOperation(operation.operators,filterString,p);
} else { } else {
@@ -187,7 +202,7 @@ exports.parseFilter = function(filterString) {
// No filter run prefix // No filter run prefix
p = parseFilterOperation(operation.operators,filterString,p); p = parseFilterOperation(operation.operators,filterString,p);
} }
// Quoted strings and unquoted title
if(match[5] || match[6] || match[7]) { // Double quoted string, single quoted string or unquoted title if(match[5] || match[6] || match[7]) { // Double quoted string, single quoted string or unquoted title
operation.operators.push( operation.operators.push(
{operator: "title", operands: [{text: match[5] || match[6] || match[7]}]} {operator: "title", operands: [{text: match[5] || match[6] || match[7]}]}
@@ -213,13 +228,18 @@ exports.getFilterRunPrefixes = function() {
$tw.modules.applyMethods("filterrunprefix",this.filterRunPrefixes); $tw.modules.applyMethods("filterrunprefix",this.filterRunPrefixes);
} }
return this.filterRunPrefixes; return this.filterRunPrefixes;
} };
exports.filterTiddlers = function(filterString,widget,source) { exports.filterTiddlers = function(filterString,widget,source) {
var fn = this.compileFilter(filterString); var fn = this.compileFilter(filterString);
return fn.call(this,source,widget); return fn.call(this,source,widget);
}; };
/*
Compile a filter into a function with the signature fn(source,widget) where:
source: an iterator function for the source tiddlers, called source(iterator), where iterator is called as iterator(tiddler,title)
widget: an optional widget node for retrieving the current tiddler etc.
*/
exports.compileFilter = function(filterString) { exports.compileFilter = function(filterString) {
if(!this.filterCache) { if(!this.filterCache) {
this.filterCache = Object.create(null); this.filterCache = Object.create(null);
@@ -237,7 +257,7 @@ exports.compileFilter = function(filterString) {
return [$tw.language.getString("Error/Filter") + ": " + e]; return [$tw.language.getString("Error/Filter") + ": " + e];
}; };
} }
// Get the hashmap of filter operator functions
var filterOperators = this.getFilterOperators(); var filterOperators = this.getFilterOperators();
// Assemble array of functions, one for each operation // Assemble array of functions, one for each operation
var operationFunctions = []; var operationFunctions = [];
@@ -282,7 +302,7 @@ exports.compileFilter = function(filterString) {
operand.value = ""; operand.value = "";
operand.multiValue = []; operand.multiValue = [];
} }
operand.isMultiValueOperand = true; operand.isMultiValueOperand = true;
} else { } else {
operand.value = operand.text; operand.value = operand.text;
operand.multiValue = [operand.value]; operand.multiValue = [operand.value];
@@ -294,19 +314,19 @@ exports.compileFilter = function(filterString) {
// Invoke the appropriate filteroperator module // Invoke the appropriate filteroperator module
results = operatorFunction(accumulator,{ results = operatorFunction(accumulator,{
operator: operator.operator, operator: operator.operator,
operand: operands.length > 0 ? operands[0] : undefined, operand: operands.length > 0 ? operands[0] : undefined,
operands: operands, operands: operands,
multiValueOperands: multiValueOperands, multiValueOperands: multiValueOperands,
isMultiValueOperand: isMultiValueOperand, isMultiValueOperand: isMultiValueOperand,
prefix: operator.prefix, prefix: operator.prefix,
suffix: operator.suffix, suffix: operator.suffix,
suffixes: operator.suffixes, suffixes: operator.suffixes,
regexp: operator.regexp regexp: operator.regexp
},{ },{
wiki: self, wiki: self,
widget: widget widget: widget
}); });
if($tw.utils.isArray(results)) { if($tw.utils.isArray(results)) {
accumulator = self.makeTiddlerIterator(results); accumulator = self.makeTiddlerIterator(results);
} else { } else {
@@ -340,7 +360,7 @@ exports.compileFilter = function(filterString) {
return filterRunPrefixes["else"](operationSubFunction, options); return filterRunPrefixes["else"](operationSubFunction, options);
case "=>": // This operation is applied to the main results so far, and the results are assigned to a variable case "=>": // This operation is applied to the main results so far, and the results are assigned to a variable
return filterRunPrefixes["let"](operationSubFunction, options); return filterRunPrefixes["let"](operationSubFunction, options);
default: default:
if(operation.namedPrefix && filterRunPrefixes[operation.namedPrefix]) { if(operation.namedPrefix && filterRunPrefixes[operation.namedPrefix]) {
return filterRunPrefixes[operation.namedPrefix](operationSubFunction, options); return filterRunPrefixes[operation.namedPrefix](operationSubFunction, options);
} else { } else {
@@ -382,7 +402,7 @@ exports.compileFilter = function(filterString) {
}); });
if(this.filterCacheCount >= 2000) { if(this.filterCacheCount >= 2000) {
// To prevent memory leak, we maintain an upper limit for cache size. // To prevent memory leak, we maintain an upper limit for cache size.
// Reset if exceeded. This should give us 95% of the benefit
// that no cache limit would give us. // that no cache limit would give us.
this.filterCache = Object.create(null); this.filterCache = Object.create(null);
this.filterCacheCount = 0; this.filterCacheCount = 0;
+8
View File
@@ -2,10 +2,18 @@
title: $:/core/modules/filters/addprefix.js title: $:/core/modules/filters/addprefix.js
type: application/javascript type: application/javascript
module-type: filteroperator module-type: filteroperator
Filter operator for adding a prefix to each title in the list. This is
especially useful in contexts where only a filter expression is allowed
and macro substitution isn't available.
\*/ \*/
"use strict"; "use strict";
/*
Export our filter function
*/
exports.addprefix = function(source,operator,options) { exports.addprefix = function(source,operator,options) {
var results = []; var results = [];
source(function(tiddler,title) { source(function(tiddler,title) {
+8
View File
@@ -2,10 +2,18 @@
title: $:/core/modules/filters/addsuffix.js title: $:/core/modules/filters/addsuffix.js
type: application/javascript type: application/javascript
module-type: filteroperator module-type: filteroperator
Filter operator for adding a suffix to each title in the list. This is
especially useful in contexts where only a filter expression is allowed
and macro substitution isn't available.
\*/ \*/
"use strict"; "use strict";
/*
Export our filter function
*/
exports.addsuffix = function(source,operator,options) { exports.addsuffix = function(source,operator,options) {
var results = []; var results = [];
source(function(tiddler,title) { source(function(tiddler,title) {
+6
View File
@@ -2,10 +2,16 @@
title: $:/core/modules/filters/after.js title: $:/core/modules/filters/after.js
type: application/javascript type: application/javascript
module-type: filteroperator module-type: filteroperator
Filter operator returning the tiddler from the current list that is after the tiddler named in the operand.
\*/ \*/
"use strict"; "use strict";
/*
Export our filter function
*/
exports.after = function(source,operator,options) { exports.after = function(source,operator,options) {
var results = []; var results = [];
source(function(tiddler,title) { source(function(tiddler,title) {
+9 -1
View File
@@ -2,6 +2,11 @@
title: $:/core/modules/filters/all.js title: $:/core/modules/filters/all.js
type: application/javascript type: application/javascript
module-type: filteroperator module-type: filteroperator
Filter operator for selecting tiddlers
[all[shadows+tiddlers]]
\*/ \*/
"use strict"; "use strict";
@@ -16,6 +21,9 @@ function getAllFilterOperators() {
return allFilterOperators; return allFilterOperators;
} }
/*
Export our filter function
*/
exports.all = function(source,operator,options) { exports.all = function(source,operator,options) {
// Check for common optimisations // Check for common optimisations
var subops = operator.operand.split("+"); var subops = operator.operand.split("+");
@@ -30,7 +38,7 @@ exports.all = function(source,operator,options) {
} else if(subops.length === 2 && subops[0] === "shadows" && subops[1] === "tiddlers") { } else if(subops.length === 2 && subops[0] === "shadows" && subops[1] === "tiddlers") {
return options.wiki.eachShadowPlusTiddlers; return options.wiki.eachShadowPlusTiddlers;
} }
// Do it the hard way
// Get our suboperators // Get our suboperators
var allFilterOperators = getAllFilterOperators(); var allFilterOperators = getAllFilterOperators();
// Cycle through the suboperators accumulating their results // Cycle through the suboperators accumulating their results
+6
View File
@@ -2,10 +2,16 @@
title: $:/core/modules/filters/all/current.js title: $:/core/modules/filters/all/current.js
type: application/javascript type: application/javascript
module-type: allfilteroperator module-type: allfilteroperator
Filter function for [all[current]]
\*/ \*/
"use strict"; "use strict";
/*
Export our filter function
*/
exports.current = function(source,prefix,options) { exports.current = function(source,prefix,options) {
var currTiddlerTitle = options.widget && options.widget.getVariable("currentTiddler"); var currTiddlerTitle = options.widget && options.widget.getVariable("currentTiddler");
if(currTiddlerTitle) { if(currTiddlerTitle) {
+6
View File
@@ -2,10 +2,16 @@
title: $:/core/modules/filters/all/missing.js title: $:/core/modules/filters/all/missing.js
type: application/javascript type: application/javascript
module-type: allfilteroperator module-type: allfilteroperator
Filter function for [all[missing]]
\*/ \*/
"use strict"; "use strict";
/*
Export our filter function
*/
exports.missing = function(source,prefix,options) { exports.missing = function(source,prefix,options) {
return options.wiki.getMissingTitles(); return options.wiki.getMissingTitles();
}; };
+6
View File
@@ -2,10 +2,16 @@
title: $:/core/modules/filters/all/orphans.js title: $:/core/modules/filters/all/orphans.js
type: application/javascript type: application/javascript
module-type: allfilteroperator module-type: allfilteroperator
Filter function for [all[orphans]]
\*/ \*/
"use strict"; "use strict";
/*
Export our filter function
*/
exports.orphans = function(source,prefix,options) { exports.orphans = function(source,prefix,options) {
return options.wiki.getOrphanTitles(); return options.wiki.getOrphanTitles();
}; };
+6
View File
@@ -2,10 +2,16 @@
title: $:/core/modules/filters/all/shadows.js title: $:/core/modules/filters/all/shadows.js
type: application/javascript type: application/javascript
module-type: allfilteroperator module-type: allfilteroperator
Filter function for [all[shadows]]
\*/ \*/
"use strict"; "use strict";
/*
Export our filter function
*/
exports.shadows = function(source,prefix,options) { exports.shadows = function(source,prefix,options) {
return options.wiki.allShadowTitles(); return options.wiki.allShadowTitles();
}; };
+6
View File
@@ -2,10 +2,16 @@
title: $:/core/modules/filters/all/tags.js title: $:/core/modules/filters/all/tags.js
type: application/javascript type: application/javascript
module-type: allfilteroperator module-type: allfilteroperator
Filter function for [all[tags]]
\*/ \*/
"use strict"; "use strict";
/*
Export our filter function
*/
exports.tags = function(source,prefix,options) { exports.tags = function(source,prefix,options) {
return Object.keys(options.wiki.getTagMap()); return Object.keys(options.wiki.getTagMap());
}; };
+6
View File
@@ -2,10 +2,16 @@
title: $:/core/modules/filters/all/tiddlers.js title: $:/core/modules/filters/all/tiddlers.js
type: application/javascript type: application/javascript
module-type: allfilteroperator module-type: allfilteroperator
Filter function for [all[tiddlers]]
\*/ \*/
"use strict"; "use strict";
/*
Export our filter function
*/
exports.tiddlers = function(source,prefix,options) { exports.tiddlers = function(source,prefix,options) {
return options.wiki.allTitles(); return options.wiki.allTitles();
}; };
+6
View File
@@ -2,10 +2,16 @@
title: $:/core/modules/filters/backlinks.js title: $:/core/modules/filters/backlinks.js
type: application/javascript type: application/javascript
module-type: filteroperator module-type: filteroperator
Filter operator for returning all the backlinks from a tiddler
\*/ \*/
"use strict"; "use strict";
/*
Export our filter function
*/
exports.backlinks = function(source,operator,options) { exports.backlinks = function(source,operator,options) {
var results = new $tw.utils.LinkedList(); var results = new $tw.utils.LinkedList();
source(function(tiddler,title) { source(function(tiddler,title) {
+6
View File
@@ -2,9 +2,15 @@
title: $:/core/modules/filters/backtranscludes.js title: $:/core/modules/filters/backtranscludes.js
type: application/javascript type: application/javascript
module-type: filteroperator module-type: filteroperator
Filter operator for returning all the backtranscludes from a tiddler
\*/ \*/
"use strict"; "use strict";
/*
Export our filter function
*/
exports.backtranscludes = function(source,operator,options) { exports.backtranscludes = function(source,operator,options) {
var results = new $tw.utils.LinkedList(); var results = new $tw.utils.LinkedList();
source(function(tiddler,title) { source(function(tiddler,title) {
+6
View File
@@ -2,10 +2,16 @@
title: $:/core/modules/filters/before.js title: $:/core/modules/filters/before.js
type: application/javascript type: application/javascript
module-type: filteroperator module-type: filteroperator
Filter operator returning the tiddler from the current list that is before the tiddler named in the operand.
\*/ \*/
"use strict"; "use strict";
/*
Export our filter function
*/
exports.before = function(source,operator,options) { exports.before = function(source,operator,options) {
var results = []; var results = [];
source(function(tiddler,title) { source(function(tiddler,title) {
+6
View File
@@ -2,10 +2,16 @@
title: $:/core/modules/filters/commands.js title: $:/core/modules/filters/commands.js
type: application/javascript type: application/javascript
module-type: filteroperator module-type: filteroperator
Filter operator for returning the names of the commands available in this wiki
\*/ \*/
"use strict"; "use strict";
/*
Export our filter function
*/
exports.commands = function(source,operator,options) { exports.commands = function(source,operator,options) {
var results = []; var results = [];
$tw.utils.each($tw.commands,function(commandInfo,name) { $tw.utils.each($tw.commands,function(commandInfo,name) {
+4 -1
View File
@@ -2,6 +2,9 @@
title: $:/core/modules/filters/compare.js title: $:/core/modules/filters/compare.js
type: application/javascript type: application/javascript
module-type: filteroperator module-type: filteroperator
General purpose comparison operator
\*/ \*/
"use strict"; "use strict";
@@ -29,4 +32,4 @@ var modes = {
"gt": function(value) {return value > 0;}, "gt": function(value) {return value > 0;},
"lteq": function(value) {return value <= 0;}, "lteq": function(value) {return value <= 0;},
"lt": function(value) {return value < 0;} "lt": function(value) {return value < 0;}
} };
+6
View File
@@ -2,10 +2,16 @@
title: $:/core/modules/filters/contains.js title: $:/core/modules/filters/contains.js
type: application/javascript type: application/javascript
module-type: filteroperator module-type: filteroperator
Filter operator for finding values in array fields
\*/ \*/
"use strict"; "use strict";
/*
Export our filter function
*/
exports.contains = function(source,operator,options) { exports.contains = function(source,operator,options) {
var results = [], var results = [],
fieldname = operator.suffix || "list"; fieldname = operator.suffix || "list";
+6
View File
@@ -2,10 +2,16 @@
title: $:/core/modules/filters/count.js title: $:/core/modules/filters/count.js
type: application/javascript type: application/javascript
module-type: filteroperator module-type: filteroperator
Filter operator returning the number of entries in the current list.
\*/ \*/
"use strict"; "use strict";
/*
Export our filter function
*/
exports.count = function(source,operator,options) { exports.count = function(source,operator,options) {
var count = 0; var count = 0;
source(function(tiddler,title) { source(function(tiddler,title) {
+3
View File
@@ -2,6 +2,9 @@
title: $:/core/modules/filters/crypto.js title: $:/core/modules/filters/crypto.js
type: application/javascript type: application/javascript
module-type: filteroperator module-type: filteroperator
Filter operators for cryptography, using the Stanford JavaScript library
\*/ \*/
"use strict"; "use strict";
+6
View File
@@ -2,10 +2,16 @@
title: $:/core/modules/filters/days.js title: $:/core/modules/filters/days.js
type: application/javascript type: application/javascript
module-type: filteroperator module-type: filteroperator
Filter operator that selects tiddlers with a specified date field within a specified date interval.
\*/ \*/
"use strict"; "use strict";
/*
Export our filter function
*/
exports.days = function(source,operator,options) { exports.days = function(source,operator,options) {
var results = [], var results = [],
fieldName = operator.suffix || "modified", fieldName = operator.suffix || "modified",
+1 -1
View File
@@ -31,4 +31,4 @@ exports["deserialize"] = function(source,operator,options) {
return [$tw.language.getString("Error/DeserializeOperator/MissingOperand")]; return [$tw.language.getString("Error/DeserializeOperator/MissingOperand")];
} }
return results; return results;
} };
+6
View File
@@ -2,10 +2,16 @@
title: $:/core/modules/filters/deserializers.js title: $:/core/modules/filters/deserializers.js
type: application/javascript type: application/javascript
module-type: filteroperator module-type: filteroperator
Filter operator for returning the names of the deserializers in this wiki
\*/ \*/
"use strict"; "use strict";
/*
Export our filter function
*/
exports.deserializers = function(source,operator,options) { exports.deserializers = function(source,operator,options) {
var results = []; var results = [];
$tw.utils.each($tw.Wiki.tiddlerDeserializerModules,function(deserializer,type) { $tw.utils.each($tw.Wiki.tiddlerDeserializerModules,function(deserializer,type) {
+6
View File
@@ -2,10 +2,16 @@
title: $:/core/modules/filters/duplicateslugs.js title: $:/core/modules/filters/duplicateslugs.js
type: application/javascript type: application/javascript
module-type: filteroperator module-type: filteroperator
Filter function for [duplicateslugs[]]
\*/ \*/
"use strict"; "use strict";
/*
Export our filter function
*/
exports.duplicateslugs = function(source,operator,options) { exports.duplicateslugs = function(source,operator,options) {
var slugs = Object.create(null), // Hashmap by slug of title, replaced with "true" if the duplicate title has already been output var slugs = Object.create(null), // Hashmap by slug of title, replaced with "true" if the duplicate title has already been output
results = []; results = [];
+9 -2
View File
@@ -2,14 +2,21 @@
title: $:/core/modules/filters/each.js title: $:/core/modules/filters/each.js
type: application/javascript type: application/javascript
module-type: filteroperator module-type: filteroperator
Filter operator that selects one tiddler for each unique value of the specified field.
With suffix "list", selects all tiddlers that are values in a specified list field.
\*/ \*/
"use strict"; "use strict";
/*
Export our filter function
*/
exports.each = function(source,operator,options) { exports.each = function(source,operator,options) {
var results =[] , var results =[] ,
value,values = {}, value,values = {},
field = operator.operand || "title"; field = operator.operand || "title";
if(operator.suffix === "value" && field === "title") { if(operator.suffix === "value" && field === "title") {
source(function(tiddler,title) { source(function(tiddler,title) {
if(!$tw.utils.hop(values,title)) { if(!$tw.utils.hop(values,title)) {
+6
View File
@@ -2,10 +2,16 @@
title: $:/core/modules/filters/eachday.js title: $:/core/modules/filters/eachday.js
type: application/javascript type: application/javascript
module-type: filteroperator module-type: filteroperator
Filter operator that selects one tiddler for each unique day covered by the specified date field
\*/ \*/
"use strict"; "use strict";
/*
Export our filter function
*/
exports.eachday = function(source,operator,options) { exports.eachday = function(source,operator,options) {
var results = [], var results = [],
values = [], values = [],
@@ -2,10 +2,16 @@
title: $:/core/modules/filters/editiondescription.js title: $:/core/modules/filters/editiondescription.js
type: application/javascript type: application/javascript
module-type: filteroperator module-type: filteroperator
Filter operator for returning the descriptions of the specified edition names
\*/ \*/
"use strict"; "use strict";
/*
Export our filter function
*/
exports.editiondescription = function(source,operator,options) { exports.editiondescription = function(source,operator,options) {
var results = []; var results = [];
if($tw.node) { if($tw.node) {
+6
View File
@@ -2,10 +2,16 @@
title: $:/core/modules/filters/editions.js title: $:/core/modules/filters/editions.js
type: application/javascript type: application/javascript
module-type: filteroperator module-type: filteroperator
Filter operator for returning the names of the available editions in this wiki
\*/ \*/
"use strict"; "use strict";
/*
Export our filter function
*/
exports.editions = function(source,operator,options) { exports.editions = function(source,operator,options) {
var results = []; var results = [];
if($tw.node) { if($tw.node) {
+6
View File
@@ -2,10 +2,16 @@
title: $:/core/modules/filters/else.js title: $:/core/modules/filters/else.js
type: application/javascript type: application/javascript
module-type: filteroperator module-type: filteroperator
Filter operator for replacing an empty input list with a constant, passing a non-empty input list straight through
\*/ \*/
"use strict"; "use strict";
/*
Export our filter function
*/
exports.else = function(source,operator,options) { exports.else = function(source,operator,options) {
var results = []; var results = [];
source(function(tiddler,title) { source(function(tiddler,title) {
+7
View File
@@ -2,10 +2,17 @@
title: $:/core/modules/filters/decodeuricomponent.js title: $:/core/modules/filters/decodeuricomponent.js
type: application/javascript type: application/javascript
module-type: filteroperator module-type: filteroperator
Filter operator for applying decodeURIComponent() to each item.
\*/ \*/
"use strict"; "use strict";
/*
Export our filter functions
*/
exports.decodebase64 = function(source,operator,options) { exports.decodebase64 = function(source,operator,options) {
var results = []; var results = [];
var binary = operator.suffixes && operator.suffixes[0].indexOf("binary") !== -1; var binary = operator.suffixes && operator.suffixes[0].indexOf("binary") !== -1;
+6
View File
@@ -2,10 +2,16 @@
title: $:/core/modules/filters/enlist.js title: $:/core/modules/filters/enlist.js
type: application/javascript type: application/javascript
module-type: filteroperator module-type: filteroperator
Filter operator returning its operand parsed as a list
\*/ \*/
"use strict"; "use strict";
/*
Export our filter function
*/
exports.enlist = function(source,operator,options) { exports.enlist = function(source,operator,options) {
var allowDuplicates = false; var allowDuplicates = false;
switch(operator.suffix) { switch(operator.suffix) {

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