1
0
mirror of https://github.com/Jermolene/TiddlyWiki5 synced 2026-01-22 19:04:38 +00:00

Compare commits

...

161 Commits

Author SHA1 Message Date
Jeremy Ruston
26fe15670c Joyously fix eslint error
I don't think this was introduced by this PR
2026-01-10 14:46:39 +00:00
Jeremy Ruston
f825255bb8 Create changenote 2026-01-10 14:43:51 +00:00
Jeremy Ruston
ee42f2f731 Fix images loaded from _canonical_uri tiddlers do not have loading and error classes assigned 2026-01-10 14:27:36 +00:00
Jeremy Ruston
5e4b8fbb3c Additional fix for #9177 2026-01-08 18:20:57 +00:00
lin onetwo
8e301178a4 Feat/view button condition (#9466)
* feat: support condition field on $:/tags/ViewToolbar button

* Delete convert-markdown.tid

* Create #9466.tid

* Update title.tid
2026-01-08 13:34:58 +01:00
Cameron Fischer
a72d3a09bf Quick fix to some deprecated class methods (#9561) 2026-01-08 10:13:16 +01:00
Jeremy Ruston
56634ffe29 Merge branch 'tiddlywiki-com' 2026-01-05 16:36:49 +00:00
Jeremy Ruston
24c317e1ab Revert "Add macro operator (#9520)"
This reverts commit 3c8ee86e23.
2026-01-05 16:18:01 +00:00
Jeremy Ruston
07329c6849 Revert "Root stylesheet refresh - Stylesheets in single <style> tags (#8130)"
This reverts commit da41a55f29.
2026-01-05 16:05:15 +00:00
Mohammad Rahmani
9663e65f4b list-tagged-draggable to use title field by default (#9177)
* Revert the list-tagged-draggable to use title field by default

* Update list.tid

It seems `<$transclude field=<<__field__>> />` when field default value is empty does not work as expected.
This commit uses `field:"title"` instead of `field:""` in the list-tagged-draggable header.

* Update list.tid

The redundant `field` removed. The new `displayField` attribute is used to let user choose the filed they like to display when the list items are rendered as simple links.

* Update list.tid

The proposed field attribute in list-tagged-draggable updated to use the same `displayField` aslist-links-draggable.

* Create #9177

change note for #9177 was created

* Update list.tid

The `list-links-draggable` now is fully backward compatible. It uses caption as default field as before and is compatible with `list-links`.

The `list-tagged-draggable` is fully backward compatible while got a new displayField parameter.

* Update list-tagged-draggable Macro.tid

Update docs to reflect new displayField parameter

* Update list-links-draggable Macro.tid

Update documentation to reflect the new displayField parameter.

* Update list-tagged-draggable Macro.tid

Update docs. Remove formatting error in doc.
2026-01-04 21:41:38 +00:00
Jeremy Ruston
f075f24e6b Merge branch 'tiddlywiki-com' 2026-01-04 20:48:58 +00:00
XLBilly
98a61f01bb Add locale support for sort operator (#9400)
* Locale support for sort operator

* Add checkLanguageCode util function

* Update docs & add language code validation

* Replace multiple isDescending with reverse

* Revert "Replace multiple isDescending with reverse"

This reverts commit 793177b8bc.

* Simplify sortTiddler with Intl.Collator

* Add change notes

* Remove comment

* Update makeCompareFunction to support locale

* Update checkLanguageCode

* Add locale support for sortsub

* Add locale support for sort filter run prefix

* Revert "Add locale support for sort filter run prefix"

This reverts commit 9479a156d7.

* Remove checkLanguageCode
Since filters are able to catch errors now

* Update locale example

* Revert " Add locale support for sortsub"

This reverts commit 4a617188fc.

* Revert "Update makeCompareFunction to support locale"

This reverts commit 0ebca08036.

* Update docs

* Update change note
2026-01-04 18:03:54 +00:00
Mario Pietsch
e2fb22ade0 Bug report template: Rename description to about (#9546) 2026-01-04 16:50:01 +00:00
Mario Pietsch
6eb881bffe New issue and bug report templates (#9512)
* New issue and bug report templates

* Change bug_report to .md file

* update bug report text

* Move To Reproduce up next to Problem Description

* Fix typo
2026-01-04 13:08:40 +00:00
IchijikuIchigo
2a5ce95d99 Update Japanese translation (#9542)
* Update Japanese translation

* Update Japanese translation

* Update Japanese translation

* Update Japanese translation
2026-01-04 12:14:41 +00:00
XLBilly
bd4fdd8f2e Improve tabs macro accessibility (#9348)
* Improve tabs macro accessibility

* Fix wrong aria-selected attribute

* Patch button widget to allow changing the default aria-checked attribute

* Patch button widget to have aria-checked attribute set to false

* Update tests

* Further fix tests

* Update docs

* Update change notes
2026-01-04 12:13:10 +00:00
XLBilly
09379abd5d Bidirectional improvements for core classes (#9148)
* Bidirectional improvements for core classes

* Fix Control Panel table text direction

* Switch to logical properties

* Add -webkit-margin-* properties

* Improve backward compatibility

* Use supports CSS at-rule for fallback

* Tiddler controls, alert, TOC update.

* tc-tree & testcase update

* .tc-sidebar-scrollable use new syntax

* Snow white use standard value

* Update divider

* Avoid negative logic

* Remove :dir rules at the moment

* Use property procedures

* Add change note

* Update blockquote

* Update unfold banner

* Update tiddlerinfo close button

* Group supports rule

* Update tiddler title icon

* Fix wrong float and margin
2026-01-04 12:02:28 +00:00
XLBilly
c6906120d8 Migrate diff-match-patch to a modern fork (#9511)
* Migrate to diff-match-patch-es & update api

* Update acknowledgements

* Update change notes

* Fix editcost attribute not working

* Make library compatible with ES2017
2026-01-04 11:56:25 +00:00
Jeremy Ruston
c4c60933f4 Merge branch 'tiddlywiki-com' 2026-01-04 11:40:01 +00:00
XLBilly
a3979cda9c Refactor base64 utility functions (#9488)
* Split base64 utility functions to two platforms

* Simplify Nodejs atob

* Update change note

* Update docs

* Move base64Decode & base64Encode back to utils.js

* Add missing use strict
2026-01-04 11:35:02 +00:00
XLBilly
3c8ee86e23 Add macro operator (#9520)
* Add macro operator

* Replace unnecessary usage of wikify widget

* Simplify multiple let widget

* fixup! Replace unnecessary usage of wikify widget

* Update tests

* Update change note

* fixup! Replace unnecessary usage of wikify widget

* Update docs

* fixup! Update tests
2026-01-04 11:31:30 +00:00
XLBilly
921c0174fb Purge IE related docs (#9134) 2026-01-04 09:42:01 +01:00
Jeremy Ruston
743e99d12d Further fix to #9538 2025-12-29 21:44:57 +00:00
Jeremy Ruston
6beeb23d10 Fix browser storage plugin triggers save (#9538)
* Initial Commit

* Add changenote
2025-12-29 21:09:43 +00:00
Jeremy Ruston
1b8a2caa23 Merge branch 'tiddlywiki-com' 2025-12-19 09:12:27 +00:00
yaisog
fc1e53a777 Enable popup clamping to container (#7898)
* Initial commit

* Show demo tiddler by default

* Correct filename for example tiddler

*sigh*

* Distinguish right/bottom/both/none

* Fix typo

* Revert DefaultTiddlers.tid to original version

* Add changenote

* Fix ESLint errors

* Update documentation tiddlers
2025-12-19 08:44:03 +00:00
Jeremy Ruston
2b6bdcbf97 Merge branch 'tiddlywiki-com' 2025-12-18 21:06:20 +00:00
yaisog
119706a918 Protect cached array data from external mutation (#9495)
* Initial commit

* Add changenote
2025-12-18 18:39:11 +00:00
XLBilly
86d15585b6 Fix github linguist unmarking generated files not working (#9497)
* Fix github linguist unmarking generated files not working

* further fix
2025-12-18 18:16:52 +00:00
Joe Bordes
cd173a959d i18n(ES) update to latest code base (#9311) 2025-12-17 17:53:42 +00:00
Mario Pietsch
86d61e09bd Edit-text widget: rows parameter takes precedence, CM accepts rows now (#9454)
* edit-text rows parameter takes precedence, CM accepts rows now

* Add changenote for PR

* Clarify edit-text widget rows parameter precedence
2025-12-17 15:03:23 +00:00
s793016
7c197f6ecc Fix character disappearing and false matching issues in TiddlyWiki 5.4 Freelink plugin (#9397)
* Update aho-corasick.js

False positive matches

Symptom: Words like "it is", "Choose", "Set up" are incorrectly linked to tiddler "FooBar" when a tiddler titled "xxx x FooBar" exists.

Root cause: The Aho-Corasick algorithm's output merging mechanism in buildFailureLinks caused failure link outputs to be incorrectly merged into intermediate nodes, resulting in false matches.

Fix:

Remove incorrect output merging in buildFailureLinks
Implement proper output collection during search by traversing the failure link chain
Add exact match validation: verify that the matched text exactly equals the pattern before accepting it
Add cycle detection to prevent infinite loops in failure link traversal

* Update text.js

First character disappearing

Symptom: When freelinking is enabled, the first character of matched words disappears (e.g., "The" becomes "he", "Filter" becomes "ilter").

Root cause: When the current tiddler's title was being filtered out, it was done too late in the process (during parse tree construction), causing text rendering issues.

Fix:

Move the current tiddler title filtering to the match validation stage (in processTextWithMatches)
Use substring instead of slice for better stability
Add proper case-insensitive comparison for title matching

* Update text.js

add back description

* Update aho-corasick.js

add back description

* Update tiddlywiki.info

add freelinks plugin for testing

* Update tiddlywiki.info

restore

* Update tiddlywiki.info

add freelinks plugin for test

* Update aho-corasick.js

erase comment

* Update text.js

erase comment

* Update aho-corasick.js

add back some commets

* Update aho-corasick.js

clean comment

* change note #9397

change note #9397

* Update tiddlywiki.info

reversed to original

* Update #9397.tid

update detail

* Update #9397.tid

another link added

* Update #9397.tid

add "release: 5.4.0"

* Update #9397.tid

some format modified
2025-12-17 15:02:42 +00:00
Mario Pietsch
8f8b46dab7 Add diff-text editcost parameter (#9453)
* Add diff-text editcost parameter

* Add changenote

* Add typo for diff-text testing

* Add typo for diff-text testing

* Add typo for diff-text testing
2025-12-17 14:57:56 +00:00
Simon Huber
7b013af240 Update reveal.js to position popups correctly when animated (#9239) 2025-12-17 14:51:32 +00:00
lin onetwo
05f3e6d5a0 Feat/import options (#9465)
* feat: add $:/tags/ImportOptions support on import panel

* feat: style of import option

* fix: conditional show import option

* Delete ImportOptions.tid

* feat: allow multiple import options

* Revert "Delete ImportOptions.tid"

This reverts commit 3487e3dac5.

* DEBUG options

* remove debug files

* Create #9465.tid
2025-12-17 14:44:06 +00:00
XLBilly
48eeb4603a Deprecate and simplify some utility functions (#9251)
* Deprecate some utility functions

* Drop IE support

* Update two function

* Update comment

* Further simplify with arrow function

* Fix node error

* Deprecate logTable

* Deprecate class functions

* Attempt to fix error

* Deprecate two functions

* Remove deprecation for getLocationPath

* Deprecate stringifyNumber, domContains, domMatchesSelector

* Deprecate $tw.utils.each

* Revert "Deprecate $tw.utils.each"

This reverts commit 650df1d575.

* Simplify getFullScreenApis

* Replace LLMap with Map

* Revert "Replace LLMap with Map"

This reverts commit 4410ac194a.

* Move some deprecated functions to deprecated.js

* Remove Opera & MS prefix

* Deprecate getLocationPath

* Fix code style

* Revert "Remove Opera & MS prefix"

This reverts commit e5771c00be.

* Revert "Simplify getFullScreenApis"

This reverts commit 894cb479ea.

* Further simplify toggleClass

* Second attempt to simplify $tw.utils.each

* Revert "Second attempt to simplify $tw.utils.each"

This reverts commit 74cb4f766e.

* Third attempt to simplify $tw.utils.each

* Add missing comma

* Update comments

* Deprecate hopArray

Since it is easy to implement it with some method

* Update change notes

* Deprecate tagToCssSelector

Since tc-tagged-* classes are deprecated
2025-12-17 14:40:47 +00:00
XLBilly
86c4770a28 Fix RSOE from filter operator errors (#9496)
* Fix RSOE from decodebase64

* Update change note

* Revert "Fix RSOE from decodebase64"

This reverts commit 4145f08623.

* Use a more general implementation

* Operator error should let whole filter fail
2025-12-17 14:24:25 +00:00
Simon Huber
da41a55f29 Root stylesheet refresh - Stylesheets in single <style> tags (#8130)
* Create RootStylesheet.tid

* put stylesheets into single <style> tags

* viewHandler should not be a widget

* format RootStylesheet

* add ROOT_STYLESHEET_TITLE constant

* use let widget in RootStylesheet

* use view widget from #8135 for testing

* reformat RootStylesheet

* update view widget

* update view widget

* update view widget

* update view widget

* update view widget

* update view widget

* trying to fix the date parsing

* refactor view widget with more extensible architecture

* remove performance instrumentation

* hardcode reset and base stylesheets

* adopt ES2017 syntax for view widget

* don't hardcode vanilla/base

* remove unused constructors

* remove unused variable

* make refresh method static

* trying to fix missingThis

* add changenote

* revert change in vanilla/base
2025-12-13 10:17:22 +00:00
XLBilly
846ac9a0dd Add issue types (#9490) 2025-12-13 10:14:54 +00:00
XLBilly
65edda224b Further update eslint configuration (#9474)
* Add comment to disable indent rule

* Enable no-eval rule

And only disable it in evalGlobal

* Diable indent rule in bootprefix

* Update change note

* 更新 boot.js

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

---------

Co-authored-by: Mario Pietsch <pmariojo@gmail.com>
2025-12-13 10:14:18 +00:00
yaisog
41dac42f3b Fix sluggishness bug in ActionLogWidget (#9489)
* Call getVariableInfo only for non-functions

* Add changenote
2025-12-13 09:34:47 +00:00
XLBilly
87d9754a4e github-linguist improvements (#9384) 2025-12-13 09:31:55 +00:00
Mario Pietsch
5cd3084298 Add pmario PR changenotes (#9442) 2025-12-02 10:31:19 +00:00
XLBilly
4c27c09b4d Update eslint config (#9457)
* Update eslint config

* Switch off max classes restriction
* Enforce tab indent, semicolon
* Warn when there exists unused vars

* Update change note

* Update change note

* Update change note
2025-11-28 13:16:43 +00:00
Jeremy Ruston
6cb333b65b Merge branch 'tiddlywiki-com' 2025-11-22 17:48:43 +00:00
Jeremy Ruston
033d5cf225 Missing documentation updates for #8972 2025-11-22 13:21:01 +00:00
Jeremy Ruston
52d73eb1a8 Fix accidentally committed change to default tiddlers from #8972 2025-11-22 12:59:38 +00:00
Jeremy Ruston
5d1c1eaf87 Introduce multi-valued variables and let filter run prefix (#8972)
* Introduce let filter run prefix for assigning filter run result to a variable

* Get rid of the special behaviour for all[]

Not needed because the input to the filter run is available

* Fix tests

* Fix tests

* Cleanup

* Support for saving result lists in a variable

Extend let filter run prefix to store list of results, and add varlist operator for accessing variables as a list.

We already had partial support for variables returning a list of values in order for functions to work, now we extend it so that any variable can be used to store a list

We should extend the set widget so that it returns a result list that can be accessed with the varlist operator

* Docs update

* Introduce letlist widget for assigning lists to variables

Terrible name. Annoyingly, we can't overload the existing let or set widgets.

* Docs update

* Update DefaultTiddlers to highlight the new docs

* Fixed varlist crash with empty parameter

* Switch to triple brace syntax for assigning filtered lists

* Docs update

* Test for multivalued functions

* varlist operator: fixed crash accessing non-existent variable

See https://github.com/TiddlyWiki/TiddlyWiki5/pull/8972#issuecomment-2712068743

* Dispense with the letlist widget

What this PR actually does is rename the letlist widget to "let". The result is the same as using the letlist widget, but it is backwards compatible by virtue of the fact that all existing ways to access variables will only see the single value for the variable.

* Refactor the let filter run prefix to assign the input list to the variable named by the filter run

These semantics are much simpler, and allow the variable name to be computed.

* Missed off 211b135265

* Docs update

* Bug fix

* Introduce round brackets for multi-valued filter operands

Allowing us to drop the varlist operator

* Introduce => as a shortcut syntax for the let filter run prefix

Also relax the requirement for a filter run prefix to be followed by an opening square bracket

* Fix bug exposed in "Filter Operators" tiddler

See https://github.com/TiddlyWiki/TiddlyWiki5/pull/8972#issuecomment-2740003414

* Fix bug with missing variable attributes

See https://github.com/TiddlyWiki/TiddlyWiki5/pull/8972#issuecomment-2752792329

* Fix bug with round brackets for 2nd parameter onwards

* Allow functions to take multivalued parameters

* Simplify title operator

* Extend title operator to allow negated form to use multi-valued variables

* Remove duplicate test

* Update action-log widget to log multi-valued attributes

* Docs updates

* Fix typos

* Happy linter happy life

* Fix version numbers of from-version procedures

* Another incorrect version number

* Add change note

* Fix filenames of tests

* Typo

* Update let.js

* Docs updates
2025-11-22 12:29:42 +00:00
Bram Chen
ed4a186c9c Add missing changelog for #9375 (#9456) 2025-11-21 09:50:21 +00:00
Maurycy Zarzycki
f16c0d769b Add support for text/tab-separated-values mimetype (#9445)
* Add support for `text/tab-separated-values` mimetype

* Fix eslint issues

* Add the release note for CSV parser change
2025-11-19 16:59:15 +00:00
Andrew Gregory
298508c104 Updates pluginmaker.js to add modification fields (#9401)
* Update pluginmaker.js

Update plugin modified timestamp unless disabled.

* Update pluginmaker.js

Use getModificationFields()

* Update pluginmaker.js

Inline Tiddler creation parameters

* Create #9401.tid

Create release notes tiddler

* Apply suggestion from @saqimtiaz

---------

Co-authored-by: Saq Imtiaz <saq.imtiaz@gmail.com>
2025-11-17 14:02:07 +01:00
XLBilly
f1e1532949 Updates change notes (#9413)
* Update missing change notes

* Update change notes

* Add date fields

* Update change notes

* Update change notes

* Add 9253 impact note

* Replace deprecation with performance

* Add change note for 9350
2025-11-17 12:31:08 +01:00
lin onetwo
3e1078eff1 Fix/serialize close html tag (#9437)
* fix: should use tree.isSelfClosing || isVoidElement

* docs: about html

* fix: Void element without self-closing slash (e.g., <br> instead of <br/>)

* Update editions/test/tiddlers/tests/data/serialize/VoidElements.tid

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

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-14 21:33:45 +00:00
XLBilly
cc348fee96 Further fix markup not included in external core edition (#9439)
* Further fix markup not included in external core edition

* Update change note

* Typo
2025-11-14 15:57:35 +00:00
XLBilly
24d45fd318 Replace palette category with theme (#9438) 2025-11-14 09:46:14 +00:00
Jeremy Ruston
9fd345ec06 Fixes for #9432 2025-11-13 12:00:21 +00:00
Arlen Beiler
30e9b4f3b7 Add change notes as requested in #9406 (#9435)
* Add change notes as requested in #9406

* fix username casing
2025-11-12 21:41:29 +00:00
lin onetwo
c6556d5207 Update PR validator to v6 (#9434)
* Update pr-validation.yml

* v6

* fix missing filter

* Revert "fix missing filter"

This reverts commit 9f132d8819.
2025-11-12 17:47:50 +00:00
yaisog
a8da7e0207 Add changenotes for PRs #9305 and #9337 2025-11-12 17:25:40 +00:00
Jeremy Ruston
d5762b1fbb Add "filters" to change note categories
https://github.com/TiddlyWiki/TiddlyWiki5/pull/9390#issuecomment-3523005885
2025-11-12 17:20:26 +00:00
aka James4u
2b0739f06e Adds jsondelete operator to fix #9371 (#9390)
* Added jsondelter to fix 9371

* Replacced deprecated utils.isArray with Array.isArray, Refactored to remove duplication between setDataItem() and getDataItem()

* added changenotes for #9371

* Update #9371.tid

Fix key word: links-> github-links

* changed change-category

* updated github-links

* Apply suggestion from @saqimtiaz

---------

Co-authored-by: Saq Imtiaz <saq.imtiaz@gmail.com>
2025-11-12 16:33:28 +01:00
lin onetwo
e339f112a4 Fix/Widget css leading space (#9427)
* fix: trim tc-reveal class

* test: " tc-reveal" -> "tc-reveal"

* Create #9427.tid

* fix: more widgets

* Update #9427.tid
2025-11-12 11:57:02 +00:00
Saq Imtiaz
7a9235e9d1 Revert "Create LinOnetwo.tid (#9425)" (#9428)
This reverts commit bbbc8c2c03.
2025-11-12 12:23:05 +01:00
lin onetwo
bbbc8c2c03 Create LinOnetwo.tid (#9425) 2025-11-11 23:20:25 +00:00
Jeremy Ruston
538482e9a9 Release note fixes 2025-11-11 16:53:02 +00:00
Jeremy Ruston
df7973fc3e Merge branch 'tiddlywiki-com' 2025-11-11 16:47:36 +00:00
XLBilly
ae79736e82 Deprecate some CSS property macros (#9242)
* Deprecate some CSS property macros

* Update docs

* Remove -ms and -o prefix

* Revert "Update docs"

This reverts commit cdf535054a.

* Update docs

* Update docs

* Update change note

* Update comment
2025-11-11 16:46:02 +00:00
cdruan
244251ed44 Submit @cdruan change notes (#9421)
For PR #9295 #9341 #9358.
2025-11-11 16:29:26 +00:00
XLBilly
9164f305e0 Remove Opera & Microsoft prefix in browser.js (#9422)
* Remove Opera & Microsoft prefix in browser.js

* Add change notes
2025-11-11 16:28:27 +00:00
lin onetwo
64f86c2187 ci: Uses TiddlyWiki/cerebrus@v5, and replaces obsolete workflow (#9423)
* refactor: use TiddlyWiki/cerebrus@v5, and rename yml

* continue-on-error: false

* Update .github/workflows/pr-validation.yml to restrict contents permissions

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

---------

Co-authored-by: Saq Imtiaz <saq.imtiaz@gmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-11 16:13:41 +01:00
Jeremy Ruston
2d2ba61949 Revert "Bot to remind of change note (#9407)"
This reverts commit 845f988ab0.
2025-11-11 09:19:57 +00:00
Jeremy Ruston
0bbe170cf0 Merge branch 'tiddlywiki-com' 2025-11-10 10:22:16 +00:00
lin onetwo
845f988ab0 Bot to remind of change note (#9407)
* let claude write this

* Update validate-changenotes.yml

* Update validate-changenotes.yml

* Update validate-changenotes.yml

* Update validate-changenotes.yml

* refactor: move logic to sh file
2025-11-10 09:08:29 +00:00
lin onetwo
234667cc31 Add @linonetwo’s missing change notes (#9409)
* docs

(cherry picked from commit eb5d2c50c0e45712eafda0b0e79bd1816b5f8a5b)
(cherry picked from commit eecfd35336964c756547f9ea622566f8bab02709)

* typo

(cherry picked from commit d9129bcaf3f4face613b3f6ccfe06ce6ed45b2d5)
2025-11-09 21:17:29 +00:00
Jeremy Ruston
f1ce35036e Release Notes: Introduce impact notes attached to change notes (#9385)
* Introduce impact notes attached to change notes

No styles yet

* Add more tickets for demo and testing purposes

* Slightly better description for v5.4.0

* Formatting. More to do.

* Embarrassing typo

* Update field names

* Styling refinements

* Disable the #8702 changenote as it is not yet merged

* Add incompleteness warning
2025-11-09 16:05:00 +00:00
Jeremy Ruston
3cf71365a5 Fix reference to PNG version of new release banner 2025-11-05 12:11:20 +00:00
Jeremy Ruston
799618d9f5 Somewhat better new prerelease banner
Also converted it to webp

This doesn't usurp the competition we will hold for the final banner.
2025-11-05 12:02:16 +00:00
XLBilly
19177964c8 Improve switcher (UI) accessibility (#9347)
* Improve theme switcher accessibility

* Improve view switcher accessibility

* Replace set widgets with filters

* Improve languageswitcher & layoutswitcher accessibility

* Add indentation inside list widget

* Switch to functions
2025-11-01 13:06:19 +00:00
cdruan
c8e41bfade Remove redundant code in format/json.js (#9358)
* Remove redundant code in format/json.js

* Revise json.js
2025-11-01 12:56:22 +00:00
Bram Chen
fb4d417629 Update chinese language files (#9375)
* change camel-case hint text for chinese languages
2025-10-30 09:11:36 +01:00
lin onetwo
20d6be1e23 feat: serialize AST node back to wikitext string (#8258)
* refactor: extract a new $tw.wiki.getParser

* feat: allow $tw.utils.getParseTreeText to render other rules' text

* feat: two example getText handler

* Revert "feat: allow $tw.utils.getParseTreeText to render other rules' text"

This reverts commit 8a12498fa9.

* refactor: keep original getParseTreeText not touched

* refactor: use serialize in rules

* refactor: $tw.utils.extend({},options) -> options || {}

* Update codeinline.js

* Create test-wikitext-serialize.js

* DEBUG: only run my tests for development, remove before PR merge

* lint: if

* feat: add rule: 'parseBlock' metadata

* feat: handle tailing \n that may be missing

* feat: allow recursive

* feat: generate more rule and tests

* feat: generate more rule and tests

* fix: remove pragma:true, otherwise following text will become children of it

* fix: condition manually

Deekseek is silly

* fix: some test

* fix: some test

* feat: $tw.utils.serializeAttribute

* fix: use "" for string param

* feat: list

* refactor: ' -> "

* fix: parsemode don't have node

* fix: render invisible comment and parsemode as data element

* feat: add void: true, in ast node to prevent render

* feat: use void widget, so methods always return a widget

* feat: ast to use new widget type void

* test: add rule: 'parseBlock' and isRuleEnd: true

* lint: quote

* Update widget.js

* fix: void node need to handle its children

* Update test-wikitext-parser.js

* lint: quote

* Update void.js

* Update test-wikitext-parser.js

* fix: macrodef with comment (void node) not working

* lint: ' -> "

* feat: add to styleblock

* feat: styleblock

* feat: styleinline

* Update table.js

* lint: useless comments

* feat: transcludeblock

* refactor: reuse block on inline when possible

* feat: use void node to carry important info for typedblock

* feat: run all tests

* lint: useless ai generated comments

* Update conditional.js to not include space

* Update test-wikitext-serialize.js

* Update conditional.js

* refactor: move tiddlers to /data

* refactor: no need for new $tw.Wiki()

* lint: double quote

* refactor: lowercase the parseblock rule name

* fix: Wiki parser initialize blockRuleClasses only when first new an instance

* feat: restore inline macro def

* fix: macro in widget param

* fix: positional attribute in macro call

* fix: table space and horizrule block new line

* feat: make sure block rule all have \n\n for visiblity

* lint: function param

* fix: empty list item

* feat: add \n\n based on isBlock, if could also be inline

* fix: conditional without elseif

* refactor: use isBlock in macrodef to know inline or block

* fix: link may not have attribute and children

* DEBUG: render result and diff below body only on browser

DEBUG: render result below body only on browser

DEBUG: render result below body

DEBUG: fix build

DEBUG: show render result as ViewTemplate

* fix: remove pad space in />

* test: remove pad space in />

* Revert DEBUG: render result and diff below body only on browser

* refactor: fold commentText variable

* refactor: fold long comment

* fix: double quotes for parameter values

* Update void.js

* refactor: move all exports.serialize = function(tree,serialize) { to plugin

* fix: expost listTypes from core, and require it in plugin

* refactor: move serializeWikitextParseTree to plugin and init it

* refactor: move serializeAttribute util also to the plugin

* fix: Delete unused file

* Update macrodef.js

* Update test-wikitext-parser.js

* lint: fix

* Update plugins/tiddlywiki/wikitext-serialize/rules/filteredtranscludeblock.js

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

* Update core/modules/widgets/void.js

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

* Update plugins/tiddlywiki/wikitext-serialize/rules/list.js

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

* Update plugins/tiddlywiki/wikitext-serialize/rules/list.js

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

* Update plugins/tiddlywiki/wikitext-serialize/rules/styleblock.js

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

* Remove unused methods from VoidNodeWidget

Deleted render, execute, and refresh methods from VoidNodeWidget as they are no longer needed. The widget now only inherits from the base Widget class and exports the constructor.

* docs: about regex in styleinline.js

* Update parsetree.js

* Update core/modules/widgets/void.js

Co-authored-by: Jeremy Ruston <jeremy@jermolene.com>

* feat: Ensure at least one space after the style/class

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Jeremy Ruston <jeremy@jermolene.com>
2025-10-29 21:21:36 +00:00
Saq Imtiaz
7898cb8446 Revert "Fix 8166 html tag validation (#8176)" (#9374)
This reverts commit 9a5f4cc0ef.
2025-10-29 22:01:03 +01:00
Mario Pietsch
9a5f4cc0ef Fix 8166 html tag validation (#8176)
* wip proposal still contains commented old code - tests are missing

* rename isTagNameSafe to makeTagNameSafe

* remove comments

* remove redundant space

* add htmlCustomPrimitives to the $tw.config object

* remove tag-sanitation from element.js

* WIP - add html-element sanitation to the new makeTagNameSafe function, so it can be used globally

* simplify sanitation logic and fix inline docs

* Move top comment into inline comments
2025-10-29 17:42:28 +00:00
s793016
cda8d7ca8c Aho-Corasick Freelinks Enhancement for Large Wikis and Non-Latin Titles (#9084)
* Enhance Freelinks with Aho-Corasick for long titles and large wikis

Replaces regex with Aho-Corasick, adds chunking (100 titles/chunk), cache toggle, and Chinese full-width symbol support. Tested with 11,714 tiddlers.

* delete comment

* Create AhoCorasick.js

* Update text.js

* Update text.js

* Update AhoCorasick.js

* Update text.js

* Update text.js

* move AhoCorasick to AhoCorasick.js

* update AhoCorasick.js

* Delete core/modules/utils/AhoCorasick.js

wrong place

* Update text.js

* indentation modify

* remove function {}

* remove function {}

* Rename AhoCorasick.js to aho-corasick.js

correct filename

* Update tiddlywiki.info add freelink

* missing a comma here

* clean up comments & use old style

* try add it to editions/tw5.com-server for testing

* try add it to editions/prerelease for testing

* optimized

* optimized

* add setting for "Persist AhoCorasick cache"

* add  dynamic limits

* remove comment

* revert to 5f0b98d1fd

* try sort alphabet

* try sort alphabet

* try sort alphabet

* typo freelink -> freelinks

* typo freelink -> freelinks

* typo freelink -> freelinks

* Update readme.tid

* Update aho-corasick.js

Dynamically adjust limit parameters to avoid problems caused by hard-coded limits.

* Update text.js

Dynamically adjust limit parameters to avoid problems caused by hard-coded limits.

* Update tiddlywiki.info

remove other plugin for test plugin conflict

* Update tiddlywiki.info

* Update tiddlywiki.info

* Update aho-corasick.js

Description of major changes

Improve state transition logic - Ensure to go back to root node correctly in case of mismatch, and check root node for current character transition 
Fix failed link traversal - Add condition node ! == this.trie to avoid infinite loop at root node 
Enhance output collection - collect output not only from current node, but also from all nodes on failed link path, which is key to Aho-Corasick algorithm 
Add safety limit - collectCount < 10 to prevent failed link loops

Translated with DeepL.com (free version)

* Update aho-corasick.js

Word Boundary Check - The isWordBoundaryMatch function checks if the match is on a word boundary:

Alphanumeric characters [a-zA-Z0-9_] are regarded as unicode characters 
At least one non-unicode character must be present before and after the match for it to be considered valid.

* Update text.js

Word Boundary Check - The isWordBoundaryMatch function checks if the match is on a word boundary:

Alphanumeric characters [a-zA-Z0-9_] are regarded as unicode characters 
At least one non-unicode character must be present before and after the match for it to be considered valid.

* Update settings.tid

Word Boundary Check - The isWordBoundaryMatch function checks if the match is on a word boundary:

Alphanumeric characters [a-zA-Z0-9_] are regarded as unicode characters 
At least one non-unicode character must be present before and after the match for it to be considered valid.

* fix Word Boundary logic

* remove PersistentCache @ text.js

* remove PersistentCache @settings.tid

* Update readme.tid for Word Boundary Check

* Update aho-corasick.js Organize and delete comments

* Initial commit of freelinks plugin

* Update settings.tid Organize and delete comments

* Update tiddlywiki.info add back other plugin

* Update tiddlywiki.info alphabet sort

* Update readme.tid for new future

The plugin supports non-Western language tiddler titles (e.g., Chinese) and prioritizes longer tiddler titles for matching, ensuring accurate linking in diverse contexts. 

Furthermore, the current tiddler title within its own content is excluded from generating links to avoid self-referencing.

* Update readme.tid

* Update plugins/tiddlywiki/freelinks/text.js

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

* Update plugins/tiddlywiki/freelinks/aho-corasick.js

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

* Update plugins/tiddlywiki/freelinks/aho-corasick.js

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

* Update plugins/tiddlywiki/freelinks/text.js

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

* Update plugins/tiddlywiki/freelinks/aho-corasick.js

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

* Update text.js

Added locale configuration support - Added LOCALE_CONFIG_TIDDLER constant to make the sorting locale configurable instead of hardcoded "zh"
Optimized title processing - Combined the filtering and escaping logic into a single pass to reduce duplication
Added trim() for ignoreCase - Applied .trim() to the ignore case variable for consistency
Enhanced refresh logic - Added locale configuration tiddler to the refresh check
Improved comments - Added explanation for why sorting is necessary (prioritizing longer titles)

* Update text.js

we don't need to specify 'zh' at all

* Update aho-corasick.js

This single line change would add support for:

Accented letters: á, é, í, ó, ú, à, è, ì, ò, ù, ä, ë, ï, ö, ü, ñ, ç, etc.
Most Western European languages (Spanish, French, German, Italian, Portuguese, etc.)

* Update aho-corasick.js useage

* Update readme.tid for Writing Style

* Update tiddlywiki.info

revert all the changes

* Update tiddlywiki.info

revert all the changes

* Update tiddlywiki.info

revert all the changes

* Update tiddlywiki.info

revert

* Update text.js

plugins/tiddlywiki/freelinks/text.js#L25
[ESLint PR code] reported by reviewdog 🐶
Strings must use doublequote.

* Update aho-corasick.js

plugins/tiddlywiki/freelinks/aho-corasick.js#L193
[ESLint PR code] reported by reviewdog 🐶
Strings must use doublequote.

Raw Output:
{"ruleId":"@stylistic/quotes","severity":2,"message":"Strings must use doublequote.","line":193,"column":50,"nodeType":"Literal","messageId":"wrongQuotes","endLine":193,"endColumn":52,"fix":{"range":[5743,5745],"text":"\"\""}}

---------

Co-authored-by: Mario Pietsch <pmariojo@gmail.com>
2025-10-29 17:41:35 +00:00
Jeremy Ruston
f38e9f0822 Introduce browser messaging saver (#5512)
* First commit

* Subscriber: Make onmessage be async

* Don't use a variable before it's defined...
2025-10-29 14:52:21 +00:00
Jeremy Ruston
5848d66e96 Merge branch 'tiddlywiki-com' 2025-10-29 14:48:14 +00:00
well-noted
485051951e Allow intercept of audioparser by widget and pass attributes (#9024)
* Initial Commit

* Removes Function wrapper

* Remove Function wrapper from parser

* Convert spaces to tabs in audio.js widget

* Fix indentation

---------

Co-authored-by: Saq Imtiaz <saq.imtiaz@gmail.com>
2025-10-29 12:19:09 +00:00
XLBilly
135e685811 Improve alert accessibility (#9248)
* Improve alert accessibility

* Remove procedure
2025-10-29 12:08:01 +00:00
Simon Huber
99682c5731 [Bugfix] - keep focus on Element with tc-keep-focus class (#9233)
* Update factory.js

* add tc-keep-focus class to type input field

* tryna fix with energy
2025-10-29 12:00:40 +00:00
yaisog
d58eec47c0 Modify output of some math operators for empty inputs (#9337)
* Initial commit

* Adapt test to new behavior
2025-10-29 11:57:39 +00:00
Jeremy Ruston
059978ec63 Fix crash with empty transclusions (#8145)
* Initial commit

* Remove testing code
2025-10-29 11:33:17 +00:00
Mario Pietsch
b5e20a58a6 Muted palette, minor adjustments (#9015)
* Muted palette, minor adjustments

* revert download-background

* Change sidebar tiddler link foreground color

Change sidebar tiddler link foreground colour to make the search dropdown text readable. Using: #aaaaaa
2025-10-29 11:18:31 +00:00
Mario Pietsch
e8fe6b98bc Change camel-case hint, since it is disabled by default now. (#9211)
* Change camel-case hint, since it is disabled by default now.

* change camel-case hint text for other languages, where it is obvious
2025-10-29 11:18:00 +00:00
Mario Pietsch
317104774c list-links-draggable defaults to currentTiddler if tiddler parameter is missing (#9254)
* list-links-draggable if tiddler parameter undefined default to currentTiddler

* update list-links-draggable docs with the new behaviour
2025-10-29 11:17:02 +00:00
Simon Huber
deed8631d8 exports.search: don't break the loop if encoding is base64, just continue (#9247)
This makes it possible to find tiddlers with base64 encoding using `search:*:words[searchterm]` for example

fixes #9246
2025-10-29 11:15:54 +00:00
XLBilly
d733b77e2f Replace CSS property macros in Snow White theme (#9243) 2025-10-29 11:07:37 +00:00
Simon Huber
46c26a64b9 Update framed.js to avoid quirks mode (#9227) 2025-10-29 11:05:59 +00:00
XLBilly
ec81d6663b [5.4.0] Update highlightjs plugin (#9118)
* Bump highlightjs version to 11.11.1

* highlightblock.js support es6
2025-10-29 11:04:53 +00:00
bepuzzled
23f0a9bf79 Improve handling of $link widget's draggable="no" (#9262)
* fix to to improve browser's compliance with user's intent when the widget's draggable attribute is set to no

* Update link.js with comments from the PR review

indentation changed from spaces to tabs
modified else statement into a else if statement for additional specificity

---------

Co-authored-by: Frédéric Demers <fastfreddy@fdemers.ca>
2025-10-29 11:03:19 +00:00
Rishu Kumar
40cc62f727 fix: renaming of incoming tiddlers being imported (#9368)
* fix: UX on pasting image

* fix: Rename on pasting image from clipboard
2025-10-27 16:13:42 +01:00
Cameron Fischer
b5153c0066 :reverse was ignored when prefix sorting by version (#9280) 2025-10-23 12:03:35 +01:00
lin onetwo
b061f90f87 Get-file web server route now support streaming (#9078)
* Update get-file.js

* Update WebServer API_ Get File.tid

* Update get-file.js

* Update get-file.js

* Update get-file.js

* Update get-file.js

* Update core-server/server/routes/get-file.js

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

* Update core-server/server/routes/get-file.js

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

* Update get-file.js

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-10-23 11:55:30 +01:00
Jeremy Ruston
8be83cf01b Merge branch 'tiddlywiki-com' 2025-10-21 15:45:21 +01:00
Scott Sauyet
ad6ac480f9 Remove css props rather than setting transition to none (#9284)
* Use style property deletion rather than setting to none

* Remove cecily and slide, fix quotation marks

* Fix returning typo

* Add helper functions, use some timeouts to order events
2025-10-21 11:57:10 +01:00
Cameron Fischer
8168512e95 Support for downloading base64 files (#9297)
* Support for downloading base64 files

* Added comment explaining why we'd use data urls over blobs

* Using more modern string##includes method
2025-10-21 11:53:32 +01:00
Leilei332
276fdc8634 Purge deprecated plugins and editions (#9350) 2025-10-21 11:51:12 +01:00
yaisog
0b38ced43a Preserve the module insertion order in forEachModuleOfType (#9305)
* Initial commit

* Update testcase

* Revert "Update testcase"

This reverts commit 0d24c4233f.

* Revert initial commit and swap definition order
2025-10-21 11:37:55 +01:00
cdruan
d7e48207b9 Fix crash when processing large files from tiddlywiki.files (#9341)
* skip reading file content when _canonical_uri is present

* skip loading file when file size is too large
2025-10-21 11:30:39 +01:00
欧阳
a6383aaaea feat: specify the space applied between CJK and non-CJK characters (#9349) 2025-10-21 11:26:32 +01:00
Joseph Yi
61619c07c8 chore: bump checkout action v4 to v5 (#9342) 2025-10-15 21:27:23 +01:00
Jeremy Ruston
61e638c972 Merge branch 'tiddlywiki-com' 2025-10-11 15:53:49 +01:00
lin onetwo
6bdd51d72a Fix (eslint): removes unneeded dependencies (#9332)
* Clean up eslint.config.mjs by removing unused import and comments

Removed commented-out import statements for clarity.

* Remove @eslint/compat from devDependencies

Removed deprecated @eslint/compat dependency.

* Update eslint.config.mjs

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

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-10-11 14:11:21 +01:00
Jeremy Ruston
fbf110e209 Merge branch 'tiddlywiki-com' 2025-10-10 14:58:29 +01:00
cdruan
91871d2ced Markdown: Fix missing inline support and macrocall args parsing error (#9295)
* Rewrote tw_macrocallinline() parser so macrocall args containing ">"
will be parsed properly.

* Use $tw.log.MARKDOWN flag to print debug messages.

* Fix markdown parser to respect parseAsInline option. (#8917)
2025-10-10 13:31:11 +01:00
Leilei332
5143da9cce Fix CTRL-alt-right shortcut not working (#9325) 2025-10-10 13:30:22 +01:00
Leilei332
186d1b014a Migrate most deprecated eslint rules (#9328)
* Migrate to eslint stylistic

* Remove & replace some deprecated rules
2025-10-10 13:29:35 +01:00
Saq Imtiaz
58e41ee0ad Feature: extend server routes to support multiple methods (#9207) 2025-10-10 12:37:09 +01:00
Saq Imtiaz
97824cc3a3 Feature: extend server route exports to allow ordering (#9206) 2025-10-10 12:36:36 +01:00
Arlen Beiler
6e493755be Add support for commands and startups which return promises (#9103) 2025-10-09 16:06:03 +01:00
Jeremy Ruston
64fce62075 [v5.4.0] Update configuration defaults (#9107)
* Change default sidebar layout to fluid-fixed

* Default to not wrapping code blocks

* codewrapping should be "pre" not "no"

Co-authored-by: Leilei332 <LeiYiXia29@outlook.com>

* Info panel should stay open by default

---------

Co-authored-by: Leilei332 <LeiYiXia29@outlook.com>
2025-10-08 12:32:34 +01:00
Leilei332
5a4ff56477 Fix markup not included in external core edition (#9218) 2025-10-08 10:41:08 +01:00
Leilei332
3889a2a0d0 Fix nested span.tc-keyboard element in core (#9235)
* Fix nested `span.keyboard` element in core

* Fix switching search results not working
2025-10-08 10:28:59 +01:00
Leilei332
34737f4e28 Improve ARIA support for link widget (#9167) 2025-10-07 15:46:43 +01:00
Leilei332
94673a1028 Allow button widget to use all ARIA attibutes (#9154)
* Allow button widget to use all ARIA attibutes

* Add link in docs

* Update docs
2025-10-07 13:05:09 +01:00
Mario Pietsch
b462aaa9a3 Modules in draft tidders should not be executed (#9293)
* modules in draft tiddlers should not be defined

* improve warning message and use console.warn
2025-10-07 12:03:11 +01:00
Mario Pietsch
4552c117fc Add th-debug-element hook and data-debug-element minimal useful info (#9281)
* add th-debug-element hook and date-debug-element minimal useful info

* invoke th-dom-rendering-element hook

* improve comment
2025-10-07 12:02:08 +01:00
Leilei332
dc7f2a57bb Use currentColor to style svg (#9316)
* Replace fill: with currentColor

* Remove fill from wikitext

* Replace fill property with color property for svg selectors

* Further remove fill property

* Replace more fill properties

* Replace fill in seamless theme

* Replace fill in plugins
2025-10-07 11:55:26 +01:00
Jeremy Ruston
7e91bac6b8 Merge branch 'tiddlywiki-com' 2025-10-07 10:43:50 +01:00
Leilei332
15ba415a45 Use s instead of strike (#9131)
* Use s instead of strike

* Update docs
2025-10-06 17:28:29 +01:00
Leilei332
2405e65308 Remove support for IE in range widget (#9275) 2025-10-06 17:26:34 +01:00
Saq Imtiaz
865f36a6f5 Removes deprecated attributes from $eventcatcher (#9259)
* fix: removed deprecated attributes

* docs: update for eventcatcher
2025-10-06 17:12:08 +01:00
Saq Imtiaz
a36356a07d Adds info tiddlers for viewport dimensions (#9260)
* feat: added info tiddlers for viewport dimensions

* feat: multi window support for dimensions info tiddlers

* refactor: introduce standalone eventbus and refactor for ES2017

* docs: extended docs for InfoMechanism to cover new additions
2025-10-06 17:00:13 +01:00
Leilei332
ee23097816 Set modal's mask-closable attribute to yes by default (#9313)
* Set modal's mask-closable attribute to yes by default

* Make backdrop closable only when field value is yes or empty
2025-10-06 16:40:20 +01:00
Mario Pietsch
e753851d49 Add output directory to eslint ignore configuration (#9317) 2025-10-06 16:27:11 +01:00
Leilei332
cae3d0fa2c Switch to native support for base64 utf8 (#9253)
* Switch to native support for base64 utf8

* Fix error on nodejs
2025-10-06 16:25:45 +01:00
Saq Imtiaz
afe2ac45d9 Fix: Update eslint.yml to not run on push 2025-10-02 22:38:59 +02:00
Saq Imtiaz
6791f0f130 Create eslint.yml (#9315)
Adds a workflow to lint PRs, and reports issues found in lines modified in the PR.
2025-10-02 22:33:09 +02:00
Mario Pietsch
87ba87bdd2 Set AES strength to 256 bit (#8249)
* Set AES strength to 256 bit

* Update Encryption tiddler to AES 256
2025-10-02 11:49:45 +01:00
Leilei332
619bdfcab5 Reimplement regexp sticky flag (#9119) 2025-10-02 11:42:30 +01:00
Arlen Beiler
f8170cd50a [5.4.0] Update eslint target to 2017 and do initial fixes (#9135)
* eslint manual fixes
- update eslint to 2017
- add self to forbidden globals
- fix a few unfixable bugs caught by eslint
- convert newer features in twitter-archivist

* add eslint plugin to forbid features

* import changes from @saqimtiaz

* add package.json changes
2025-10-01 15:08:00 +01:00
Jeremy Ruston
5389dc0fa7 Merge branch 'tiddlywiki-com' 2025-09-25 16:48:39 +01:00
Jeremy Ruston
810ac42810 Release notes: show current banner for the current release 2025-09-14 14:36:31 +01:00
Jeremy Ruston
88d3f69a3b Merge branch 'tiddlywiki-com' 2025-09-14 14:32:25 +01:00
Jeremy Ruston
e956bb32e9 Extend release note mechanism to allow multiple github commits/prs for a change
Also added @Leilei332's credit for #9288
2025-09-14 11:43:22 +01:00
Leilei332
af63a3b920 Further improvements for offloading server components (#9288)
* Move edition-info and repository to core-server

* Update readme

* Set platform to server for filesystem plugin

* Move startup/command.js
2025-09-14 11:21:42 +01:00
Jeremy Ruston
e4fb47ee76 Add change note for #9183 2025-09-12 17:45:13 +01:00
Jeremy Ruston
7944f42467 [v5.4.0] Offload server components from browser builds of TiddlyWiki (#9183)
* Move Node.js specific files out of the core plugin

* Package server files as new $:/core-server plugin

* Missed commander.js

* Fix crash in browser

* Extend server-only mechanism to be usable by other plugins

* in

* Revert "Extend server-only mechanism to be usable by other plugins"

This reverts commit 3faf503073.

* Revert "in"

This reverts commit b80213128f.

* Reapply "Extend server-only mechanism to be usable by other plugins"

This reverts commit c6c83bc18b.

* Fix test failure

* Move filesystem utilities into core-server

* Move old-style release notes out of the way

* Move the 5.4.0 release note into the right place

* Revert "Move the 5.4.0 release note into the right place"

This reverts commit 3f5c2bfba3.

* Revert "Move old-style release notes out of the way"

This reverts commit ee16e48a43.
2025-09-12 15:21:34 +01:00
Jeremy Ruston
578c09e0ce Remove change note for #9183 until it is merged 2025-09-12 15:19:28 +01:00
Jeremy Ruston
ffb0a2fde2 Merge branch 'tiddlywiki-com' 2025-09-12 15:15:58 +01:00
Jeremy Ruston
c5a80a1984 [v5.4.0] New release note architecture (#9287)
* Initial Commit

* Move the old release notes out of the way

* A self-referential change note

* Fix procedure name

* Rationalise link badges in old release notes

Some of them were hardly used, and getting rid of them makes it easier to see what badges we need in the new system

* Update 5.4.0 and 5.3.8 to use the new format
2025-09-12 15:13:07 +01:00
Jeremy Ruston
3592a0be67 Merge branch 'tiddlywiki-com' 2025-09-09 21:15:51 +01:00
Jeremy Ruston
3c45c17b45 Merge branch 'tiddlywiki-com' 2025-09-02 18:02:33 +01:00
Jeremy Ruston
1a81f8ee06 Merge branch 'tiddlywiki-com' 2025-09-01 16:54:26 +01:00
Jeremy Ruston
ae970fde02 Merge branch 'tiddlywiki-com' 2025-09-01 12:13:46 +01:00
Jeremy Ruston
e38d7960a9 Merge branch 'tiddlywiki-com' 2025-09-01 10:44:05 +01:00
Jeremy Ruston
2246ffdc05 Merge branch 'tiddlywiki-com' 2025-08-29 12:09:23 +01:00
Jeremy Ruston
dba34f3323 Merge branch 'tiddlywiki-com' 2025-08-26 17:50:30 +01:00
Jeremy Ruston
617394ff08 Merge branch 'tiddlywiki-com' 2025-08-21 11:32:36 +01:00
Jeremy Ruston
373eec45cd Add final batch of survey responses 2025-08-19 10:40:18 +01:00
Jeremy Ruston
38462d4464 Merge branch 'tiddlywiki-com' 2025-08-07 19:16:55 +01:00
Jeremy Ruston
b2656c82dd Preparing for v5.4.0 2025-08-07 09:33:55 +01:00
648 changed files with 9383 additions and 7564 deletions

4
.gitattributes vendored Normal file
View File

@@ -0,0 +1,4 @@
boot/** -linguist-generated
**/tiddlywiki.files linguist-language=JSON
**/tiddlywiki.info linguist-language=JSON
**/plugin.info linguist-language=JSON

62
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@@ -0,0 +1,62 @@
---
name: Bug report
about: Create a report to help us improve TiddlyWiki 5
title: "[Report] "
type: report
---
<!-- Remove elements, that you do not need -->
<!-- Add screenshots where needed -->
**Problem Description**
<!-- Describe your problem: A clear and concise description of what your problem is -->
**To Reproduce**
Steps to reproduce the behavior:
1. At https://tiddlywiki.com
2. Click on ...
3. Scroll down to ...
4. See ...
**Expected behavior**
As a user,
<!-- As a developer, -->
I would expect ...
**TiddlyWiki Configuration**
<!-- Please complete the following information -->
- Report created with: [Wiki Information](https://tiddlywiki.com/#%24%3A%2Fcore%2Fui%2FControlPanel%2FWikiInformation)
<!-- Your report comes here -->
<!-- or -->
<!-- Add it manually -->
- Version: <!-- e.g. v5.3.8 -->
- Saving mechanism: <!-- e.g. Node.js, TiddlyDesktop, TiddlyHost etc -->
- Plugins installed: <!-- e.g. Freelinks, TiddlyMap ... other 3rd party plugins -->
**Desktop**
<!-- Please complete the following information -->
- OS: <!-- e.g. iOS -->
- Browser: <!-- e.g. chrome, safari, FireFox -- Version: -->
**Smartphone**
<!-- Please complete the following information -->
- Device: <!-- e.g. iPhone6 -->
- OS: <!-- e.g. iOS8.1 -->
- Browser: <!-- e.g. stock browser, safari, FireFox -- Version: -->
**Additional context**
<!-- Add any other context about the problem here. -->

View File

@@ -1,67 +0,0 @@
name: Bug report
description: Create a report to help us improve TiddlyWiki 5
title: "[BUG] "
body:
- type: textarea
id: Describe
attributes:
label: Describe the bug
description: A clear and concise description of what the bug is.
validations:
required: true
- type: textarea
id: Expected
attributes:
label: Expected behavior
description: A clear and concise description of what you expected to happen.
validations:
required: false
- type: textarea
id: Reproduce
attributes:
label: To Reproduce
description: "Steps to reproduce the behavior:"
placeholder: |
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
validations:
required: false
- type: textarea
id: Screenshots
attributes:
label: Screenshots
description: If applicable, add screenshots to help explain your problem.
placeholder: Drag image here to upload screenshot!
validations:
required: false
- type: textarea
id: Configuration
attributes:
label: TiddlyWiki Configuration
description: please complete the following information
placeholder: |
- Version [e.g. v5.1.24]
- Saving mechanism [e.g. Node.js, TiddlyDesktop, TiddlyHost etc]
- Plugins installed [e.g. Freelinks, TiddlyMap]
### Desktop (please complete the following information):
- OS: [e.g. iOS]
- Browser [e.g. chrome, safari]
- Version [e.g. 22]
### Smartphone (please complete the following information):
- Device: [e.g. iPhone6]
- OS: [e.g. iOS8.1]
- Browser [e.g. stock browser, safari]
- Version [e.g. 22]
validations:
required: true
- type: textarea
id: Context
attributes:
label: Additional context
description: Add any other context about the problem here.

View File

@@ -4,17 +4,23 @@ about: Suggest an idea for TiddlyWiki 5
title: "[IDEA]"
labels: ''
assignees: ''
type: idea
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Is your idea related to a problem? Please describe.**
A clear and concise description of what the problem is. Eg:
As a user, I would like [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

View File

@@ -10,7 +10,7 @@ jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- uses: actions/setup-node@v4
with:
node-version: "${{ env.NODE_VERSION }}"
@@ -30,7 +30,7 @@ jobs:
TW5_BUILD_MAIN_EDITION: "./editions/prerelease"
TW5_BUILD_OUTPUT: "./output/prerelease"
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- uses: actions/setup-node@v4
with:
node-version: "${{ env.NODE_VERSION }}"
@@ -62,7 +62,7 @@ jobs:
TW5_BUILD_OUTPUT: "./output"
TW5_BUILD_ARCHIVE: "./output"
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- uses: actions/setup-node@v4
with:
node-version: "${{ env.NODE_VERSION }}"

40
.github/workflows/eslint.yml vendored Normal file
View File

@@ -0,0 +1,40 @@
name: ESLint
on:
pull_request:
types: [opened, synchronize, reopened]
workflow_dispatch:
concurrency:
group: lint-${{ github.event.pull_request.number || github.ref_name }}
cancel-in-progress: true
permissions:
contents: read
# Needed for GitHub Checks API
checks: write
jobs:
eslint:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v5
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
- name: Install dependencies
run: npm install --include=dev
- name: Run ESLint with reviewdog (GitHub Checks)
uses: reviewdog/action-eslint@v1
with:
eslint_flags: '.'
reporter: github-pr-check
fail_level: error
level: error
tool_name: ESLint PR code

View File

@@ -20,7 +20,7 @@ jobs:
steps:
- name: build-size-check
id: get_sizes
uses: TiddlyWiki/cerebrus@v4
uses: TiddlyWiki/cerebrus@v6
with:
pr_number: ${{ github.event.pull_request.number }}
repo: ${{ github.repository }}

View File

@@ -25,7 +25,7 @@ jobs:
steps:
- name: Build and check size
uses: TiddlyWiki/cerebrus@v4
uses: TiddlyWiki/cerebrus@v6
with:
pr_number: ${{ inputs.pr_number }}
repo: ${{ github.repository }}

View File

@@ -1,18 +0,0 @@
name: Validate PR Paths
on:
pull_request_target:
types: [opened, reopened, synchronize]
jobs:
validate-pr:
runs-on: ubuntu-latest
steps:
- name: Validate PR
uses: TiddlyWiki/cerebrus@v4
with:
pr_number: ${{ github.event.pull_request.number }}
repo: ${{ github.repository }}
base_ref: ${{ github.base_ref }}
github_token: ${{ secrets.GITHUB_TOKEN }}

37
.github/workflows/pr-validation.yml vendored Normal file
View File

@@ -0,0 +1,37 @@
name: PR Validation
on:
pull_request_target:
types: [opened, reopened, synchronize]
permissions:
contents: read
pull-requests: write
issues: write
jobs:
validate-pr:
runs-on: ubuntu-latest
steps:
# Step 1: Validate PR paths
- name: Validate PR Paths
uses: TiddlyWiki/cerebrus@v6
with:
pr_number: ${{ github.event.pull_request.number }}
repo: ${{ github.repository }}
base_ref: ${{ github.event.pull_request.base.ref }}
github_token: ${{ secrets.GITHUB_TOKEN }}
mode: rules
continue-on-error: true
# Step 2: Validate change notes
- name: Validate Change Notes
uses: TiddlyWiki/cerebrus@v6
with:
pr_number: ${{ github.event.pull_request.number }}
repo: ${{ github.repository }}
base_ref: ${{ github.event.pull_request.base.ref }}
github_token: ${{ secrets.GITHUB_TOKEN }}
mode: changenotes
continue-on-error: false

View File

@@ -5,7 +5,7 @@
# Default to the current version number for building the plugin library
if [ -z "$TW5_BUILD_VERSION" ]; then
TW5_BUILD_VERSION=v5.3.8
TW5_BUILD_VERSION=v5.4.0
fi
echo "Using TW5_BUILD_VERSION as [$TW5_BUILD_VERSION]"
@@ -73,10 +73,8 @@ rm $TW5_BUILD_OUTPUT/dev/static/*
echo "<a href='./plugins/tiddlywiki/tw2parser/index.html'>Moved to http://tiddlywiki.com/plugins/tiddlywiki/tw2parser/index.html</a>" > $TW5_BUILD_OUTPUT/classicparserdemo.html
echo "<a href='./plugins/tiddlywiki/codemirror/index.html'>Moved to http://tiddlywiki.com/plugins/tiddlywiki/codemirror/index.html</a>" > $TW5_BUILD_OUTPUT/codemirrordemo.html
echo "<a href='./plugins/tiddlywiki/d3/index.html'>Moved to http://tiddlywiki.com/plugins/tiddlywiki/d3/index.html</a>" > $TW5_BUILD_OUTPUT/d3demo.html
echo "<a href='./plugins/tiddlywiki/highlight/index.html'>Moved to http://tiddlywiki.com/plugins/tiddlywiki/highlight/index.html</a>" > $TW5_BUILD_OUTPUT/highlightdemo.html
echo "<a href='./plugins/tiddlywiki/markdown/index.html'>Moved to http://tiddlywiki.com/plugins/tiddlywiki/markdown/index.html</a>" > $TW5_BUILD_OUTPUT/markdowndemo.html
echo "<a href='./plugins/tiddlywiki/tahoelafs/index.html'>Moved to http://tiddlywiki.com/plugins/tiddlywiki/tahoelafs/index.html</a>" > $TW5_BUILD_OUTPUT/tahoelafs.html
# Put the build details into a .tid file so that it can be included in each build (deleted at the end of this script)
@@ -301,26 +299,6 @@ node $TW5_BUILD_TIDDLYWIKI \
--rendertiddler $:/core/save/empty plugins/tiddlywiki/katex/empty.html text/plain \
|| exit 1
# /plugins/tiddlywiki/tahoelafs/index.html Demo wiki with Tahoe-LAFS plugin
# /plugins/tiddlywiki/tahoelafs/empty.html Empty wiki with Tahoe-LAFS plugin
node $TW5_BUILD_TIDDLYWIKI \
./editions/tahoelafs \
--load $TW5_BUILD_OUTPUT/build.tid \
--output $TW5_BUILD_OUTPUT \
--rendertiddler $:/core/save/all plugins/tiddlywiki/tahoelafs/index.html text/plain \
--rendertiddler $:/core/save/empty plugins/tiddlywiki/tahoelafs/empty.html text/plain \
|| exit 1
# /plugins/tiddlywiki/d3/index.html Demo wiki with D3 plugin
# /plugins/tiddlywiki/d3/empty.html Empty wiki with D3 plugin
node $TW5_BUILD_TIDDLYWIKI \
./editions/d3demo \
--load $TW5_BUILD_OUTPUT/build.tid \
--output $TW5_BUILD_OUTPUT \
--rendertiddler $:/core/save/all plugins/tiddlywiki/d3/index.html text/plain \
--rendertiddler $:/core/save/empty plugins/tiddlywiki/d3/empty.html text/plain \
|| exit 1
# /plugins/tiddlywiki/codemirror/index.html Demo wiki with codemirror plugin
# /plugins/tiddlywiki/codemirror/empty.html Empty wiki with codemirror plugin
node $TW5_BUILD_TIDDLYWIKI \

View File

@@ -8,6 +8,8 @@ On the server this file is executed directly to boot TiddlyWiki. In the browser,
\*/
/* eslint-disable @stylistic/indent */
var _boot = (function($tw) {
/*jslint node: true, browser: true */
@@ -44,12 +46,8 @@ $tw.utils.hop = function(object,property) {
return object ? Object.prototype.hasOwnProperty.call(object,property) : false;
};
/*
Determine if a value is an array
*/
$tw.utils.isArray = function(value) {
return Object.prototype.toString.call(value) == "[object Array]";
};
/** @deprecated Use Array.isArray instead */
$tw.utils.isArray = value => Array.isArray(value);
/*
Check if an array is equal by value and by reference.
@@ -128,35 +126,22 @@ $tw.utils.pushTop = function(array,value) {
return array;
};
/*
Determine if a value is a date
*/
$tw.utils.isDate = function(value) {
return Object.prototype.toString.call(value) === "[object Date]";
};
/** @deprecated Use instanceof Date instead */
$tw.utils.isDate = value => value instanceof Date;
/*
Iterate through all the own properties of an object or array. Callback is invoked with (element,title,object)
*/
/** @deprecated Use array iterative methods instead */
$tw.utils.each = function(object,callback) {
var next,f,length;
if(object) {
if(Object.prototype.toString.call(object) == "[object Array]") {
for(f=0, length=object.length; f<length; f++) {
next = callback(object[f],f,object);
if(next === false) {
break;
}
}
if(Array.isArray(object)) {
object.every((element,index,array) => {
const next = callback(element,index,array);
return next !== false;
});
} else {
var keys = Object.keys(object);
for(f=0, length=keys.length; f<length; f++) {
var key = keys[f];
next = callback(object[key],key,object);
if(next === false) {
break;
}
}
Object.entries(object).every(entry => {
const next = callback(entry[1], entry[0], object);
return next !== false;
});
}
}
};
@@ -331,32 +316,13 @@ $tw.utils.htmlDecode = function(s) {
return s.toString().replace(/&lt;/mg,"<").replace(/&nbsp;/mg,"\xA0").replace(/&gt;/mg,">").replace(/&quot;/mg,"\"").replace(/&amp;/mg,"&");
};
/*
Get the browser location.hash. We don't use location.hash because of the way that Firefox auto-urldecodes it (see http://stackoverflow.com/questions/1703552/encoding-of-window-location-hash)
*/
$tw.utils.getLocationHash = function() {
var href = window.location.href;
var idx = href.indexOf('#');
if(idx === -1) {
return "#";
} else if(href.substr(idx + 1,1) === "#" || href.substr(idx + 1,3) === "%23") {
// Special case: ignore location hash if it itself starts with a #
return "#";
} else {
return href.substring(idx);
}
};
/** @deprecated Use window.location.hash instead. */
$tw.utils.getLocationHash = () => window.location.hash;
/*
Pad a string to a given length with "0"s. Length defaults to 2
*/
$tw.utils.pad = function(value,length) {
length = length || 2;
var s = value.toString();
if(s.length < length) {
s = "000000000000000000000000000".substr(0,length - s.length) + s;
}
return s;
/** @deprecated Pad a string to a given length with "0"s. Length defaults to 2 */
$tw.utils.pad = function(value,length = 2) {
const s = value.toString();
return s.padStart(length, "0");
};
// Convert a date into UTC YYYYMMDDHHMMSSmmm format
@@ -630,7 +596,7 @@ $tw.utils.evalGlobal = function(code,context,filename,sandbox,allowGlobals) {
// Compile the code into a function
var fn;
if($tw.browser) {
fn = window["eval"](code + "\n\n//# sourceURL=" + filename);
fn = window["eval"](code + "\n\n//# sourceURL=" + filename); // eslint-disable-line no-eval -- See https://github.com/TiddlyWiki/TiddlyWiki5/issues/6839
} else {
if(sandbox){
fn = vm.runInContext(code,sandbox,filename)
@@ -641,7 +607,7 @@ $tw.utils.evalGlobal = function(code,context,filename,sandbox,allowGlobals) {
// Call the function and return the exports
return fn.apply(null,contextValues);
};
$tw.utils.sandbox = !$tw.browser ? vm.createContext({}) : undefined;
$tw.utils.sandbox = !$tw.browser ? vm.createContext({}) : undefined;
/*
Run code in a sandbox with only the specified context variables in scope
*/
@@ -799,12 +765,13 @@ the password, and to encrypt/decrypt a block of text
$tw.utils.Crypto = function() {
var sjcl = $tw.node ? (global.sjcl || require("./sjcl.js")) : window.sjcl,
currentPassword = null,
callSjcl = function(method,inputText,password) {
callSjcl = function(method,inputText,password,options) {
options = options || {};
password = password || currentPassword;
var outputText;
try {
if(password) {
outputText = sjcl[method](password,inputText);
outputText = sjcl[method](password,inputText,options);
}
} catch(ex) {
console.log("Crypto error:" + ex);
@@ -830,7 +797,8 @@ $tw.utils.Crypto = function() {
return !!currentPassword;
}
this.encrypt = function(text,password) {
return callSjcl("encrypt",text,password);
// 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"});
};
this.decrypt = function(text,password) {
return callSjcl("decrypt",text,password);
@@ -1433,7 +1401,7 @@ $tw.Wiki = function(options) {
checkTiddler = function(tiddler,title) {
if(tiddler && tiddler.fields.type === "application/json" && tiddler.fields["plugin-type"] && (!pluginType || tiddler.fields["plugin-type"] === pluginType)) {
var disablingTiddler = self.getTiddler("$:/config/Plugins/Disabled/" + title);
if(title === "$:/core" || !disablingTiddler || (disablingTiddler.fields.text || "").trim() !== "yes") {
if(title === "$:/core" || title === "$:/core-server" || !disablingTiddler || (disablingTiddler.fields.text || "").trim() !== "yes") {
self.unregisterPluginTiddlers(null,[title]); // Unregister the plugin if it's already registered
pluginTiddlers.push(tiddler);
registeredTitles.push(tiddler.fields.title);
@@ -1530,7 +1498,8 @@ Define all modules stored in ordinary tiddlers
*/
$tw.Wiki.prototype.defineTiddlerModules = function() {
this.each(function(tiddler,title) {
if(tiddler.hasField("module-type")) {
// Modules in draft tiddlers are disabled
if(tiddler.hasField("module-type") && (!tiddler.hasField("draft.of"))) {
switch(tiddler.fields.type) {
case "application/javascript":
// We only define modules that haven't already been defined, because in the browser modules in system tiddlers are defined in inline script
@@ -1557,6 +1526,11 @@ $tw.Wiki.prototype.defineShadowModules = function() {
this.eachShadow(function(tiddler,title) {
// Don't define the module if it is overidden by an ordinary tiddler
if(!self.tiddlerExists(title) && tiddler.hasField("module-type")) {
if(tiddler.hasField("draft.of")) {
// Report a fundamental problem
console.warn(`TiddlyWiki: Plugins should not contain tiddlers with a 'draft.of' field: ${tiddler.fields.title}`);
return;
}
// Define the module
$tw.modules.define(tiddler.fields.title,tiddler.fields["module-type"],tiddler.fields.text);
}
@@ -1905,7 +1879,7 @@ $tw.loadTiddlersFromFile = function(filepath,fields) {
fileSize = fs.statSync(filepath).size,
data;
if(fileSize > $tw.config.maxEditFileSize) {
data = "File " + filepath + "not loaded because it is too large";
data = "File " + filepath + " not loaded because it is too large";
console.log("Warning: " + data);
ext = ".txt";
} else {
@@ -1976,22 +1950,41 @@ filepath: pathname of the directory containing the specification file
$tw.loadTiddlersFromSpecification = function(filepath,excludeRegExp) {
var tiddlers = [];
// Read the specification
var filesInfo = $tw.utils.parseJSONSafe(fs.readFileSync(filepath + path.sep + "tiddlywiki.files","utf8"));
var filesInfo = $tw.utils.parseJSONSafe(fs.readFileSync(filepath + path.sep + "tiddlywiki.files","utf8"), function(e) {
console.log("Warning: tiddlywiki.files in " + filepath + " invalid: " + e.message);
return {};
});
// Helper to process a file
var processFile = function(filename,isTiddlerFile,fields,isEditableFile,rootPath) {
var extInfo = $tw.config.fileExtensionInfo[path.extname(filename)],
type = (extInfo || {}).type || fields.type || "text/plain",
typeInfo = $tw.config.contentTypeInfo[type] || {},
pathname = path.resolve(filepath,filename),
text = fs.readFileSync(pathname,typeInfo.encoding || "utf8"),
metadata = $tw.loadMetadataForFile(pathname) || {},
fileTiddlers;
fileTooLarge = false,
text, fileTiddlers;
if("_canonical_uri" in fields) {
text = "";
} else if(fs.statSync(pathname).size > $tw.config.maxEditFileSize) {
var msg = "File " + pathname + " not loaded because it is too large";
console.log("Warning: " + msg);
fileTooLarge = true;
text = isTiddlerFile ? msg : "";
} else {
text = fs.readFileSync(pathname,typeInfo.encoding || "utf8");
}
if(isTiddlerFile) {
fileTiddlers = $tw.wiki.deserializeTiddlers(path.extname(pathname),text,metadata) || [];
fileTiddlers = $tw.wiki.deserializeTiddlers(fileTooLarge ? ".txt" : path.extname(pathname),text,metadata) || [];
} else {
fileTiddlers = [$tw.utils.extend({text: text},metadata)];
}
var combinedFields = $tw.utils.extend({},fields,metadata);
if(fileTooLarge && isTiddlerFile) {
delete combinedFields.type; // type altered
}
$tw.utils.each(fileTiddlers,function(tiddler) {
$tw.utils.each(combinedFields,function(fieldInfo,name) {
if(typeof fieldInfo === "string" || $tw.utils.isArray(fieldInfo)) {
@@ -2066,6 +2059,7 @@ $tw.loadTiddlersFromSpecification = function(filepath,excludeRegExp) {
} else if(tidInfo.suffix) {
tidInfo.fields.text = {suffix: tidInfo.suffix};
}
tidInfo.fields = tidInfo.fields || {};
processFile(tidInfo.file,tidInfo.isTiddlerFile,tidInfo.fields);
});
// Process any listed directories
@@ -2087,6 +2081,7 @@ $tw.loadTiddlersFromSpecification = function(filepath,excludeRegExp) {
var thisPath = path.relative(filepath, files[t]),
filename = path.basename(thisPath);
if(filename !== "tiddlywiki.files" && !metaRegExp.test(filename) && fileRegExp.test(filename)) {
dirSpec.fields = dirSpec.fields || {};
processFile(thisPath,dirSpec.isTiddlerFile,dirSpec.fields,dirSpec.isEditableFile,dirSpec.path);
}
}
@@ -2350,6 +2345,7 @@ $tw.loadTiddlersNode = function() {
});
// Load the core tiddlers
$tw.wiki.addTiddler($tw.loadPluginFolder($tw.boot.corePath));
$tw.wiki.addTiddler($tw.loadPluginFolder($tw.boot.coreServerPath));
// Load any extra plugins
$tw.utils.each($tw.boot.extraPlugins,function(name) {
if(name.charAt(0) === "+") { // Relative path to plugin
@@ -2423,6 +2419,7 @@ $tw.boot.initStartup = function(options) {
// System paths and filenames
$tw.boot.bootPath = options.bootPath || path.dirname(module.filename);
$tw.boot.corePath = path.resolve($tw.boot.bootPath,"../core");
$tw.boot.coreServerPath = path.resolve($tw.boot.bootPath,"../core-server");
// If there's no arguments then default to `--help`
if($tw.boot.argv.length === 0) {
$tw.boot.argv = ["--help"];
@@ -2547,10 +2544,10 @@ $tw.boot.execStartup = function(options){
if($tw.safeMode) {
$tw.wiki.processSafeMode();
}
// Register typed modules from the tiddlers we've just loaded
$tw.wiki.defineTiddlerModules();
// And any modules within plugins
// Register typed modules from the tiddlers we've just loaded and any modules within plugins
// Tiddlers should appear last so that they may overwrite shadows during module registration
$tw.wiki.defineShadowModules();
$tw.wiki.defineTiddlerModules();
// Make sure the crypto state tiddler is up to date
if($tw.crypto) {
$tw.crypto.updateCryptoStateTiddler();
@@ -2619,11 +2616,13 @@ $tw.boot.executeNextStartupTask = function(callback) {
$tw.boot.log(s.join(" "));
// Execute task
if(!$tw.utils.hop(task,"synchronous") || task.synchronous) {
task.startup();
if(task.name) {
$tw.boot.executedStartupModules[task.name] = true;
const thenable = task.startup();
if(thenable && typeof thenable.then === "function"){
thenable.then(asyncTaskCallback);
return true;
} else {
return asyncTaskCallback();
}
return $tw.boot.executeNextStartupTask(callback);
} else {
task.startup(asyncTaskCallback);
return true;
@@ -2768,6 +2767,8 @@ return $tw;
});
/* eslint-enable @stylistic/indent */
if(typeof(exports) !== "undefined") {
exports.TiddlyWiki = _boot;
} else {

View File

@@ -12,6 +12,8 @@ See Boot.js for further details of the boot process.
\*/
/* eslint-disable @stylistic/indent */
var _bootprefix = (function($tw) {
"use strict";
@@ -114,6 +116,8 @@ return $tw;
});
/* eslint-enable @stylistic/indent */
if(typeof(exports) === "undefined") {
// Set up $tw global for the browser
window.$tw = _bootprefix(window.$tw);

View File

@@ -99,16 +99,18 @@ Commander.prototype.executeNextCommand = function() {
}
}
if(command.info.synchronous) {
// Synchronous command
// Synchronous command (await thenables)
c = new command.Command(params,this);
err = c.execute();
if(err) {
if(err && typeof err.then === "function") {
err.then(e => { e ? this.callback(e) : this.executeNextCommand(); });
} else if(err) {
this.callback(err);
} else {
this.executeNextCommand();
}
} else {
// Asynchronous command
// Asynchronous command (await thenables)
c = new command.Command(params,this,function(err) {
if(err) {
self.callback(err);
@@ -117,7 +119,9 @@ Commander.prototype.executeNextCommand = function() {
}
});
err = c.execute();
if(err) {
if(err && typeof err.then === "function") {
err.then(e => { if(e) this.callback(e); });
} else if(err) {
this.callback(err);
}
}

View File

@@ -76,6 +76,7 @@ WikiFolderMaker.prototype.tiddlersToIgnore = [
"$:/boot/boot.js",
"$:/boot/bootprefix.js",
"$:/core",
"$:/core-server",
"$:/library/sjcl.js",
"$:/temp/info-plugin"
];

11
core-server/plugin.info Normal file
View File

@@ -0,0 +1,11 @@
{
"title": "$:/core-server",
"name": "Core Server Components",
"description": "TiddlyWiki5 core server components",
"author": "JeremyRuston",
"core-version": ">=5.0.0",
"platform": "server",
"plugin-priority": "0",
"list": "readme",
"stability": "STABILITY_2_STABLE"
}

7
core-server/readme.tid Normal file
View File

@@ -0,0 +1,7 @@
title: $:/core-server/readme
This plugin contains TiddlyWiki's core components that are only needed on the server, comprising:
* Commands
* HTTP server code
* Utility functions for server

View File

@@ -8,10 +8,14 @@ DELETE /recipes/default/tiddlers/:title
\*/
"use strict";
exports.method = "DELETE";
exports.methods = ["DELETE"];
exports.path = /^\/bags\/default\/tiddlers\/(.+)$/;
exports.info = {
priority: 100
};
exports.handler = function(request,response,state) {
var title = $tw.utils.decodeURIComponentSafe(state.params[0]);
state.wiki.deleteTiddler(title);

View File

@@ -8,10 +8,14 @@ GET /favicon.ico
\*/
"use strict";
exports.method = "GET";
exports.methods = ["GET"];
exports.path = /^\/favicon.ico$/;
exports.info = {
priority: 100
};
exports.handler = function(request,response,state) {
var buffer = state.wiki.getTiddlerText("$:/favicon.ico","");
state.sendResponse(200,{"Content-Type": "image/x-icon"},buffer,"base64");

View File

@@ -0,0 +1,73 @@
/*\
title: $:/core/modules/server/routes/get-file.js
type: application/javascript
module-type: route
GET /files/:filepath
\*/
"use strict";
exports.methods = ["GET"];
exports.path = /^\/files\/(.+)$/;
exports.info = {
priority: 100
};
exports.handler = function(request,response,state) {
var path = require("path"),
fs = require("fs"),
suppliedFilename = $tw.utils.decodeURIComponentSafe(state.params[0]),
baseFilename = path.resolve(state.boot.wikiPath,"files"),
filename = path.resolve(baseFilename,suppliedFilename),
extension = path.extname(filename);
// Check that the filename is inside the wiki files folder
if(path.relative(baseFilename,filename).indexOf("..") === 0) {
return state.sendResponse(404,{"Content-Type": "text/plain"},"File '" + suppliedFilename + "' not found");
}
fs.stat(filename, function(err, stats) {
if(err) {
return state.sendResponse(404,{"Content-Type": "text/plain"},"File '" + suppliedFilename + "' not found");
} else {
var type = ($tw.config.fileExtensionInfo[extension] ? $tw.config.fileExtensionInfo[extension].type : "application/octet-stream"),
responseHeaders = {
"Content-Type": type,
"Accept-Ranges": "bytes"
};
var rangeHeader = request.headers.range,
stream;
if(rangeHeader) {
// Handle range requests
var parts = rangeHeader.replace(/bytes=/, "").split("-"),
start = parseInt(parts[0], 10),
end = parts[1] ? parseInt(parts[1], 10) : stats.size - 1;
// Validate start and end
if(isNaN(start) || isNaN(end) || start < 0 || end < start || end >= stats.size) {
responseHeaders["Content-Range"] = "bytes */" + stats.size;
return response.writeHead(416, responseHeaders).end();
}
var chunksize = (end - start) + 1;
responseHeaders["Content-Range"] = "bytes " + start + "-" + end + "/" + stats.size;
responseHeaders["Content-Length"] = chunksize;
response.writeHead(206, responseHeaders);
stream = fs.createReadStream(filename, {start: start, end: end});
} else {
responseHeaders["Content-Length"] = stats.size;
response.writeHead(200, responseHeaders);
stream = fs.createReadStream(filename);
}
// Common stream error handling
stream.on("error", function(err) {
if(!response.headersSent) {
response.writeHead(500, {"Content-Type": "text/plain"});
response.end("Read error");
} else {
response.destroy();
}
});
stream.pipe(response);
}
});
};

View File

@@ -8,10 +8,14 @@ GET /
\*/
"use strict";
exports.method = "GET";
exports.methods = ["GET"];
exports.path = /^\/$/;
exports.info = {
priority: 100
};
exports.handler = function(request,response,state) {
var text = state.wiki.renderTiddler(state.server.get("root-render-type"),state.server.get("root-tiddler")),
responseHeaders = {

View File

@@ -8,10 +8,14 @@ GET /login-basic -- force a Basic Authentication challenge
\*/
"use strict";
exports.method = "GET";
exports.methods = ["GET"];
exports.path = /^\/login-basic$/;
exports.info = {
priority: 100
};
exports.handler = function(request,response,state) {
if(!state.authenticatedUsername) {
// Challenge if there's no username

View File

@@ -8,10 +8,14 @@ GET /status
\*/
"use strict";
exports.method = "GET";
exports.methods = ["GET"];
exports.path = /^\/status$/;
exports.info = {
priority: 100
};
exports.handler = function(request,response,state) {
var text = JSON.stringify({
username: state.authenticatedUsername || state.server.get("anon-username") || "",

View File

@@ -8,10 +8,14 @@ GET /:title
\*/
"use strict";
exports.method = "GET";
exports.methods = ["GET"];
exports.path = /^\/([^\/]+)$/;
exports.info = {
priority: 100
};
exports.handler = function(request,response,state) {
var title = $tw.utils.decodeURIComponentSafe(state.params[0]),
tiddler = state.wiki.getTiddler(title);

View File

@@ -8,10 +8,14 @@ GET /recipes/default/tiddlers/:title
\*/
"use strict";
exports.method = "GET";
exports.methods = ["GET"];
exports.path = /^\/recipes\/default\/tiddlers\/(.+)$/;
exports.info = {
priority: 100
};
exports.handler = function(request,response,state) {
var title = $tw.utils.decodeURIComponentSafe(state.params[0]),
tiddler = state.wiki.getTiddler(title),

View File

@@ -10,10 +10,14 @@ GET /recipes/default/tiddlers.json?filter=<filter>
var DEFAULT_FILTER = "[all[tiddlers]!is[system]sort[title]]";
exports.method = "GET";
exports.methods = ["GET"];
exports.path = /^\/recipes\/default\/tiddlers.json$/;
exports.info = {
priority: 100
};
exports.handler = function(request,response,state) {
var filter = state.queryParameters.filter || DEFAULT_FILTER;
if(state.wiki.getTiddlerText("$:/config/Server/AllowAllExternalFilters") !== "yes") {

View File

@@ -8,10 +8,14 @@ PUT /recipes/default/tiddlers/:title
\*/
"use strict";
exports.method = "PUT";
exports.methods = ["PUT"];
exports.path = /^\/recipes\/default\/tiddlers\/(.+)$/;
exports.info = {
priority: 100
};
exports.handler = function(request,response,state) {
var title = $tw.utils.decodeURIComponentSafe(state.params[0]),
fields = $tw.utils.parseJSONSafe(state.data);

View File

@@ -74,6 +74,11 @@ function Server(options) {
// console.log("Loading server route " + title);
self.addRoute(routeDefinition);
});
this.routes.sort((a, b) => {
const priorityA = a.info?.priority ?? 100,
priorityB = b.info?.priority ?? 100;
return priorityB - priorityA;
});
// Initialise the http vs https
this.listenOptions = null;
this.protocol = "http";
@@ -217,7 +222,7 @@ Server.prototype.findMatchingRoute = function(request,state) {
} else {
match = potentialRoute.path.exec(pathname);
}
if(match && request.method === potentialRoute.method) {
if(match && (potentialRoute.methods?.includes(request.method) || potentialRoute.method === request.method)) {
state.params = [];
for(var p=1; p<match.length; p++) {
state.params.push(match[p]);

View File

@@ -0,0 +1,30 @@
/*\
title: $:/core-modules/modules/utils/base64.js
type: application/javascript
module-type: utils-node
Base64 UTF-8 utlity functions.
\*/
"use strict";
const{ TextEncoder, TextDecoder } = require("node:util");
exports.btoa = binstr => Buffer.from(binstr, "binary").toString("base64");
exports.atob = b64 => Buffer.from(b64, "base64").toString("binary");
function base64ToBytes(base64) {
const binString = exports.atob(base64);
return Uint8Array.from(binString, m => m.codePointAt(0));
};
function bytesToBase64(bytes) {
const binString = Array.from(bytes, byte => String.fromCodePoint(byte)).join("");
return exports.btoa(binString);
};
exports.base64EncodeUtf8 = str => bytesToBase64(new TextEncoder().encode(str));
exports.base64DecodeUtf8 = str => new TextDecoder().decode(base64ToBytes(str));

View File

@@ -5,3 +5,4 @@ TiddlyWiki incorporates code from these fine OpenSource projects:
* [[The Stanford Javascript Crypto Library|http://bitwiseshiftleft.github.io/sjcl/]]
* [[The Jasmine JavaScript Test Framework|https://jasmine.github.io/]]
* [[modern-normalize by Sindre Sorhus|https://github.com/sindresorhus/modern-normalize]]
* [[diff-match-patch-es by antfu|https://github.com/antfu/diff-match-patch-es]]

View File

@@ -147,7 +147,7 @@ Settings/AutoSave/Disabled/Description: Do not save changes automatically
Settings/AutoSave/Enabled/Description: Save changes automatically
Settings/AutoSave/Hint: Attempt to automatically save changes during editing when using a supporting saver
Settings/CamelCase/Caption: Camel Case Wiki Links
Settings/CamelCase/Hint: You can globally disable automatic linking of ~CamelCase phrases. Requires reload to take effect
Settings/CamelCase/Hint: Requires reload to take effect
Settings/CamelCase/Description: Enable automatic ~CamelCase linking
Settings/Caption: Settings
Settings/EditorToolbar/Caption: Editor Toolbar

View File

@@ -34,7 +34,7 @@ function FramedEngine(options) {
var paletteTitle = this.widget.wiki.getTiddlerText("$:/palette");
var colorScheme = (this.widget.wiki.getTiddler(paletteTitle) || {fields: {}}).fields["color-scheme"] || "light";
this.iframeDoc.open();
this.iframeDoc.write("<meta name='color-scheme' content='" + colorScheme + "'>");
this.iframeDoc.write("<!DOCTYPE html><html><head><meta name='color-scheme' content='" + colorScheme + "'></head><body></body></html>");
this.iframeDoc.close();
// Style the iframe
this.iframeNode.className = this.dummyTextArea.className;
@@ -156,8 +156,8 @@ Fix the height of textarea to fit content
FramedEngine.prototype.fixHeight = function() {
// Make sure styles are updated
this.copyStyles();
// Adjust height
if(this.widget.editTag === "textarea") {
// If .editRows is initialised, it takes precedence
if(this.widget.editTag === "textarea" && !this.widget.editRows) {
if(this.widget.editAutoHeight) {
if(this.domNode && !this.domNode.isTiddlyWikiFakeDom) {
var newHeight = $tw.utils.resizeTextAreaToFit(this.domNode,this.widget.editMinHeight);

View File

@@ -100,7 +100,8 @@ SimpleEngine.prototype.getText = function() {
Fix the height of textarea to fit content
*/
SimpleEngine.prototype.fixHeight = function() {
if(this.widget.editTag === "textarea") {
// If .editRows is initialised, it takes precedence
if((this.widget.editTag === "textarea") && !this.widget.editRows) {
if(this.widget.editAutoHeight) {
if(this.domNode && !this.domNode.isTiddlyWikiFakeDom) {
$tw.utils.resizeTextAreaToFit(this.domNode,this.widget.editMinHeight);

View File

@@ -68,7 +68,7 @@ function editTextWidgetFactory(toolbarEngine,nonToolbarEngine) {
// Fix height
this.engine.fixHeight();
// Focus if required
if(this.editFocus === "true" || this.editFocus === "yes") {
if($tw.browser && (this.editFocus === "true" || this.editFocus === "yes") && !$tw.utils.hasClass(this.parentDomNode.ownerDocument.activeElement,"tc-keep-focus")) {
this.engine.focus();
}
// Add widget message listeners

View File

@@ -0,0 +1,41 @@
/*\
title: $:/core/modules/filterrunprefixes/let.js
type: application/javascript
module-type: filterrunprefix
Assign a value to a variable
\*/
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
/*
Export our filter prefix function
*/
exports.let = function(operationSubFunction,options) {
// Return the filter run prefix function
return function(results,source,widget) {
// Save the result list
var resultList = results.toArray();
// Clear the results
results.clear();
// Evaluate the subfunction to get the variable name
var subFunctionResults = operationSubFunction(source,widget);
if(subFunctionResults.length === 0) {
return;
}
var name = subFunctionResults[0];
if(typeof name !== "string" || name.length === 0) {
return;
}
// Assign the result of the subfunction to the variable
var variables = {};
variables[name] = resultList;
// Return the variables
return {
variables: variables
};
};
};

View File

@@ -35,7 +35,7 @@ function parseFilterOperation(operators,filterString,p) {
operator.prefix = filterString.charAt(p++);
}
// Get the operator name
nextBracketPos = filterString.substring(p).search(/[\[\{<\/]/);
nextBracketPos = filterString.substring(p).search(/[\[\{<\/\(]/);
if(nextBracketPos === -1) {
throw "Missing [ in filter expression";
}
@@ -79,13 +79,17 @@ function parseFilterOperation(operators,filterString,p) {
operand.variable = true;
nextBracketPos = filterString.indexOf(">",p);
break;
case "(": // Round brackets
operand.multiValuedVariable = true;
nextBracketPos = filterString.indexOf(")",p);
break;
case "/": // regexp brackets
var rex = /^((?:[^\\\/]|\\.)*)\/(?:\(([mygi]+)\))?/g,
rexMatch = rex.exec(filterString.substring(p));
if(rexMatch) {
operator.regexp = new RegExp(rexMatch[1], rexMatch[2]);
// DEPRECATION WARNING
console.log("WARNING: Filter",operator.operator,"has a deprecated regexp operand",operator.regexp);
// DEPRECATION WARNING
console.log("WARNING: Filter",operator.operator,"has a deprecated regexp operand",operator.regexp);
nextBracketPos = p + rex.lastIndex - 1;
}
else {
@@ -112,7 +116,7 @@ function parseFilterOperation(operators,filterString,p) {
// Check for multiple operands
while(filterString.charAt(p) === ",") {
p++;
if(/^[\[\{<\/]/.test(filterString.substring(p))) {
if(/^[\[\{<\/\(]/.test(filterString.substring(p))) {
nextBracketPos = p;
p++;
parseOperand(filterString.charAt(nextBracketPos));
@@ -141,7 +145,15 @@ exports.parseFilter = function(filterString) {
p = 0, // Current position in the filter string
match;
var whitespaceRegExp = /(\s+)/mg,
operandRegExp = /((?:\+|\-|~|=|\:(\w+)(?:\:([\w\:, ]*))?)?)(?:(\[)|(?:"([^"]*)")|(?:'([^']*)')|([^\s\[\]]+))/mg;
// Groups:
// 1 - entire filter run prefix
// 2 - filter run prefix itself
// 3 - filter run prefix suffixes
// 4 - opening square bracket 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
operandRegExp = /((?:\+|\-|~|(?:=>?)|\:(\w+)(?:\:([\w\:, ]*))?)?)(?:(\[)|(?:"([^"]*)")|(?:'([^']*)')|([^\s\[\]]+))/mg;
while(p < filterString.length) {
// Skip any whitespace
whitespaceRegExp.lastIndex = p;
@@ -152,38 +164,45 @@ exports.parseFilter = function(filterString) {
// Match the start of the operation
if(p < filterString.length) {
operandRegExp.lastIndex = p;
match = operandRegExp.exec(filterString);
if(!match || match.index !== p) {
throw $tw.language.getString("Error/FilterSyntax");
}
var operation = {
prefix: "",
operators: []
};
if(match[1]) {
operation.prefix = match[1];
p = p + operation.prefix.length;
if(match[2]) {
operation.namedPrefix = match[2];
}
if(match[3]) {
operation.suffixes = [];
$tw.utils.each(match[3].split(":"),function(subsuffix) {
operation.suffixes.push([]);
$tw.utils.each(subsuffix.split(","),function(entry) {
entry = $tw.utils.trim(entry);
if(entry) {
operation.suffixes[operation.suffixes.length -1].push(entry);
}
match = operandRegExp.exec(filterString);
if(match && match.index === p) {
// If there is a filter run prefix
if(match[1]) {
operation.prefix = match[1];
p = p + operation.prefix.length;
// Name for named prefixes
if(match[2]) {
operation.namedPrefix = match[2];
}
// Suffixes for filter run prefix
if(match[3]) {
operation.suffixes = [];
$tw.utils.each(match[3].split(":"),function(subsuffix) {
operation.suffixes.push([]);
$tw.utils.each(subsuffix.split(","),function(entry) {
entry = $tw.utils.trim(entry);
if(entry) {
operation.suffixes[operation.suffixes.length -1].push(entry);
}
});
});
});
}
}
// Opening square bracket
if(match[4]) {
p = parseFilterOperation(operation.operators,filterString,p);
} else {
p = match.index + match[0].length;
}
}
if(match[4]) { // Opening square bracket
p = parseFilterOperation(operation.operators,filterString,p);
} else {
p = match.index + match[0].length;
// No filter run prefix
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
operation.operators.push(
{operator: "title", operands: [{text: match[5] || match[6] || match[7]}]}
@@ -213,7 +232,12 @@ exports.getFilterRunPrefixes = function() {
exports.filterTiddlers = function(filterString,widget,source) {
var fn = this.compileFilter(filterString);
return fn.call(this,source,widget);
try {
const fnResult = fn.call(this,source,widget);
return fnResult;
} catch(e) {
return [`${$tw.language.getString("Error/Filter")}: ${e}`];
}
};
/*
@@ -251,6 +275,8 @@ exports.compileFilter = function(filterString) {
results = [];
$tw.utils.each(operation.operators,function(operator) {
var operands = [],
multiValueOperands = [],
isMultiValueOperand = [],
operatorFunction;
if(!operator.operator) {
// Use the "title" operator if no operator is specified
@@ -266,28 +292,46 @@ exports.compileFilter = function(filterString) {
if(operand.indirect) {
var currTiddlerTitle = widget && widget.getVariable("currentTiddler");
operand.value = self.getTextReference(operand.text,"",currTiddlerTitle);
operand.multiValue = [operand.value];
} else if(operand.variable) {
var varTree = $tw.utils.parseFilterVariable(operand.text);
operand.value = widgetClass.evaluateVariable(widget,varTree.name,{params: varTree.params, source: source})[0] || "";
operand.multiValue = [operand.value];
} else if(operand.multiValuedVariable) {
var varTree = $tw.utils.parseFilterVariable(operand.text);
var resultList = widgetClass.evaluateVariable(widget,varTree.name,{params: varTree.params, source: source});
if((resultList.length > 0 && resultList[0] !== undefined) || resultList.length === 0) {
operand.multiValue = widgetClass.evaluateVariable(widget,varTree.name,{params: varTree.params, source: source}) || [];
operand.value = operand.multiValue[0] || "";
} else {
operand.value = "";
operand.multiValue = [];
}
operand.isMultiValueOperand = true;
} else {
operand.value = operand.text;
operand.multiValue = [operand.value];
}
operands.push(operand.value);
multiValueOperands.push(operand.multiValue);
isMultiValueOperand.push(!!operand.isMultiValueOperand);
});
// Invoke the appropriate filteroperator module
results = operatorFunction(accumulator,{
operator: operator.operator,
operand: operands.length > 0 ? operands[0] : undefined,
operands: operands,
prefix: operator.prefix,
suffix: operator.suffix,
suffixes: operator.suffixes,
regexp: operator.regexp
},{
wiki: self,
widget: widget
});
operator: operator.operator,
operand: operands.length > 0 ? operands[0] : undefined,
operands: operands,
multiValueOperands: multiValueOperands,
isMultiValueOperand: isMultiValueOperand,
prefix: operator.prefix,
suffix: operator.suffix,
suffixes: operator.suffixes,
regexp: operator.regexp
},{
wiki: self,
widget: widget
});
if($tw.utils.isArray(results)) {
accumulator = self.makeTiddlerIterator(results);
} else {
@@ -319,6 +363,8 @@ exports.compileFilter = function(filterString) {
return filterRunPrefixes["and"](operationSubFunction, options);
case "~": // This operation is unioned into the result only if the main result so far is empty
return filterRunPrefixes["else"](operationSubFunction, options);
case "=>": // This operation is applied to the main results so far, and the results are assigned to a variable
return filterRunPrefixes["let"](operationSubFunction, options);
default:
if(operation.namedPrefix && filterRunPrefixes[operation.namedPrefix]) {
return filterRunPrefixes[operation.namedPrefix](operationSubFunction, options);
@@ -345,7 +391,13 @@ exports.compileFilter = function(filterString) {
self.filterRecursionCount = (self.filterRecursionCount || 0) + 1;
if(self.filterRecursionCount < MAX_FILTER_DEPTH) {
$tw.utils.each(operationFunctions,function(operationFunction) {
operationFunction(results,source,widget);
var operationResult = operationFunction(results,source,widget);
if(operationResult) {
if(operationResult.variables) {
// If the filter run prefix has returned variables, create a new fake widget with those variables
widget = widget.makeFakeWidgetWithVariables(operationResult.variables);
}
}
});
} else {
results.push("/**-- Excessive filter recursion --**/");

View File

@@ -16,12 +16,8 @@ exports.json = function(source,operand,options) {
spaces = /^\d+$/.test(operand) ? parseInt(operand,10) : operand;
}
source(function(tiddler,title) {
var data = $tw.utils.parseJSONSafe(title);
try {
data = JSON.parse(title);
} catch(e) {
data = undefined;
}
var data = $tw.utils.parseJSONSafe(title,function(){return undefined;});
if(data !== undefined) {
results.push(JSON.stringify(data,null,spaces));
}

View File

@@ -16,8 +16,8 @@ exports.function = function(source,operator,options) {
var functionName = operator.operands[0],
params = [],
results;
$tw.utils.each(operator.operands.slice(1),function(param) {
params.push({value: param});
$tw.utils.each(operator.multiValueOperands.slice(1),function(paramList) {
params.push({value: paramList[0] || "",multiValue: paramList});
});
// console.log(`Calling ${functionName} with params ${JSON.stringify(params)}`);
var variableInfo = options.widget && options.widget.getVariableInfo && options.widget.getVariableInfo(functionName,{params: params, source: source});

View File

@@ -113,6 +113,22 @@ exports["jsonset"] = function(source,operator,options) {
return results;
};
exports["jsondelete"] = function(source,operator,options) {
var indexes = operator.operands,
results = [];
source(function(tiddler,title) {
var data = $tw.utils.parseJSONSafe(title,title);
// If parsing failed (data equals original title and is a string), return unchanged
if(data === title && typeof data === "string") {
results.push(title);
} else if(data) {
data = deleteDataItem(data,indexes);
results.push(JSON.stringify(data));
}
});
return results;
};
/*
Given a JSON data structure and an array of index strings, return an array of the string representation of the values at the end of the index chain, or "undefined" if any of the index strings are invalid
*/
@@ -144,7 +160,7 @@ function convertDataItemValueToStrings(item) {
return ["null"]
} else if(typeof item === "object") {
var results = [],i,t;
if($tw.utils.isArray(item)) {
if(Array.isArray(item)) {
// Return all the items in arrays recursively
for(i=0; i<item.length; i++) {
t = convertDataItemValueToStrings(item[i])
@@ -178,7 +194,7 @@ function convertDataItemKeysToStrings(item) {
return [];
}
var results = [];
if($tw.utils.isArray(item)) {
if(Array.isArray(item)) {
for(var i=0; i<item.length; i++) {
results.push(i.toString());
}
@@ -201,7 +217,7 @@ function getDataItemType(data,indexes) {
return item;
} else if(item === null) {
return "null";
} else if($tw.utils.isArray(item)) {
} else if(Array.isArray(item)) {
return "array";
} else if(typeof item === "object") {
return "object";
@@ -213,7 +229,7 @@ function getDataItemType(data,indexes) {
function getItemAtIndex(item,index) {
if($tw.utils.hop(item,index)) {
return item[index];
} else if($tw.utils.isArray(item)) {
} else if(Array.isArray(item)) {
index = $tw.utils.parseInt(index);
if(index < 0) { index = index + item.length };
return item[index]; // Will be undefined if index was out-of-bounds
@@ -223,15 +239,16 @@ function getItemAtIndex(item,index) {
}
/*
Given a JSON data structure and an array of index strings, return the value at the end of the index chain, or "undefined" if any of the index strings are invalid
Traverse the index chain and return the item at the specified depth.
Returns the item at the end of the traversal, or undefined if traversal fails.
*/
function getDataItem(data,indexes) {
function traverseIndexChain(data,indexes,stopBeforeLast) {
if(indexes.length === 0 || (indexes.length === 1 && indexes[0] === "")) {
return data;
}
// Get the item
var item = data;
for(var i=0; i<indexes.length; i++) {
var stopIndex = stopBeforeLast ? indexes.length - 1 : indexes.length;
for(var i = 0; i < stopIndex; i++) {
if(item !== undefined) {
if(item !== null && ["number","string","boolean"].indexOf(typeof item) === -1) {
item = getItemAtIndex(item,indexes[i]);
@@ -243,6 +260,13 @@ function getDataItem(data,indexes) {
return item;
}
/*
Given a JSON data structure and an array of index strings, return the value at the end of the index chain, or "undefined" if any of the index strings are invalid
*/
function getDataItem(data,indexes) {
return traverseIndexChain(data,indexes,false);
}
/*
Given a JSON data structure, an array of index strings and a value, return the data structure with the value added at the end of the index chain. If any of the index strings are invalid then the JSON data structure is returned unmodified. If the root item is targetted then a different data object will be returned
*/
@@ -255,18 +279,15 @@ function setDataItem(data,indexes,value) {
if(indexes.length === 0 || (indexes.length === 1 && indexes[0] === "")) {
return value;
}
// Traverse the JSON data structure using the index chain
var current = data;
for(var i = 0; i < indexes.length - 1; i++) {
current = getItemAtIndex(current,indexes[i]);
if(current === undefined) {
// Return the original JSON data structure if any of the index strings are invalid
return data;
}
// Traverse the JSON data structure using the index chain up to the parent
var current = traverseIndexChain(data,indexes,true);
if(current === undefined) {
// Return the original JSON data structure if any of the index strings are invalid
return data;
}
// Add the value to the end of the index chain
var lastIndex = indexes[indexes.length - 1];
if($tw.utils.isArray(current)) {
if(Array.isArray(current)) {
lastIndex = $tw.utils.parseInt(lastIndex);
if(lastIndex < 0) { lastIndex = lastIndex + current.length };
}
@@ -276,3 +297,32 @@ function setDataItem(data,indexes,value) {
}
return data;
}
/*
Given a JSON data structure and an array of index strings, return the data structure with the item at the end of the index chain deleted. If any of the index strings are invalid then the JSON data structure is returned unmodified. If the root item is targetted then the JSON data structure is returned unmodified.
*/
function deleteDataItem(data,indexes) {
// Check for the root item - don't delete the root
if(indexes.length === 0 || (indexes.length === 1 && indexes[0] === "")) {
return data;
}
// Traverse the JSON data structure using the index chain up to the parent
var current = traverseIndexChain(data,indexes,true);
if(current === undefined || current === null) {
// Return the original JSON data structure if any of the index strings are invalid
return data;
}
// Delete the item at the end of the index chain
var lastIndex = indexes[indexes.length - 1];
if(Array.isArray(current) && current !== null) {
lastIndex = $tw.utils.parseInt(lastIndex);
if(lastIndex < 0) { lastIndex = lastIndex + current.length };
// Check if index is valid before splicing
if(lastIndex >= 0 && lastIndex < current.length) {
current.splice(lastIndex,1);
}
} else if(typeof current === "object" && current !== null) {
delete current[lastIndex];
}
return data;
}

View File

@@ -217,6 +217,10 @@ function makeNumericReducingOperator(fnCalc,initialValue,fnFinal) {
source(function(tiddler,title) {
result.push($tw.utils.parseNumber(title));
});
// We return an empty array if there are no input titles
if(result.length === 0) {
return [];
}
var value = result.reduce(function(accumulator,currentValue) {
return fnCalc(accumulator,currentValue);
},initialValue);

View File

@@ -14,31 +14,31 @@ Export our filter function
*/
exports.sort = function(source,operator,options) {
var results = prepare_results(source);
options.wiki.sortTiddlers(results,operator.operand || "title",operator.prefix === "!",false,false);
options.wiki.sortTiddlers(results,operator.operands[0] || "title",operator.prefix === "!",false,false,undefined,operator.operands[1]);
return results;
};
exports.nsort = function(source,operator,options) {
var results = prepare_results(source);
options.wiki.sortTiddlers(results,operator.operand || "title",operator.prefix === "!",false,true);
options.wiki.sortTiddlers(results,operator.operands[0] || "title",operator.prefix === "!",false,true,undefined,operator.operands[1]);
return results;
};
exports.sortan = function(source, operator, options) {
var results = prepare_results(source);
options.wiki.sortTiddlers(results, operator.operand || "title", operator.prefix === "!",false,false,true);
options.wiki.sortTiddlers(results, operator.operands[0] || "title", operator.prefix === "!",false,false,true,operator.operands[1]);
return results;
};
exports.sortcs = function(source,operator,options) {
var results = prepare_results(source);
options.wiki.sortTiddlers(results,operator.operand || "title",operator.prefix === "!",true,false);
options.wiki.sortTiddlers(results,operator.operands[0] || "title",operator.prefix === "!",true,false,undefined,operator.operands[1]);
return results;
};
exports.nsortcs = function(source,operator,options) {
var results = prepare_results(source);
options.wiki.sortTiddlers(results,operator.operand || "title",operator.prefix === "!",true,true);
options.wiki.sortTiddlers(results,operator.operands[0] || "title",operator.prefix === "!",true,true,undefined,operator.operands[1]);
return results;
};

View File

@@ -71,107 +71,103 @@ exports.join = makeStringReducingOperator(
},null
);
var dmp = require("$:/core/modules/utils/diff-match-patch/diff_match_patch.js");
const dmp = require("$:/core/modules/utils/diff-match-patch/diff_match_patch.js");
exports.levenshtein = makeStringBinaryOperator(
function(a,b) {
var dmpObject = new dmp.diff_match_patch(),
diffs = dmpObject.diff_main(a,b);
return [dmpObject.diff_levenshtein(diffs) + ""];
const diffs = dmp.diffMain(a,b);
return [dmp.diffLevenshtein(diffs).toString()];
}
);
// these two functions are adapted from https://github.com/google/diff-match-patch/wiki/Line-or-Word-Diffs
function diffLineWordMode(text1,text2,mode) {
var dmpObject = new dmp.diff_match_patch();
var a = diffPartsToChars(text1,text2,mode);
var lineText1 = a.chars1;
var lineText2 = a.chars2;
var lineArray = a.lineArray;
var diffs = dmpObject.diff_main(lineText1,lineText2,false);
dmpObject.diff_charsToLines_(diffs,lineArray);
var diffs = dmp.diffMain(lineText1,lineText2,false);
dmp.diffCharsToLines(diffs,lineArray);
return diffs;
}
function diffPartsToChars(text1,text2,mode) {
var lineArray = [];
var lineHash = {};
lineArray[0] = '';
lineArray[0] = "";
function diff_linesToPartsMunge_(text,mode) {
var chars = '';
var lineStart = 0;
var lineEnd = -1;
var lineArrayLength = lineArray.length,
regexpResult;
var searchRegexp = /\W+/g;
while(lineEnd < text.length - 1) {
if(mode === "words") {
regexpResult = searchRegexp.exec(text);
lineEnd = searchRegexp.lastIndex;
if(regexpResult === null) {
lineEnd = text.length;
}
lineEnd = --lineEnd;
} else {
lineEnd = text.indexOf('\n', lineStart);
if(lineEnd == -1) {
lineEnd = text.length - 1;
}
}
var line = text.substring(lineStart, lineEnd + 1);
function diff_linesToPartsMunge_(text,mode) {
var chars = "";
var lineStart = 0;
var lineEnd = -1;
var lineArrayLength = lineArray.length,
regexpResult;
var searchRegexp = /\W+/g;
while(lineEnd < text.length - 1) {
if(mode === "words") {
regexpResult = searchRegexp.exec(text);
lineEnd = searchRegexp.lastIndex;
if(regexpResult === null) {
lineEnd = text.length;
}
lineEnd = --lineEnd;
} else {
lineEnd = text.indexOf("\n", lineStart);
if(lineEnd == -1) {
lineEnd = text.length - 1;
}
}
var line = text.substring(lineStart, lineEnd + 1);
if(lineHash.hasOwnProperty ? lineHash.hasOwnProperty(line) : (lineHash[line] !== undefined)) {
if(lineHash.hasOwnProperty ? lineHash.hasOwnProperty(line) : (lineHash[line] !== undefined)) {
chars += String.fromCharCode(lineHash[line]);
} else {
if(lineArrayLength == maxLines) {
line = text.substring(lineStart);
lineEnd = text.length;
}
chars += String.fromCharCode(lineArrayLength);
lineHash[line] = lineArrayLength;
lineArray[lineArrayLength++] = line;
}
lineStart = lineEnd + 1;
}
return chars;
}
var maxLines = 40000;
var chars1 = diff_linesToPartsMunge_(text1,mode);
maxLines = 65535;
var chars2 = diff_linesToPartsMunge_(text2,mode);
return {chars1: chars1, chars2: chars2, lineArray: lineArray};
} else {
if(lineArrayLength == maxLines) {
line = text.substring(lineStart);
lineEnd = text.length;
}
chars += String.fromCharCode(lineArrayLength);
lineHash[line] = lineArrayLength;
lineArray[lineArrayLength++] = line;
}
lineStart = lineEnd + 1;
}
return chars;
}
var maxLines = 40000;
var chars1 = diff_linesToPartsMunge_(text1,mode);
maxLines = 65535;
var chars2 = diff_linesToPartsMunge_(text2,mode);
return {chars1: chars1, chars2: chars2, lineArray: lineArray};
};
exports.makepatches = function(source,operator,options) {
var dmpObject = new dmp.diff_match_patch(),
suffix = operator.suffix || "",
var suffix = operator.suffix || "",
result = [];
source(function(tiddler,title) {
var diffs, patches;
if(suffix === "lines" || suffix === "words") {
diffs = diffLineWordMode(title,operator.operand,suffix);
patches = dmpObject.patch_make(title,diffs);
} else {
patches = dmpObject.patch_make(title,operator.operand);
}
Array.prototype.push.apply(result,[dmpObject.patch_toText(patches)]);
});
source(function(tiddler,title) {
let diffs, patches;
if(suffix === "lines" || suffix === "words") {
diffs = diffLineWordMode(title,operator.operand,suffix);
patches = dmp.patchMake(title,diffs);
} else {
patches = dmp.patchMake(title,operator.operand);
}
Array.prototype.push.apply(result,[dmp.patchToText(patches)]);
});
return result;
};
exports.applypatches = makeStringBinaryOperator(
function(a,b) {
var dmpObject = new dmp.diff_match_patch(),
patches;
let patches;
try {
patches = dmpObject.patch_fromText(b);
patches = dmp.patchFromText(b);
} catch(e) {
}
if(patches) {
return [dmpObject.patch_apply(patches,a)[0]];
return [dmp.patchApply(patches,a)[0]];
} else {
return [a];
}

View File

@@ -16,12 +16,13 @@ exports.title = function(source,operator,options) {
var results = [];
if(operator.prefix === "!") {
source(function(tiddler,title) {
if(tiddler && tiddler.fields.title !== operator.operand) {
var titleList = operator.multiValueOperands[0] || [];
if(tiddler && titleList.indexOf(tiddler.fields.title) === -1) {
results.push(title);
}
});
} else {
results.push(operator.operand);
Array.prototype.push.apply(results,operator.multiValueOperands[0]);
}
return results;
};

View File

@@ -20,8 +20,8 @@ exports["[unknown]"] = function(source,operator,options) {
// Check for a user defined filter operator
if(operator.operator.indexOf(".") !== -1) {
var params = [];
$tw.utils.each(operator.operands,function(param) {
params.push({value: param});
$tw.utils.each(operator.multiValueOperands,function(paramList) {
params.push({value: paramList[0] || "",multiValue: paramList});
});
var variableInfo = options.widget && options.widget.getVariableInfo && options.widget.getVariableInfo(operator.operator,{params: params, source: source});
if(variableInfo && variableInfo.srcVariable) {

View File

@@ -0,0 +1,86 @@
/*\
title: $:/core/modules/info/windowdimensions.js
type: application/javascript
module-type: info
\*/
exports.getInfoTiddlerFields = function(updateInfoTiddlersCallback) {
if(!$tw.browser) {
return [];
}
class WindowDimensionsTracker {
constructor(updateCallback) {
this.updateCallback = updateCallback;
this.resizeHandlers = new Map();
this.dimensionsInfo = [
["outer/width", win => win.outerWidth],
["outer/height", win => win.outerHeight],
["inner/width", win => win.innerWidth],
["inner/height", win => win.innerHeight],
["client/width", win => win.document.documentElement.clientWidth],
["client/height", win => win.document.documentElement.clientHeight]
];
}
buildTiddlers(win,windowId) {
const prefix = `$:/info/browser/window/${windowId}/`;
return this.dimensionsInfo.map(([suffix, getter]) => ({
title: prefix + suffix,
text: String(getter(win))
}));
}
clearTiddlers(windowId) {
const prefix = `$:/info/browser/window/${windowId}/`,
deletions = this.dimensionsInfo.map(([suffix]) => prefix + suffix);
this.updateCallback([], deletions);
}
getUpdateHandler(win,windowId) {
let scheduled = false;
return () => {
if(!scheduled) {
scheduled = true;
requestAnimationFrame(() => {
this.updateCallback(this.buildTiddlers(win,windowId), []);
scheduled = false;
});
}
};
}
trackWindow(win,windowId) {
const handler = this.getUpdateHandler(win, windowId);
handler(); // initial update
win.addEventListener("resize",handler,{passive:true});
this.resizeHandlers.set(windowId,{win, handler});
}
untrackWindow(windowId) {
const entry = this.resizeHandlers.get(windowId);
if(entry) {
entry.win.removeEventListener("resize", entry.handler);
this.resizeHandlers.delete(windowId);
}
this.clearTiddlers(windowId);
}
}
const tracker = new WindowDimensionsTracker(updateInfoTiddlersCallback);
// Track main window
tracker.trackWindow(window,"system/main");
// Hook into event bus for user windows
if($tw.eventBus) {
$tw.eventBus.on("window:opened", ({window: win, windowID}) => {
tracker.trackWindow(win, "user/" + windowID);
});
$tw.eventBus.on("window:closed", ({windowID}) => {
tracker.untrackWindow("user/" + windowID);
});
}
return [];
};

View File

@@ -7,23 +7,34 @@ The audio parser parses an audio tiddler into an embeddable HTML element
\*/
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
var AudioParser = function(type,text,options) {
var element = {
type: "element",
tag: "audio",
tag: "$audio", // Using $audio to enable widget interception
attributes: {
controls: {type: "string", value: "controls"},
style: {type: "string", value: "width: 100%; object-fit: contain"}
}
},
src;
};
// Pass through source information
if(options._canonical_uri) {
element.attributes.src = {type: "string", value: options._canonical_uri};
element.attributes.type = {type: "string", value: type};
} else if(text) {
element.attributes.src = {type: "string", value: "data:" + type + ";base64," + text};
element.attributes.type = {type: "string", value: type};
}
// Pass through tiddler title if available
if(options.title) {
element.attributes.tiddler = {type: "string", value: options.title};
}
this.tree = [element];
this.source = text;
this.type = type;
@@ -33,3 +44,4 @@ exports["audio/ogg"] = AudioParser;
exports["audio/mpeg"] = AudioParser;
exports["audio/mp3"] = AudioParser;
exports["audio/mp4"] = AudioParser;

View File

@@ -11,10 +11,13 @@ The CSV text parser processes CSV files into a table wrapped in a scrollable wid
var CsvParser = function(type,text,options) {
// Special handler for tab-delimited files
if (type === 'text/tab-delimited-values' && !options.separator) {
if(
!options.separator &&
(type === "text/tab-delimited-values" || type === "text/tab-separated-values")
) {
options.separator = "\t";
}
// Table framework
this.tree = [{
"type": "scrollable", "children": [{
@@ -32,7 +35,7 @@ var CsvParser = function(type,text,options) {
$tw.utils.each(lines, function(columns) {
maxColumns = Math.max(columns.length, maxColumns);
});
for(var line=0; line<lines.length; line++) {
var columns = lines[line];
var row = {
@@ -55,3 +58,4 @@ var CsvParser = function(type,text,options) {
exports["text/csv"] = CsvParser;
exports["text/tab-delimited-values"] = CsvParser;
exports["text/tab-separated-values"] = CsvParser;

View File

@@ -11,17 +11,16 @@ The image parser parses an image into an embeddable HTML element
var ImageParser = function(type,text,options) {
var element = {
type: "element",
tag: "img",
attributes: {}
};
type: "image",
attributes: {}
};
if(options._canonical_uri) {
element.attributes.src = {type: "string", value: options._canonical_uri};
element.attributes.source = {type: "string", value: options._canonical_uri};
} else if(text) {
if(type === "image/svg+xml" || type === ".svg") {
element.attributes.src = {type: "string", value: "data:image/svg+xml," + encodeURIComponent(text)};
element.attributes.source = {type: "string", value: "data:image/svg+xml," + encodeURIComponent(text)};
} else {
element.attributes.src = {type: "string", value: "data:" + type + ";base64," + text};
element.attributes.source = {type: "string", value: "data:" + type + ";base64," + text};
}
}
this.tree = [element];

View File

@@ -82,6 +82,7 @@ exports.parseTokenString = function(source,pos,token) {
/*
Look for a token matching a regex. Returns null if not found, otherwise returns {type: "regexp", match:, start:, end:,}
Use the "Y" (sticky) flag to avoid searching the entire rest of the string
*/
exports.parseTokenRegExp = function(source,pos,reToken) {
var node = {
@@ -172,7 +173,7 @@ exports.parseMacroParameter = function(source,pos) {
start: pos
};
// Define our regexp
var reMacroParameter = /(?:([A-Za-z0-9\-_]+)\s*:)?(?:\s*(?:"""([\s\S]*?)"""|"([^"]*)"|'([^']*)'|\[\[([^\]]*)\]\]|((?:(?:>(?!>))|[^\s>"'])+)))/g;
const reMacroParameter = /(?:([A-Za-z0-9\-_]+)\s*:)?(?:\s*(?:"""([\s\S]*?)"""|"([^"]*)"|'([^']*)'|\[\[([^\]]*)\]\]|((?:(?:>(?!>))|[^\s>"'])+)))/y;
// Skip whitespace
pos = $tw.utils.skipWhiteSpace(source,pos);
// Look for the parameter
@@ -240,7 +241,7 @@ exports.parseMacroInvocation = function(source,pos) {
params: []
};
// Define our regexps
var reMacroName = /([^\s>"'=]+)/g;
const reMacroName = /([^\s>"'=]+)/y;
// Skip whitespace
pos = $tw.utils.skipWhiteSpace(source,pos);
// Look for a double less than sign
@@ -277,7 +278,7 @@ exports.parseFilterVariable = function(source) {
params: [],
},
pos = 0,
reName = /([^\s"']+)/g;
reName = /([^\s"']+)/y;
// If there is no whitespace or it is an empty string then there are no macro parameters
if(/^\S*$/.test(source)) {
node.name = source;
@@ -302,11 +303,11 @@ exports.parseAttribute = function(source,pos) {
start: pos
};
// Define our regexps
var reAttributeName = /([^\/\s>"'`=]+)/g,
reUnquotedAttribute = /([^\/\s<>"'`=]+)/g,
reFilteredValue = /\{\{\{([\S\s]+?)\}\}\}/g,
reIndirectValue = /\{\{([^\}]+)\}\}/g,
reSubstitutedValue = /(?:```([\s\S]*?)```|`([^`]|[\S\s]*?)`)/g;
const reAttributeName = /([^\/\s>"'`=]+)/y,
reUnquotedAttribute = /([^\/\s<>"'`=]+)/y,
reFilteredValue = /\{\{\{([\S\s]+?)\}\}\}/y,
reIndirectValue = /\{\{([^\}]+)\}\}/y,
reSubstitutedValue = /(?:```([\s\S]*?)```|`([^`]|[\S\s]*?)`)/y;
// Skip whitespace
pos = $tw.utils.skipWhiteSpace(source,pos);
// Get the attribute name

View File

@@ -22,7 +22,7 @@ Note that the syntax for comments is simplified to an opening "<!--" sequence an
"use strict";
exports.name = "commentblock";
exports.types = {block:true, pragma:true};
exports.types = {block: true, pragma: true};
exports.init = function(parser) {
this.parser = parser;
@@ -43,9 +43,18 @@ exports.findNextMatch = function(startPos) {
return undefined;
};
exports.parse = function() {
// Move past the match
this.parser.pos = this.endMatchRegExp.lastIndex;
// Don't return any elements
return [];
// Return a node representing the comment that is not rendered
var commentStart = this.match.index;
var commentEnd = this.endMatch.index + this.endMatch[0].length;
return [{
type: "void",
children: [],
text: this.parser.source.slice(commentStart, commentEnd),
start: commentStart,
end: commentEnd
}];
};

View File

@@ -40,6 +40,13 @@ exports.findNextMatch = function(startPos) {
exports.parse = function() {
// Move past the match
this.parser.pos = this.endMatchRegExp.lastIndex;
// Don't return any elements
return [];
// Return a node representing the inline comment
var commentStart = this.match.index;
var commentEnd = this.endMatch.index + this.endMatch[0].length;
return [{
type: "void",
text: this.parser.source.slice(commentStart, commentEnd),
start: commentStart,
end: commentEnd
}];
};

View File

@@ -39,7 +39,7 @@ exports.parse = function() {
// Return the classed span
return [{
type: "element",
tag: "strike",
tag: "s",
children: tree
}];
};

View File

@@ -28,11 +28,11 @@ exports.init = function(parser) {
exports.parse = function() {
// Move past the match
var start = this.parser.pos;
var start = this.parser.pos;
this.parser.pos = this.matchRegExp.lastIndex;
// Create the link unless it is suppressed
if(this.match[0].substr(0,1) === "~") {
return [{type: "text", text: this.match[0].substr(1)}];
return [{type: "text", text: this.match[0].substr(1), start: start, end: this.parser.pos}];
} else {
return [{
type: "element",

View File

@@ -6,15 +6,15 @@ module-type: wikirule
Wiki pragma rule for function, procedure and widget definitions
```
\function name(param:defaultvalue,param2:defaultvalue)
\function name(param:"defaultvalue", param2:"defaultvalue")
definition text
\end
\procedure name(param:defaultvalue,param2:defaultvalue)
\procedure name(param:"defaultvalue", param2:"defaultvalue")
definition text
\end
\widget $mywidget(param:defaultvalue,param2:defaultvalue)
\widget $mywidget(param:"defaultvalue", param2:"defaultvalue")
definition text
\end
```

View File

@@ -50,6 +50,8 @@ exports.parse = function() {
}
}
} while(match && !match[1]);
// Return the nodes
// Mark first and last node, and return the nodes
if(tree[0]) tree[0].isRuleStart = true;
if(tree[tree.length-1]) tree[tree.length-1].isRuleEnd = true;
return tree;
};

View File

@@ -41,7 +41,7 @@ Parse the most recent match
exports.parse = function() {
// Retrieve the most recent match so that recursive calls don't overwrite it
var tag = this.nextTag;
if (!tag.isSelfClosing) {
if(!tag.isSelfClosing) {
tag.openTagStart = tag.start;
tag.openTagEnd = tag.end;
}
@@ -49,7 +49,7 @@ exports.parse = function() {
// Advance the parser position to past the tag
this.parser.pos = tag.end;
// Check for an immediately following double linebreak
var hasLineBreak = !tag.isSelfClosing && !!$tw.utils.parseTokenRegExp(this.parser.source,this.parser.pos,/([^\S\n\r]*\r?\n(?:[^\S\n\r]*\r?\n|$))/g);
var hasLineBreak = !tag.isSelfClosing && !!$tw.utils.parseTokenRegExp(this.parser.source,this.parser.pos,/([^\S\n\r]*\r?\n(?:[^\S\n\r]*\r?\n|$))/y);
// Set whether we're in block mode
tag.isBlock = this.is.block || hasLineBreak;
// Parse the body if we need to
@@ -63,22 +63,22 @@ exports.parse = function() {
}
tag.end = this.parser.pos;
tag.closeTagEnd = tag.end;
if (tag.closeTagEnd === tag.openTagEnd || this.parser.source[tag.closeTagEnd - 1] !== '>') {
if(tag.closeTagEnd === tag.openTagEnd || this.parser.source[tag.closeTagEnd - 1] !== ">") {
tag.closeTagStart = tag.end;
} else {
tag.closeTagStart = tag.closeTagEnd - 2;
var closeTagMinPos = tag.children.length > 0 ? tag.children[tag.children.length-1].end : tag.openTagEnd;
if (!Number.isSafeInteger(closeTagMinPos)) closeTagMinPos = tag.openTagEnd;
while (tag.closeTagStart >= closeTagMinPos) {
if(!Number.isSafeInteger(closeTagMinPos)) closeTagMinPos = tag.openTagEnd;
while(tag.closeTagStart >= closeTagMinPos) {
var char = this.parser.source[tag.closeTagStart];
if (char === '>') {
if(char === ">") {
tag.closeTagStart = -1;
break;
}
if (char === '<') break;
if(char === "<") break;
tag.closeTagStart -= 1;
}
if (tag.closeTagStart < closeTagMinPos) {
if(tag.closeTagStart < closeTagMinPos) {
tag.closeTagStart = tag.end;
}
}
@@ -100,7 +100,7 @@ exports.parseTag = function(source,pos,options) {
orderedAttributes: []
};
// Define our regexps
var reTagName = /([a-zA-Z0-9\-\$\.]+)/g;
const reTagName = /([a-zA-Z0-9\-\$\.]+)/y;
// Skip whitespace
pos = $tw.utils.skipWhiteSpace(source,pos);
// Look for a less than sign
@@ -148,7 +148,7 @@ exports.parseTag = function(source,pos,options) {
pos = token.end;
// Check for a required line break
if(options.requireLineBreak) {
token = $tw.utils.parseTokenRegExp(source,pos,/([^\S\n\r]*\r?\n(?:[^\S\n\r]*\r?\n|$))/g);
token = $tw.utils.parseTokenRegExp(source,pos,/([^\S\n\r]*\r?\n(?:[^\S\n\r]*\r?\n|$))/y);
if(!token) {
return null;
}

View File

@@ -113,7 +113,7 @@ exports.parseImage = function(source,pos) {
// Skip whitespace
pos = $tw.utils.skipWhiteSpace(source,pos);
// Get the source up to the terminating `]]`
token = $tw.utils.parseTokenRegExp(source,pos,/(?:([^|\]]*?)\|)?([^\]]+?)\]\]/g);
token = $tw.utils.parseTokenRegExp(source,pos,/(?:([^|\]]*?)\|)?([^\]]+?)\]\]/y);
if(!token) {
return null;
}

View File

@@ -59,6 +59,7 @@ var listTypes = {
":": {listTag: "dl", itemTag: "dd"},
">": {listTag: "blockquote", itemTag: "div"}
};
exports.listTypes = listTypes;
/*
Parse the most recent match

View File

@@ -29,7 +29,7 @@ exports.findNextMatch = function(startPos) {
var c = this.parser.source.charAt(nextCall.end);
// Ensure EOL after parsed macro
// If we didn't need to support IE, we'd just use /(?:\r?\n|$)/ym
if ((c === "") || (c === "\n") || ((c === "\r") && this.parser.source.charAt(nextCall.end+1) === "\n")) {
if((c === "") || (c === "\n") || ((c === "\r") && this.parser.source.charAt(nextCall.end+1) === "\n")) {
this.nextCall = nextCall;
return nextStart;
}

View File

@@ -42,3 +42,5 @@ exports.parse = function() {
this.parser.pos = call.end;
return [call];
};

View File

@@ -52,9 +52,10 @@ exports.parse = function() {
}
}
// Is the remainder of the \define line blank after the parameter close paren?
var reEnd;
var reEnd,isBlock = true;
if(this.match[3]) {
// If so, it is a multiline definition and the end of the body is marked with \end
isBlock = false;
reEnd = new RegExp("((?:^|\\r?\\n)[^\\S\\n\\r]*\\\\end[^\\S\\n\\r]*(?:" + $tw.utils.escapeRegExp(this.match[1]) + ")?\\s*?(?:$|\\r?\\n))","mg");
} else {
// Otherwise, the end of the definition is marked by the end of the line
@@ -79,7 +80,8 @@ exports.parse = function() {
attributes: {},
children: [],
params: params,
isMacroDefinition: true
isMacroDefinition: true,
isBlock: isBlock && !!endMatch
}];
$tw.utils.addAttributeToParseTreeNode(parseTreeNodes[0],"name",this.match[1]);
$tw.utils.addAttributeToParseTreeNode(parseTreeNodes[0],"value",text);

View File

@@ -31,6 +31,7 @@ Parse the most recent match
*/
exports.parse = function() {
// Move past the pragma invocation
var start = this.parser.pos;
this.parser.pos = this.matchRegExp.lastIndex;
// Parse whitespace delimited tokens terminated by a line break
var reMatch = /[^\S\n]*(\S+)|(\r?\n)/mg,
@@ -58,6 +59,11 @@ exports.parse = function() {
this.parser.parseAsInline = true;
}
}
// No parse tree nodes to return
return [];
return [{
type: "void",
children: [],
parseAsInline: this.parser.parseAsInline,
start: start,
end: this.parser.pos
}];
};

View File

@@ -113,3 +113,5 @@ exports.parseLink = function(source,pos) {
node.end = closePos + 2;
return node;
};

View File

@@ -32,7 +32,7 @@ exports.parse = function() {
var text = this.match[1],
link = this.match[2] || text,
textEndPos = this.parser.source.indexOf("|", start);
if (textEndPos < 0 || textEndPos > this.matchRegExp.lastIndex) {
if(textEndPos < 0 || textEndPos > this.matchRegExp.lastIndex) {
textEndPos = this.matchRegExp.lastIndex - 2;
}
var linkStart = this.match[2] ? (start + this.match[1].length + 1) : start;

View File

@@ -54,6 +54,13 @@ exports.parse = function() {
if(tokens.length > 0) {
this.parser.amendRules(tokens[0],tokens.slice(1));
}
// No parse tree nodes to return
return [];
// No widget to render, return void node.
return [{
type: "void",
attributes: {
action: {type: "string", value: tokens[0]},
rules: {type: "string", value: tokens.slice(1).join(" ")}
},
children: []
}];
};

View File

@@ -64,5 +64,8 @@ exports.parse = function() {
$tw.utils.addAttributeToParseTreeNode(tree[t],"style",styles.join(""));
}
}
return tree;
return [{
type: "void",
children: tree
}]
};

View File

@@ -21,7 +21,7 @@ exports.types = {inline: true};
exports.init = function(parser) {
this.parser = parser;
// Regexp to match
// Regexp to match /@@(styles)?\s*(\.class\s+)?/
this.matchRegExp = /@@((?:[^\.\r\n\s:]+:[^\r\n;]+;)+)?(\.(?:[^\r\n\s]+)\s+)?/mg;
};

View File

@@ -23,6 +23,27 @@ exports.init = function(parser) {
this.matchRegExp = /\{\{([^\{\}\|]*)(?:\|\|([^\|\{\}]+))?(?:\|([^\{\}]+))?\}\}(?:\r?\n|$)/mg;
};
/*
Reject the match if we don't have a template or text reference
*/
exports.findNextMatch = function(startPos) {
this.matchRegExp.lastIndex = startPos;
this.match = this.matchRegExp.exec(this.parser.source);
if(this.match) {
var template = $tw.utils.trim(this.match[2]),
textRef = $tw.utils.trim(this.match[1]);
// Bail if we don't have a template or text reference
if(!template && !textRef) {
return undefined;
} else {
return this.match.index;
}
} else {
return undefined;
}
return this.match ? this.match.index : undefined;
};
exports.parse = function() {
// Move past the match
this.parser.pos = this.matchRegExp.lastIndex;

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