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

Compare commits

..

181 Commits

Author SHA1 Message Date
Jeremy Ruston
54ebf60a05 Merge branch 'master' into dynamic-macro-calls 2025-12-18 21:09:07 +00:00
Jeremy Ruston
5d72fb0608 Minor docs update 2025-12-18 21:08:54 +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
IchijikuIchigo
4b824795c8 [ja_JP] Update of Japanese translations (#9499)
* [ja_JP] Japanese translation update from commit: c625e3c, ac83b46, 314ce12

* [ja_JP] Japanese translation update from commit: b0d950f, 381388f, 4dc89f6, 6a39a4e

* [ja_JP] Japanese translation update from commit: 29a567f, 3597e65, 3378497

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

* [ja_JP] Japanese translation update from commit: b0d950f, 381388f

* [ja_JP] Japanese translation update from commit: 614ba84

* [ja_JP] Japanese translation update from commit: 23a23d9

* [ja_JP] Japanese translation update from commit: 8993572

* [ja_JP] Japanese translation update from commit: 8993572, 697171a, 75e89a1, ee55ab6, a73e03c, ac83b46

* [ja_JP] Japanese translation update from commit: 8993572

* [ja_JP] Japanese translation update from commit: 8993572

* [ja_JP] Japanese translation update from commit: 81862b5

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

* [ja_JP] Japanese translation update from commit: 899a498

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

* [ja_JP] Japanese translation update from commit: d63a189

* [ja_JP] Japanese translation update from commit: 789d64f

* [ja_JP] Japanese translation update from commit: 5490b78

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

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

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

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

* [ja_JP] Japanese translation update from commit: 29a567f
2025-12-18 16:13:07 +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
Christian Byron
fc74219c0b Add CeebeeTree comunity card (#9501) 2025-12-17 14:28:23 +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
Jeremy Ruston
ffde2da16c Add @CeeBeeTree to the comunity card for the Newsletter team 2025-12-16 09:20:51 +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
30e6092819 Fix unneeded diff 2025-11-24 11:11:33 +00:00
lin onetwo
4305a0bb92 Fix/dynamic macro calls test (#9459)
* Revert "Temporarily disable a broken serializer test"

This reverts commit b3144300ee.

* restore synamic parameter parse result

* lint

* lint

* remove duplicate

* Update core/modules/parsers/parseutils.js

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

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

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

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

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

* fix: mixed qouted and unquoted

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-24 11:10:42 +00:00
Jeremy Ruston
b3144300ee Temporarily disable a broken serializer test 2025-11-23 21:04:04 +00:00
Jeremy Ruston
205e8cf9c9 Fix test 2025-11-23 21:03:32 +00:00
Jeremy Ruston
ad4c1ca5a1 Merge branch 'master' into dynamic-macro-calls 2025-11-23 20:59:41 +00:00
Christian Höhne
4bb0bc5527 Signing CLA (#9307)
Co-authored-by: Jeremy Ruston <jeremy@jermolene.com>
2025-11-23 18:32:43 +00:00
Jeremy Ruston
6cb333b65b Merge branch 'tiddlywiki-com' 2025-11-22 17:48:43 +00:00
Jeremy Ruston
3378497816 Add link to launch archive 2025-11-22 17:48:28 +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
Jeremy Ruston
d81204c6ab Add Community Card for QA team
@Leilei332 I hope you don't mind, but I've nominated you to be the lead of the QA team. If you're happy to take the role, please could you submit a community card?
2025-11-12 13:03:14 +00: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
lin onetwo
e001c21bf5 Adds commuinity card for LinOnetwo (#9426)
(cherry picked from commit 98ecbf7441)
2025-11-12 12:26:27 +01: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
Mario Pietsch
102c236267 Create PMario community card (#9417) 2025-11-11 16:43:59 +00:00
Saq Imtiaz
06a2923adf Adds edit.tiddlywiki.com to infrastructure team responsibilities (#9419) 2025-11-11 16:37:45 +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
54e5ef7489 Fix link 2025-11-11 11:41:27 +00: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
Jeremy Ruston
6f306d1ed6 Community Team Card for the Developer Experience Team
@pmario this is a starter list of, please do suggest improvements
2025-11-10 10:21:58 +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
Mario Pietsch
5dfdbc8ea0 [DOCS] Slightly improve the substitution documentation (#9357) 2025-11-07 19:27:20 +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
4d4d9d9995 [DOCS] Add docs about deprecated classes (#9345)
* Add docs about deprecated classes

* Add Core classes tag

* Deprecate `tc-language-*` class

* Update deprecation warning

* 更新 Deprecated Core Classes.tid

Co-authored-by: Saq Imtiaz <saq.imtiaz@gmail.com>

* Apply suggestion from @saqimtiaz

---------

Co-authored-by: Saq Imtiaz <saq.imtiaz@gmail.com>
2025-11-04 13:03:38 +01: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
yogoshell
bad87c405e docs: Correcting typo in description field (#9087) 2025-10-28 22:16:32 +01:00
yogoshell
6f23a078b7 Signing CLA (#9086)
Co-authored-by: Saq Imtiaz <saq.imtiaz@gmail.com>
2025-10-28 22:15:04 +01:00
Mario Pietsch
c3706b8a79 [Docs] Convert Markdown links into TW-Links. Remove redundant TiddlyWiki links. Add links to current docs. (#9209) 2025-10-28 22:12:05 +01:00
Mohammad Rahmani
7ca8fb29af Docs: Fix Issue in Triggering $:/AdvancedSearch (#9244) 2025-10-28 22:11:20 +01:00
Markus Sauermann
d39bb5274e Fix typo in Filter Syntax (#9111) 2025-10-28 22:07:54 +01:00
lin onetwo
81b69783c4 docs: How to add new cascade and use it (#8472)
* docs: How to add new cascade and use it

* Update Cascade Mechanism.tid

* docs: update

* Update How to Create a Custom Cascade Entry.tid

* Refine with deepseek
2025-10-27 15:59:14 +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
Rishu Kumar
b349adde16 Fix Renaming on pasting image from clipboard (#9369)
We can do Renaming on pasting image from clipboard
2025-10-27 16:11:32 +01:00
Saq Imtiaz
d63a1896b3 docs: update more links for PR maker (#9362) 2025-10-23 17:48:57 +02:00
Saq Imtiaz
b65fa11643 Update ContributionBanner to reflect new URI for pr maker 2025-10-23 16:56:21 +02:00
Saq Imtiaz
4043499633 Update link in make-pr-maker-link procedure
Update URI for PR maker
2025-10-23 16:53:30 +02:00
Saq Imtiaz
6a39a4e13b Docs: add link to Contributing guidelines in developers tiddler (#9361) 2025-10-23 12:07:37 +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
Mario Pietsch
5b5147dade Make ReleaseTemplate use code-body (#9340) 2025-10-21 13:00:06 +02: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
Mohammad Rahmani
2c8fafee48 docs: Fix Slider Plugin Url (#9354) 2025-10-21 12:30:37 +02: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
Joseph Yi
09a42a54c0 Chore: Signing CLA (#9344) 2025-10-15 21:25:01 +01:00
Mario Pietsch
f4f31c37fc Remove tweb.at from community resources (#9330) 2025-10-12 10:55:39 +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
Mario Pietsch
ed3405672a Rename Intertwingled innovations.webp to Innovations.webp (#9331) 2025-10-11 11:19:55 +02:00
Mario Pietsch
e3af967cbb [DOCS] Convert PNG images into WEBP to reduce wiki size by 3MB (#9162)
* convert PNG images into WEBP to reduce wiki size by 3MB

* convert Community Survey 2025.png to webp
2025-10-10 15:02:30 +01:00
Mario Pietsch
6b0d3fab5d [Docs] Fix typo in DefaultTiddlers (#9202) 2025-10-10 15:01:00 +01:00
Mario Pietsch
13f1689e7e [DOC] ContentType Info and Links - Add Compount Tiddler Type (#9249) 2025-10-10 15:00:43 +01:00
Mario Pietsch
4c09a88272 [DOCS] Improve "range Operator" documentation. Make placeholder "terms" consistent with filter strings (#9276)
* [DOCS] Improve "range Operator" documentation. Make placeholder "terms" consistent with filter strings

* update range operator table formatting a bit
2025-10-10 14:59:27 +01:00
Jeremy Ruston
fbf110e209 Merge branch 'tiddlywiki-com' 2025-10-10 14:58:29 +01:00
Mario Pietsch
3c1d658fad Adjust Missing Links for Community Resources (#9329)
* Adjust Missing Links for Community Resources

* Update _Lucky Sushi_ online shop by sini-Kit.tid
2025-10-10 14:44:57 +01:00
Cameron Fischer
5d738673ac Added "Hello There" Thumbnail for TW5-Graph (#9322)
* Added "Hello There" Thumbnail for TW5-Graph

* Preventing TiddlyWiki link in TW5-Graph description

* Added modified date for TW5-Graph post
2025-10-10 14:30:54 +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
Mario Pietsch
7c020d0eb6 Add a CSV Table as Example to Typed Blocks in Wikitext (#9318) 2025-10-06 16:22:31 +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
a2e5c2cca2 Add documentation for Encrypted Import Problems (#9100) 2025-10-02 11:49:59 +01: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
Scott Sauyet
5ff4e02a61 Update TableOfContents.tid (#9312)
Move Welcome back to the top of the TOC.

Third attempt.  Someday I'll get this right.
2025-10-02 11:22:37 +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
2cc7c96eec Remove inadvertently committed files 2025-09-25 16:48:22 +01:00
Jeremy Ruston
8cd3d4e22c HireJeremy: Tweaks 2025-09-20 11:52:39 +01:00
Jeremy Ruston
37e09d1c25 Add Bluesky link for Jermolene 2025-09-17 14:02:13 +01:00
Jeremy Ruston
7a080092d0 Allow community cards to be edited in the browser under Node.js 2025-09-16 11:05:04 +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
dd91ac0b82 [DOCS] Button widget role attribute (#9168)
* [DOCS] Button role attribute

* Fix wrong language
2025-09-14 11:27:49 +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
c22046a2c2 Fix some duplicate tiddlers
There were two Welcome tiddlers, and two tiddlers with the title "Saving with the HTML5 saver"
2025-09-11 13:29:53 +01:00
Jeremy Ruston
5136e33f07 Tweak examples 2025-05-15 15:09:05 +01:00
Jeremy Ruston
744f6e7a3b Add some examples 2025-05-12 17:43:45 +01:00
Jeremy Ruston
1ded43f6ea Test for attribute macros 2025-05-12 17:32:30 +01:00
Jeremy Ruston
48e6863a89 Parse attribute macros with the new parser
This fixes the tests
2025-05-12 17:16:08 +01:00
Jeremy Ruston
f5d5a23aff Test for standalone macro calls with dynamic parameters 2025-05-12 14:26:21 +01:00
Jeremy Ruston
2d6b6b5011 Initial commit
The idea is to extend the macro call syntax to accept dynamic parameter values (ie thing:{{more}} etc). Eventually, this will work in all the contexts in which the double angle bracket syntax is valid.

This initial commit gets the tests passing, but doesn't yet activate the new functionality.
2025-05-12 14:03:29 +01:00
697 changed files with 8993 additions and 31990 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

View File

@@ -1,6 +1,7 @@
name: Bug report
description: Create a report to help us improve TiddlyWiki 5
title: "[BUG] "
type: bug
body:
- type: textarea
id: Describe

View File

@@ -4,6 +4,7 @@ about: Suggest an idea for TiddlyWiki 5
title: "[IDEA]"
labels: ''
assignees: ''
type: feature
---

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

@@ -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

@@ -0,0 +1,14 @@
title: @Christian_Byron
tags: Community/Person
fullname: Christian Byron
talk.tiddlywiki.org: Christian_Byron
github: ceebeetree
linkedin: www.linkedin.com/in/christian-byron-b84a594/
avatar: /9j/4AAQSkZJRgABAQAAAQABAAD/2wCEAAgICAgJCAkKCgkNDgwODRMREBARExwUFhQWFBwrGx8bGx8bKyYuJSMlLiZENS8vNUROQj5CTl9VVV93cXecnNEBCAgICAkICQoKCQ0ODA4NExEQEBETHBQWFBYUHCsbHxsbHxsrJi4lIyUuJkQ1Ly81RE5CPkJOX1VVX3dxd5yc0f/CABEIACAAIAMBIgACEQEDEQH/xAAuAAEBAAMBAAAAAAAAAAAAAAAHBgEDBQQBAAMBAAAAAAAAAAAAAAAAAAABAwX/2gAMAwEAAhADEAAAADv2xtJlY03sqePW3ARS1RSydIhcH//EACcQAAICAgIBAgYDAAAAAAAAAAECAwQFEQASMRMhBhBBk8HRIzJx/9oACAEBAAE/AMFQxs+NExqJLMCwYE+SOT4bF3qr+hAIpRsDQ6lWH0Yco4S/eVniRVQHXZzrZ5dwGQpQtNII2RfJVvHMRl5cbKxC94n/ALp+RxfiKpNcgMMUqPIwjcnWip/I5XtUowaL3Ujir/xt79Glb6/4OZ7MV5oEpUzuIa7MPB14A5jpoYLsEsydo1bbLre+CWEEEYab7Uf74ZYSSThpvtR/vmRmhnuzywp1jZtquta+VPM49qlcy24lf017At7g8uZnHrUsGK3Ez+m3UBvcnXy//8QAHhEAAgEFAAMAAAAAAAAAAAAAAQIDAAQRIkEyUaH/2gAIAQIBAT8AmiuVlZkLEeQOflJPcvMAF0z65V+h0YIW52rBDuxUrztf/8QAIxEBAAEDAwMFAAAAAAAAAAAAAgEAAxEEBSMSQcEiMVJxof/aAAgBAwEBPwC/Z1ZvNBOYz1Gc/lDUat3ySPRM/H2P3W4hcbIldpxnxW3BcjQk9oznzX//2Q==
Hello ~TiddlyWikiers - I have been a long time fan, recent contributor to the TW community.
Recently I have volunteered to run the [[TiddlyWiki Newsletter|https://tiddlywiki.substack.com/]] to spread the great news about TW.
I have been in the IT industry for about thirty years, mostly as a consultant and technical arcitect.
More recently I went back to study a masters in IT focussing on AI and data science.
Now my partner and I have started our own business ([[Sphere Innovations|https://sphere-innovations.com.au]]) - in consulting and building web applications for small to medium size businesses here in Australia.

View File

@@ -6,6 +6,7 @@ talk.tiddlywiki.org: jeremyruston
github: Jermolene
linkedin: www.linkedin.com/in/jermy
flickr: www.flickr.com/photos/jermy/
bluesky: https://bsky.app/profile/jermolene.bsky.social
homepage: jermolene.com
email: jeremy@jermolene.com
avatar: /9j/4AAQSkZJRgABAQAAAQABAAD/2wCEAAgICAgJCAkKCgkNDgwODRMREBARExwUFhQWFBwrGx8bGx8bKyYuJSMlLiZENS8vNUROQj5CTl9VVV93cXecnNEBCAgICAkICQoKCQ0ODA4NExEQEBETHBQWFBYUHCsbHxsbHxsrJi4lIyUuJkQ1Ly81RE5CPkJOX1VVX3dxd5yc0f/CABEIACAAIAMBIgACEQEDEQH/xAAtAAEBAAMAAAAAAAAAAAAAAAAHBgIEBQEBAQEBAAAAAAAAAAAAAAAAAgQBBf/aAAwDAQACEAMQAAAANF4uTuPRhD2nBLnUiJvKM0DtMKy//8QAKxAAAgIBAwMDAQkAAAAAAAAAAQIDBBEABRITITEiMkFxFEJRUmFicoGR/9oACAEBAAE/AInTA6gUGP4ZOQbW1bPsmyUq1q+gmvFPUzZPDkPamtwqU75ks04JakroVcg5RwRjg66NUx25KbzqJYyMngfqSuq0M3NZYIebJIvZozIvI/iNPcp/aalSdJXsS4VcKeIzlvU3jVTcYLNiaGISrjkhWQYDfQ63pYAzCDBsOiu7Dsx4EHH6r2w2ttimjd2IsNErhhJHKI04/uzqxuCxpBYVVWKSHqwMyMSQ33SB7dUJFmlkMYRgnqZgCMf7rf8AeEt3A9YOhjXAb2k8u7dtT1RZeOtXmYxiOPj4ZWY/lb51skqUNnNW/wBNzC7IpB6gQeeB/jq/fqGOaLbowuYn5MAQOw8LjW5Vmeo0qIsqYLLKjHIZmwv9fB1//8QAHxEAAQMEAwEAAAAAAAAAAAAAEQABAgMSIWExMkFR/9oACAECAQE/AD9iTy2lJmHUB8BVKM4SNSOj46a29saX/8QAHREAAgICAwEAAAAAAAAAAAAAAQIAAwQRITGBkf/aAAgBAwEBPwDHpFpJZtamVSiBWT2Yt7hmCDsb+TKtsKqpGg3M/9k=

View File

@@ -0,0 +1,19 @@
avatar: /9j/4AAQSkZJRgABAQAAAQABAAD/2wCEAAgICAgJCAkKCgkNDgwODRMREBARExwUFhQWFBwrGx8bGx8bKyYuJSMlLiZENS8vNUROQj5CTl9VVV93cXecnNEBCAgICAkICQoKCQ0ODA4NExEQEBETHBQWFBYUHCsbHxsbHxsrJi4lIyUuJkQ1Ly81RE5CPkJOX1VVX3dxd5yc0f/CABEIACAAIAMBIgACEQEDEQH/xAAuAAEAAwEBAAAAAAAAAAAAAAAGAwQHAgUBAAMBAAAAAAAAAAAAAAAAAAACAwT/2gAMAwEAAhADEAAAAOfCWAMdKKetM4wOvY5OcvZnrYf/xAApEAACAQQBBAECBwAAAAAAAAABAgMABAURQQYSIVETFCIxMkJicYKh/9oACAEBAAE/AEtysaStr7mPaPeuazWdMM4gEnfPryW8hBUuZvou2RXRxyreBWPmgyNqs8f8MOQalhdY7Vz+R4/s/qfP+1edNi/zl7HDcFbmS3E8CcMR4INP0PkBhklIm+sZNtFtQiV0nj57Owl+dSrSTFgD6/CtH4VV9lU3oAbPngAVY389lc5URuUZkMxhnR4pvW0VwDqsP1FNmLWYqCpikMbngmliJNY+aKzyTxXS6lRAyg/u5rq+5x2RsuyTa3MQMlvKniRGThTUd1JYXUdzAwDvqVxGdRXMbfrVOD7HBrG3mNEsU8z98TRhl9eRzX//xAAcEQACAgIDAAAAAAAAAAAAAAABAgARAzESIVH/2gAIAQIBAT8ARuXZPsul3Eoje5lBQWBP/8QAGREAAwEBAQAAAAAAAAAAAAAAAAECEiER/9oACAEDAQE/AM98Lk7LJe20z//Z
created: 20251110102157310
first-sighting: 2019-03-01
fullname: Lin Onetwo
github: linonetwo
homepage: https://wiki.onetwo.website/
modified: 20251111184556193
tags: Community/Person Community/Team/Contributors
talk.tiddlywiki.org: linonetwo
title: @linonetwo
type: text/vnd.tiddlywiki
Since 2014, when I started college, I've been on a quest for a lifelong PKM tool. I cherish my life and all my experiences, and I dont want to forget any of them. When Im deeply focused on a task, its easy to lose sight of other important parts of my life—so I needed a system to help me stay balanced.
Early on, I tried TiddlyWiki several times, but I was initially put off by its save mechanism and markup editing. That changed when I discovered an auto-backup script, which gave me the confidence to fully commit. Over time, I improved the script and eventually transitioned to using TidGi-Desktop and TidGi-Mobile.
Today, my TiddlyWiki holds all my game design ideas and progress logs—it has truly become my second brain. With the help of LLM-powered programming tools, Ive enhanced it with numerous plugins, allowing me to manage my mind in a more programmable and structured way. As a game developer, TiddlyWiki isn't the core of my professional work; But I've invested so much time because it's fundamentally about upgrading my mind.
Most of my notes are open by default and shared publicly on my homepage as a digital garden.

View File

@@ -0,0 +1,25 @@
avatar: UklGRiwIAABXRUJQVlA4WAoAAAAwAAAAPwAAPwAASUNDUCACAAAAAAIgbGNtcwRAAABtbnRyR1JBWVhZWiAH6QALAAoACwADAAZhY3NwTVNGVAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA9tYAAQAAAADTLWxjbXMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZkZXNjAAAAzAAAAG5jcHJ0AAABPAAAADZ3dHB0AAABdAAAABRrVFJDAAABiAAAACBkbW5kAAABqAAAACRkbWRkAAABzAAAAFJtbHVjAAAAAAAAAAEAAAAMZW5VUwAAAFIAAAAcAEcASQBNAFAAIABiAHUAaQBsAHQALQBpAG4AIABEADYANQAgAEcAcgBhAHkAcwBjAGEAbABlACAAdwBpAHQAaAAgAHMAUgBHAEIAIABUAFIAQwAAbWx1YwAAAAAAAAABAAAADGVuVVMAAAAaAAAAHABQAHUAYgBsAGkAYwAgAEQAbwBtAGEAaQBuAABYWVogAAAAAAAA81EAAQAAAAEWzHBhcmEAAAAAAAMAAAACZmYAAPKnAAANWQAAE9AAAApbbWx1YwAAAAAAAAABAAAADGVuVVMAAAAIAAAAHABHAEkATQBQbWx1YwAAAAAAAAABAAAADGVuVVMAAAA2AAAAHABEADYANQAgAEcAcgBhAHkAcwBjAGEAbABlACAAdwBpAHQAaAAgAHMAUgBHAEIAIABUAFIAQwAAVlA4TOYFAAAvP8APEDWGgbRtWv+yt/0WImICOBvWn1C4dFi1bStbvpY8Qg2ePANNNAMh3N2db/7A91/7CHBvBBRr25ZFH+4k98ihkqi2CP4tsANvX8a+8y8Ct04dn0nuUt39ZiBJkowqt911M+MJ1G3bNiZJr1iP0DZ+2bbdadsqprOjAqmoUIX9hf3Fl5/uPYV7I3OMeoFzIvrvwG0kRUr3zPLdYMMXaqrMMsp0K4fufKO6c2hFV5Zh7kRROZX0PSCmB/3KWQwpuiekWelSRZDW94d0q750NrxavpFn1eLNQ9EV8nWlmAET6Q8lrCRTcjFLlLImluK3iXJW/hT47KGklS8OlzWUtXLFYDRCSS74ojUjxggqKMoxd6A1lTCyvsvyzC5/d7BsCHb7yIcHyrX2yR/NPnsAdRT2i0Pwp/o0Il6ix8hsRAuJmQgcr4KREfAiMgUVm9KqmfSxL5pOJspVwwTiV6jiIAg1RMhHpERhbvwgGI34Hc49T7UeKZtXwEqJ+BAaoBneperJH0POs1u4dufwv8Gf+qcOfjyvX6ZIVgxE0Rw87YF3BSc9c7jsXfdjOBG7FwmSb39pfGRwu8IuvUjJNoTpFzkEvDg6W3Qt/9nf99ZXPy8HM43IweTKyNR+WVatXcWWyakBksj9cqW+QetplcjsKElvZH/zuOO/PrCx//tL3/6x/O/C1PZZvSKuulLcS4l8M1ewGPR6ef5sllXW2eGQZ7hVSEZiPmcqrSS8e2ElX8o7t1fvB9LFetmEx5hx1Xuye2PpfjZnSjj7QfKTB3bZZo05Zvh6YuivX24cpc8+ddvADWG9odrSwFalVurxUiidDHmTiaoNkkh2gjbcpxMiAbd39aVP119/N9k4+euNKfcNjwaPhZEuUupUsJrHchw1LkPrRC9bQKa3M8Mj/xx903drdnHMpbirj1ENsUre0oo3N+7gat+2ZctKdsIUYc21sRu+Ucdhn+P7DyarftW00iu3Tmbv+hTfdCTmyaIPT4PrYZDFtBN2W8S9m4oTB5Z2P3Oe7weKjVBq86kXX/r0+WuvTAzfjqm1hsYRPWlbxm4n3IaeGOJEizv8orH9w5ejjmSrfOuEq/HxT6eDemtsZ/HTvvG1/8iVspxZILrlkz/cdsIbIroOgJileFSty2xiHNW5t9fbHJ3ze87bp5T9vc8RuqMB0ReDSt464R/BJxspvgpEsrVAJMTsYg2QovPTOHrvQ9et/S2Xx+40z7dY4JBX0Pz/ElH/T73U2DkK8EiqC9hM/zV3frQfzjaAqO16s1l6xCUXnBFlYxyIer3eEdth7u5xsHKxWoGLqzY3wIULt9G3K6soei9jZ+UcF+Ka3M/II9EUWrJ/LLxy+Q9xIh0vOl3NZCrVnBsuFUTOSnJnSioRWZ9q4g+ZDk5XVORoW2qX2hbIkna3JOrdR3jmpHVLovUkLES6grRO010u0GkDlX7SpH1DQ64Wl2zaSUJv1Mtti2G7kx5IyftWMhfDlGClcxvIUhP5crhp9LIb1Vne187oSAWxelcR/kXjYQTZboW+Oj1pqF0gmfZhSDD6bSgzGWrw3s7QLNtCV+2uatYrd/aFtjDI8R52e/DdyKgRKXBhEak3Ev50+GCUA9EFUor39htVMxmWvW8AM6ptG416rZvdWn+MarIEyH5r6ruZSrx8XrWDP370vbfTjqpmZGIbiFPFoihc4jcrlYi9p3ndSuymZ+XLaKza/P/HUWHn5Axdkd9OjBskY0+pIlz4AlFPFs+aStK5PBIRR4MVVJDihsy4JdEA4pVcrVqMZDyL2/8aYocikEAR9Xjc1BNG9zEiJG7n/cGyrtnblkClBhEgMW4Kx21BEBGJjLa0hcOGmTK64KsKLfKr9QyQELclxY3hqowTIZKdZNTSS5BWiBPlKxDWBVSS41bOepkhTkhGDajLfLyUBOKlkMHPgOhx3JoRN/cEiRgSWdgF2yCyDQu4IcbNo8ftTzxveOJ5y+h509h52+h549h569h587/M20f/b1AB
created: 20251110102157310
first-sighting: 2009-11-14
fullname: Mario Pietsch
github: pmario
homepage: https://wikilabs.github.io/
modified: 20251110124935183
tags: Community/Person Community/Team/Contributors
talk.tiddlywiki.org: pmario
title: @pmario
type: text/vnd.tiddlywiki
youtube: https://www.youtube.com/@pmario
''Hi, My name is Mario Pietsch''. Back in 2009 I was ''searching'' for ''a simple presentation tool'' and discovered ~TiddlyWiki Classic, Monkey Pirate ~TiddlyWiki ([[MPTW|https://mptw.tiddlyspot.com/]]) with ~TagglyTagging, Eric Shulman's ~TiddlyTools, Saq Imtiaz's navigation macros, and more. --- ''I was captivated''.
After a deep dive, I combined these elements into my own "Presentation Manager", along [[3 step by step tutorials|https://groups.google.com/g/tiddlywiki/c/qG_tZ1x0MEU/m/-vLA0luMicYJ]] to help others build it.
Thanks to ''the positive spirit'' of the ~TiddlyWiki community, I am proud to be part of it since 2009.
When Jeremy started developing ~TiddlyWiki 5 on ~GitHub, I joined in—opening [[issue no. 1|https://github.com/TiddlyWiki/TiddlyWiki5/issues/1]] all the way up to 13. For what thats good ;) Since then, I have submitted nearly 600 pull requests and more than 500 issues, many of which have been merged or resolved.
My ~TiddlyWiki 5 "laboratory" is at https://wikilabs.github.io, and I also share content on my ''~YouTube'' channel: https://www.youtube.com/@pmario
Have fun!<br>
Mario

View File

@@ -0,0 +1,19 @@
title: Developer Experience Team
tags: Community/Team
modified: 20251109200632671
created: 20251109200632671
leader: @pmario
team: @saqimtiaz
The Developer Experience Team improves the experience of software contributors to the TiddlyWiki project. This includes enhancing documentation, streamlining contribution processes, and providing tools and resources to help developers effectively contribute to TiddlyWiki.
Tools and resources managed by the Developer Experience Team include:
* Advising and assisting contributors, particularly new developers
* Maintenance of developer-focused documentation on the https://tiddlywiki.com/dev/ site, including:
** Development environment setup guides
** Code review processes and best practices
** Contribution guidelines and documentation
* Continuous integration and deployment scripts providing feedback on pull requests
* Devising and implementing labelling systems for issues and pull requests
* Automation scripts to simplify common development tasks

View File

@@ -1,8 +1,8 @@
title: Infrastructure Team
tags: Community/Team
modified: 20250909171928024
created: 20250909171928024
modified: 20251110133437795
tags: Community/Team
team: @MotovunJack
title: Infrastructure Team
The Infrastructure Team is responsible for maintaining and improving the infrastructure that supports the TiddlyWiki project. This includes the hosting, deployment, and management of the TiddlyWiki websites and services, as well as the tools and systems used by the TiddlyWiki community.
@@ -12,3 +12,4 @@ The infrastructure includes:
* github.com/TiddlyWiki
* tiddlywiki.com DNS
* Netlify account for PR previews
* edit.tiddlywiki.com

View File

@@ -2,5 +2,6 @@ title: Newsletter Team
tags: Community/Team
modified: 20250909171928024
created: 20250909171928024
leader: @Christian_Byron
The Newsletter Team is responsible for producing the TiddlyWiki Newsletter, a monthly email newsletter that highlights news, updates, and community contributions related to TiddlyWiki.

View File

@@ -0,0 +1,11 @@
title: Quality Assurance Team
created: 20251112125742296
modified: 20251112125742296
tags: Community/Team
team:
leader: @Leilei332
title: Quality Assurance Team
The Quality Assurance Team is responsible for ensuring the quality and reliability of TiddlyWiki releases. This includes reviewing code submissions, testing new features, identifying bugs, and verifying that fixes are effective.

View File

@@ -1,5 +1,5 @@
title: Community/Team
modified: 20250909171928024
created: 20250909171928024
list: [[Project Team]] [[Core Team]] [[Documentation Team]] [[MultiWikiServer Team]] [[Newsletter Team]] [[Infrastructure Team]] [[Succession Team]]
list: [[Project Team]] [[Core Team]] [[Documentation Team]] [[Quality Assurance Team]] [[Infrastructure Team]] [[MultiWikiServer Team]] [[Newsletter Team]] [[Succession Team]]

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

@@ -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

@@ -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

@@ -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 = {
@@ -106,13 +107,14 @@ exports.parseStringLiteral = function(source,pos) {
type: "string",
start: pos
};
var reString = /(?:"""([\s\S]*?)"""|"([^"]*)")|(?:'([^']*)')/g;
var reString = /(?:"""([\s\S]*?)"""|"([^"]*)")|(?:'([^']*)')|\[\[([^\]]*)\]\]/g;
reString.lastIndex = pos;
var match = reString.exec(source);
if(match && match.index === pos) {
node.value = match[1] !== undefined ? match[1] :(
match[2] !== undefined ? match[2] : match[3]
);
match[2] !== undefined ? match[2] : (
match[3] !== undefined ? match[3] : match[4]
));
node.end = pos + match[0].length;
return node;
} else {
@@ -172,7 +174,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
@@ -205,28 +207,164 @@ exports.parseMacroParameter = function(source,pos) {
Look for a macro invocation. Returns null if not found, or {type: "transclude", attributes:, start:, end:}
*/
exports.parseMacroInvocationAsTransclusion = function(source,pos) {
var node = $tw.utils.parseMacroInvocation(source,pos);
if(node) {
var positionalName = 0,
transclusion = {
type: "transclude",
start: node.start,
end: node.end
};
$tw.utils.addAttributeToParseTreeNode(transclusion,"$variable",node.name);
$tw.utils.each(node.params,function(param) {
var name = param.name;
if(name) {
if(name.charAt(0) === "$") {
name = "$" + name;
}
$tw.utils.addAttributeToParseTreeNode(transclusion,{name: name,type: "string", value: param.value, start: param.start, end: param.end});
} else {
$tw.utils.addAttributeToParseTreeNode(transclusion,{name: (positionalName++) + "",type: "string", value: param.value, start: param.start, end: param.end});
}
});
return transclusion;
// console.log("parseMacroInvocationAsTransclusion",source,pos);
var node = {
type: "transclude",
start: pos,
attributes: {},
orderedAttributes: []
};
// Define our regexps
var reVarName = /([^\s>"'=:]+)/g;
// Skip whitespace
pos = $tw.utils.skipWhiteSpace(source,pos);
// Look for a double opening angle bracket
var token = $tw.utils.parseTokenString(source,pos,"<<");
if(!token) {
// console.log("No opening << in",source,"at",pos);
return null;
}
pos = token.end;
// Get the variable name for the macro
token = $tw.utils.parseTokenRegExp(source,pos,reVarName);
if(!token) {
// console.log("No macro name");
return null;
}
$tw.utils.addAttributeToParseTreeNode(node,"$variable",token.match[1]);
pos = token.end;
// Check that the tag is terminated by a space or >>
if(!$tw.utils.parseWhiteSpace(source,pos) && !(source.charAt(pos) === ">" && source.charAt(pos + 1) === ">") ) {
// console.log("No space or >> after macro name");
return null;
}
// Process attributes
pos = $tw.utils.parseMacroParametersAsAttributes(node,source,pos);
// Skip whitespace
pos = $tw.utils.skipWhiteSpace(source,pos);
// Look for a double closing angle bracket
token = $tw.utils.parseTokenString(source,pos,">>");
if(!token) {
// console.log("No closing >>");
return null;
}
node.end = token.end;
return node;
};
/*
Parse macro parameters as attributes. Returns the position after the last attribute
*/
exports.parseMacroParametersAsAttributes = function(node,source,pos) {
var position = 0,
attribute = $tw.utils.parseMacroParameterAsAttribute(source,pos);
while(attribute) {
if(!attribute.name) {
attribute.name = (position++) + "";
attribute.isPositional = true;
}
node.orderedAttributes.push(attribute);
node.attributes[attribute.name] = attribute;
pos = attribute.end;
// Get the next attribute
attribute = $tw.utils.parseMacroParameterAsAttribute(source,pos);
}
node.end = pos;
return pos;
}
/*
Parse a macro parameter as an attribute. Returns null if not found, otherwise returns {name:, type: "filtered|string|indirect|macro", value|filter|textReference:, start:, end:,}, with the name being optional
*/
exports.parseMacroParameterAsAttribute = function(source,pos) {
var node = {
start: pos
};
// Define our regexps
var reAttributeName = /([^\/\s>"'`=:]+)/g,
reUnquotedAttribute = /((?:(?:>(?!>))|[^\s>"'])+)/g,
reFilteredValue = /\{\{\{([\S\s]+?)\}\}\}/g,
reIndirectValue = /\{\{([^\}]+)\}\}/g,
reSubstitutedValue = /(?:```([\s\S]*?)```|`([^`]|[\S\s]*?)`)/g;
// Skip whitespace
pos = $tw.utils.skipWhiteSpace(source,pos);
// Get the attribute name and the separator token
var nameToken = $tw.utils.parseTokenRegExp(source,pos,reAttributeName),
namePos = nameToken && $tw.utils.skipWhiteSpace(source,nameToken.end),
separatorToken = nameToken && $tw.utils.parseTokenRegExp(source,namePos,/=|:/g);
// console.log(`parseMacroParametersAtAttribute source is ${source} at ${pos} and namepos is ${namePos} with nameToken as ${JSON.stringify(nameToken)} and separatorToken as ${JSON.stringify(separatorToken)}`);
// If we have a name and a separator then we have a named attribute
if(nameToken && separatorToken) {
node.name = nameToken.match[1];
// key value separator is `=` or `:`
node.assignmentOperator = separatorToken.match[0];
pos = separatorToken.end;
}
// Skip whitespace
pos = $tw.utils.skipWhiteSpace(source,pos);
// Look for a string literal
var stringLiteral = $tw.utils.parseStringLiteral(source,pos);
if(stringLiteral) {
pos = stringLiteral.end;
node.type = "string";
node.value = stringLiteral.value;
// Mark the value as having been quoted in the source
node.quoted = true;
} else {
// console.log(`Failed to parse string literal ${source} at ${pos} with node as ${JSON.stringify(node)}`);
// Look for a filtered value
var filteredValue = $tw.utils.parseTokenRegExp(source,pos,reFilteredValue);
if(filteredValue) {
pos = filteredValue.end;
node.type = "filtered";
node.filter = filteredValue.match[1];
} else {
// console.log(`Failed to parse filtered value ${source} at ${pos} with node as ${JSON.stringify(node)}`);
// Look for an indirect value
var indirectValue = $tw.utils.parseTokenRegExp(source,pos,reIndirectValue);
if(indirectValue) {
pos = indirectValue.end;
node.type = "indirect";
node.textReference = indirectValue.match[1];
} else {
// console.log(`Failed to parse indirect value ${source} at ${pos} with node as ${JSON.stringify(node)}`);
// Look for a unquoted value
var unquotedValue = $tw.utils.parseTokenRegExp(source,pos,reUnquotedAttribute);
if(unquotedValue) {
pos = unquotedValue.end;
node.type = "string";
node.value = unquotedValue.match[1];
// console.log(`Parsed unquoted value ${source} at ${pos} with node as ${JSON.stringify(node)}`);
} else {
// console.log(`Failed to parse unquoted value ${source} at ${pos} with node as ${JSON.stringify(node)}`);
// Look for a macro invocation value
var macroInvocation = $tw.utils.parseMacroInvocationAsTransclusion(source,pos);
if(macroInvocation) {
pos = macroInvocation.end;
node.type = "macro";
node.value = macroInvocation;
// console.log(`Parsed macro invocation ${source} at ${pos} with node as ${JSON.stringify(node)}`);
} else {
// console.log(`Failed to parse macro invocation ${source} at ${pos} with node as ${JSON.stringify(node)}`);
var substitutedValue = $tw.utils.parseTokenRegExp(source,pos,reSubstitutedValue);
if(substitutedValue) {
pos = substitutedValue.end;
node.type = "substituted";
node.rawValue = substitutedValue.match[1] || substitutedValue.match[2];
} else {
// console.log(`Failed to parse substituted value ${source} at ${pos} with node as ${JSON.stringify(node)}`);
}
}
}
}
}
}
// Bail if we don't have a value
if(!node.type) {
return null;
}
// Update the end position
node.end = pos;
return node;
};
@@ -240,7 +378,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 +415,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;
@@ -295,18 +433,18 @@ exports.parseFilterVariable = function(source) {
};
/*
Look for an HTML attribute definition. Returns null if not found, otherwise returns {type: "attribute", name:, type: "filtered|string|indirect|macro", value|filter|textReference:, start:, end:,}
Look for an HTML attribute definition. Returns null if not found, otherwise returns {name:, type: "filtered|string|indirect|macro", value|filter|textReference:, start:, end:,}
*/
exports.parseAttribute = function(source,pos) {
var node = {
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
@@ -353,7 +491,7 @@ exports.parseAttribute = function(source,pos) {
node.value = unquotedValue.match[1];
} else {
// Look for a macro invocation value
var macroInvocation = $tw.utils.parseMacroInvocation(source,pos);
var macroInvocation = $tw.utils.parseMacroInvocationAsTransclusion(source,pos);
if(macroInvocation) {
pos = macroInvocation.end;
node.type = "macro";
@@ -374,6 +512,7 @@ exports.parseAttribute = function(source,pos) {
}
}
} else {
// If there is no equals sign or colon, then this is an attribute with no value, defaulting to "true"
node.type = "string";
node.value = "true";
}

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: []
}];
};

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