From 6bd4127e883e1165670af477aae5125d7d6e416e Mon Sep 17 00:00:00 2001
From: Jeremy Ruston
Date: Wed, 19 Apr 2023 11:55:25 +0100
Subject: [PATCH] Parameterised transclusions (#6666)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* Initial commit
Everything is draft.
* Fix test execution
* Fix and test missing target handling
* Use the ubertransclude widget for the wikitext transclusion syntax
* Changed transclude widget in binary parser to ubertransclude
* Add a test for custom action widgets
* Don't worry about ordered attributes
The changes in 0bffae21088aafc0cdebafe6a5de7907d7c52a3a mean that we don't need to explicitly maintain the ordered attributes
* Remove need to explicitly clear widget mapping variable when invoking overridden widget
* Use ts- prefix for system slot names
* Add a definition for the value widget just so that it doesn't cause errors
Of course, it doesn't actually need to be a JS widget, it could be a wikitext widget...
* Add support for positional parameters
* Ubertransclusion positional parameters should be based on name, not position
* Add support for shortcut syntax for positional transclusion parameters
* Importvariables should skip parameters widgets
* Refactor transclude widget before uberfying it
* Refactor ubertransclude functionality into transclude widget
* Replace ubertransclude widget with transclude widget
* Add wikitext shortcut for new-style function definitions
* Allow brackets to be omitted for function definitions with no parameters
* Add pragma rule for parameters declarations
* Remove erroneous "tag" property
* Add support for accessing function parameters as name/value pairs
* Be as permissive as possible with parameter names
Previously restricted to upper and lower case, digits and dash and underscore
* Rewrite some tests to use the shortcut syntaxes
* Mustn't allow commas in parameter names
* Fix crash when transcluding an undefined variable
Thanks @pmario
See https://github.com/Jermolene/TiddlyWiki5/pull/6666#issuecomment-1114692359
* Unquoted parameters should not eat a succeeding comma
Fixes #6672
* Remove extraneous code
* Allow the let widget to create variables starting with $
* Fix addAttributeToParseTreeNode handling of ordered attributes
* Reuse attribute objects when executing custom widgets
* Fix importing of function definitions
* Fix parameter handling
* Introduce genesis widget for dynamically creating widgets
See the "RedefineLet" test for a contrived example of usage
* Change tiddler separator used in wikitext tests
Underscore looked ambiguous; I kept typing dashes by accident
* Cache parse trees when transcluding variables
* Fix bug with empty strings ignored in $tw.utils.stringifyList/parseStringArray
I will pull this out into a separate PR. Fixing it doesn't cause problems for the core but I imagine it might cause issues for 3rd party code.
* Fixes to enable the transclude widget itself to be overridden
There are two big changes here:
Replace the previous "ts-wrapper" mechanism, which we had been using to redefine custom widgets inside their definitions to prevent recursive calls. Now we've got the genesis widget we can instead control recursion through a new "$remappable" attribute that allows the custom widget mechanism to be skipped.
We also extend the slot widget to allow a depth to be specified; it then reaches up by the indicated number of transclusion widgets to find the one from which it should retrieve the slot value.
* Fix genesis widget example
* Use enlist:raw to preserve duplicates
* Don't create variables with value undefined for missing parameters
* Fix variable retrieval bug with test harness
* Improve recursion detection
While retaining backwards compatibility
* Experimental support for custom filter operators
Just as we can define custom widgets we can also define custom parameterised filter operators
* Add visible transclusions component and demo
Very useful to see transclusions explicitly
Makes a good demo of a super-complicated widget override.
* Genesis widget should pass raw attributes onto child widget...
...so that it can more efficiently handle refreshing itself.
* Use consistent parse tree node property for params
* Extend transclude widget to work with old-style macros and use it for the macrocall shortcut syntax
* Clarify that the recent changes allow functions to be invoked with the double bracket syntax
In other words, the transclude widget distinguishes between functions and macros and handles the parameters appropriately
* Make the macrocall widget delegate to the transclude widget
* Switch to using \procedure to define new-style macros, and \function for custom filter operator functions
I now need to update the OP!
* Fix visible transclusion example
* Remove obsolete code
Left over after refactoring
* Better backwards compatibility for legacy recursion marker
Fixes the problem with tag dropdowns @btheado
* Fix stringifying/parsing string arrays containing newlines
A very old bug.
Fixes the ActionListOpsWidget problem @btheado
* Transclude: replace paramNames/paramValues with more robust JSON payload
More details at https://github.com/Jermolene/TiddlyWiki5/pull/6666#issuecomment-1123719153
* Rename internal "unknown" filter operator so that users cannot invoke it
* Detect recursion by tracking widget tree depth
The old recursion marker approach was very slow, and didn't catch test cases like editions/test/tiddlers/tests/data/transclude/Recursion.tid
* Use \widget for custom widget definitions, and remove need for angle brackets
Need to do some refactoring of all those isFunctionDefinition/isProcedureDefinition/isWidgetDefinition flags into a single property
* Rename <$value> widget to <$fill>
* Require $$ for custom widgets, and that overridden JS widgets must exist
See https://github.com/Jermolene/TiddlyWiki5/pull/6666#issuecomment-1133637763
* Fix invocation of JS macros
* Experimental update of the parse-tree preview visualisation
An experiment to try out using the new JSON operators for rendering the JSON parse tree that we get back from the wikify widget.
As usual with these experiments, this one is going to require quite a lot more work to finish up:
* The formatting is via direct styles rather than classes
* The formatting for attributes and properties is not yet completed
* The same thing needs to also be done to the widget tree preview
* Procedures and widgets inherit whitespace trim setting from their definition
* Missed off 22e7ec23811b137a119295b5ce72bccdc18a697a
* Require period prefix for custom filter operator functions
To ensure that custom filter operators cannot clash with future core operators.
* Allow custom functions to be invoked as attributes
* WIP
* Remove unneeded test tiddler
* Make is[variable] and variables[] operators resilient to fake widgets
* Fix importvariables to work with setvariables as well as set (they are aliases)
* Add support for $:/tags/Global
* Remove accidental commit 6cc99fcbe33da47cfcb1335d0742b16ea1b9ce03
* Add utility function for parsing macro parameter definitions
* Introduce true global variables
The basic idea is that if we don't find a variable `foo` then we fallback to retrieving the value from the tiddler `$:/global/foo`, if it exists.
This allows us to replace the usual importvariables-based mechanism for global definitions, avoiding cluttering up the variable namespace with every macro.
In order to permit subprocedures to be overridden, we also introduce a mechanism for conditional definitions: preceding the word definition|procedure|function|widget with a + causes the definition only to occur if the specified variable doesn't already exist. In the next commit we'll apply this mechanism to the tabs macro
* Convert the tabs macro into a global
So far it appears to be totally backwards compatible... In practice, I think maybe this and the conversion of the other macros should go into a separate subsequent PR.
* Change to `?` for conditional definitions
* Fix tabs global so it doesn't crash when viewed directly
* Test showing how to un-override a core widget
* Cleaning up after f63634900724eda937286d946b2e6f65fcf6d503
* Minor cleanups
* Clean up unknown filter
* Introduce function operator for calling functions
Can invoke any functions, not just those start with a period. And can pass zero parameters (in contrast when invoked as a custom filter operator there's no way to omit the first parameter).
* Use underscores for new system fields for global variable tiddlers
For consistency with `_canonical_uri`; unlike many system fields, the behaviour of these fields is baked into the core JS code.
* Refactor $parameters widget
The objective is to add a $depth attribute so that it is possible to reach up to retrieve the parameters of ancestor transclusions. However, doing so requires changing the encoding of parameter names so that it is not possible for a user parameter to clash with an attribute like $depth. So now we have to double up dollars on any attribute names seen by the parameters widget, just like with the transclude widget itself.
* Fix refreshing of global variables
Global variables access within attributes will automatically trigger a refresh if the attribute text changes, but that wasn't happening for transclusions.
* Remove support for $:/tags/Global
It is not needed now that we have true global variables
* Typo from f513b403fe911442bcbaf0628fa47d3d2ed3cf93
* Make slot fill data available to transclusions
Allows transcluded content to dynamically process <$fill> widgets within the calling transclusion
* Mark docs as v5.3.0
* Simplify metaparameters implementation
* Fix typo
* Adjust naming of transclusion metaparameter
* Fix up handling of slot/fill for custom widgets
Previously we were wrapping the body in an implicit `<$fill $name="ts-body">` widget
* Add format:json operator
I've been finding this useful for debugging, and it kind of goes with the JSON operators
* Docs: JSON operators and tweaks to genesis widget
* Docs: format:json
Also tweak to the behaviour of format:json if the input string is not valid JSON
* Fix #6721
* Revert "Fix #6721"
This reverts commit b216579255d6e6214f3cf71ab771fcc57240aa74 which was committed to the wrong branch
* Fix new selection tracker to return relative coordinates
* Make use of type attribute consistent
* Docs: Transclude widget
* Simplify the fill widget
We can rely on the default processing in the base class
* Slot widget: be more defensive about negative depth values
* Parameters widget: Be defensive about negative depths
* Protect against excessively recursive functions
* FIx transcluding of functions
This first implementation concatenates the results of the filter (with no separator) and then wikifies the result.
The test in this commit is quite interesting...
* Tweak semantics of JSON operators to match #6932
This allows us to later bring in the optimisations without breaking backwards compatibility.
* Revert obsolete changes to boot.js
* Fix inadvertent whitespace change
* Remove tests related to obsolete changes to boot.js
Should have been part of 2f494ba15246edd356bfc591b0115d30592e7eb8
* Revert changes to parse tree preview
This implementation requires #6666
* Add test to show that global widgets need not use the _parameters field
* Disable overriding core widgets in safe mode
* Coding style tweak
* More comments
* Fix caching of parse variables/macros/procedures
* Transcluded functions should operate on the entire store
* Refactor filter recursion detection to avoid an unneeded wrapper function
* Fix error in 25312b3e3218c1002c483a1fc995d2b65509b993
* WIP
* Revert "WIP"
This reverts commit 8654dfc679ea12d30ffd8b14a30165d826be06b7.
* When transcluding functions, pass an empty item list to the filter, and just return the first item
* Rejig genesis widget to be easier to use
* Parameters widget: protect against negative $depth
* Docs updates
* Docs updates
* Tweak comments
* Add custom view template body for globals, and a new sidebar tab under "more"
And also a custom view template title that greys out the $:/global/ part of the title
* Update function operator to return the input list if the function is missing
* Remove negation from function operator
This implementation was not useful.
* Tests and docs for function operator
* Docs tweaks
* Improve indentation
See https://github.com/Jermolene/TiddlyWiki5/pull/6666#discussion_r967655251
* Missing tests for parameters widget
* Fix visible transclude
* Docs update
* Docs typo
* Huge Documentation Update
Not quite finished, but definitely on the home stretch
* Slight optimisation to user defined widgets
* Remove implementation of $:/globals/
Performance with this implementation is inherently poor because of the need to perform a wiki lookup for each child widget created.
* Docs clarification
* Docs update
* Some widget.js cleanups
* Remove support for conditional definitions
It was introduced for use cases associated with the global mechanism that was dropped in e3d13696c887ba849958c8980623d8ff45bb8a36
* Docs updates
* Revert change to setwidget docs
* Docs update
* Docs updates
* Clarify/simplify some tests
* More docs updates
* Fix doc file locations
* Docs updates
* Revert modified date of docs that have only had minor tweaks
* Docs typo
https://github.com/Jermolene/TiddlyWiki5/pull/6666#discussion_r990811220
Thanks @btheado
* Transcluding functions: fix missing parameters passed as undefined
Thanks @btheado – see https://github.com/Jermolene/TiddlyWiki5/pull/6666#issuecomment-1276187372
* Parameter parenthesis should be mandatory in function/procedure/widget definitions
See https://github.com/Jermolene/TiddlyWiki5/pull/6666#issuecomment-1280404387
* Attempt to build this branch with CI
* Add release note etc for 5.3.0
* Temporary new release banner for v5.3.0
* New New Release Banner
* New test for undefined parameters
* Adjust modified times of docs tiddlers to make them easier to find
* Update release note
* Add parenthesis to the visible transclusion definition
Parenthesis were made mandatory in 5194b24108efda6da95daf4261ffd80473073a65
Fixes #6998
* Fix macrocall refresh issue
It turns out that this.transcludeTitle is always truthy, even if we are transcluding a variable
Fixes #7001
* Filter run prefixes should use widget.makeFakeWidgetWithVariables
* Docs typo
Thanks @twMat
* Docs: clarify function operator invocation
See discussion at https://github.com/Jermolene/TiddlyWiki5/issues/6991#issuecomment-1301703599
* Docs: Update \define pragma to cover named ends
* Docs: move tiddlers to correct directory
* Add support for named end markers for procedures, functions and widgets
* Docs note about nested macro definitions
* Rename test
* Fix detection of empty transclusions
See https://talk.tiddlywiki.org/t/exploring-default-tiddler-links-hackability-in-v5-3-0/5745/25?u=jeremyruston
* New test missed off a45349cc996390192114fed486bfa6900da641d7
* Refactor wikified function tests
* Refactor function invocation
* Introduce new widget helper function to evaluate variables.Functions are evaluated as parameterised filter strings, macros as text with textual substitution of parameters and variables, and procedures and widgets as plain text
* Refactor the function operator and unknown operator to use the new helper
* Use the new helper to evaluate variables within filter strings, thus fixing a bug whereby functions called in such a way were being returned as plain text instead of being evaluated
* Refactor the transclude widget to use the new helper
* Update tests
* Fix positional parameters in widget.evaluateVariable()
This should clear up the remaining anomalies in #7009, let me know how you get on @btheado
* Remove 5.2.8 release note
* Fix nonstandard initialisation code for fill/parameter/slot widgets
* Update modification times of doc tiddlers
So that they are at the top of the recent tab
* Update 5.3.0 release note
* Remove custom CI step for this branch
* Restore standard sitetitle
---
core/modules/filterrunprefixes/cascade.js | 18 +-
core/modules/filterrunprefixes/filter.js | 24 +-
core/modules/filterrunprefixes/map.js | 24 +-
core/modules/filterrunprefixes/reduce.js | 26 +-
core/modules/filterrunprefixes/sort.js | 18 +-
core/modules/filters.js | 8 +-
core/modules/filters/filter.js | 17 +-
core/modules/filters/function.js | 32 ++
core/modules/filters/reduce.js | 29 +-
core/modules/filters/sortsub.js | 17 +-
core/modules/filters/unknown.js | 45 +++
core/modules/parsers/audioparser.js | 2 +
core/modules/parsers/binaryparser.js | 6 +-
core/modules/parsers/csvparser.js | 2 +
core/modules/parsers/htmlparser.js | 2 +
core/modules/parsers/imageparser.js | 2 +
core/modules/parsers/parseutils.js | 61 ++-
core/modules/parsers/pdfparser.js | 2 +
core/modules/parsers/textparser.js | 2 +
core/modules/parsers/videoparser.js | 2 +
.../parsers/wikiparser/rules/fnprocdef.js | 97 +++++
core/modules/parsers/wikiparser/rules/html.js | 3 -
.../wikiparser/rules/macrocallblock.js | 2 +-
.../wikiparser/rules/macrocallinline.js | 2 +-
.../parsers/wikiparser/rules/macrodef.js | 10 +-
.../parsers/wikiparser/rules/parameters.js | 60 +++
.../wikiparser/rules/transcludeblock.js | 23 +-
.../wikiparser/rules/transcludeinline.js | 23 +-
core/modules/parsers/wikiparser/wikiparser.js | 3 +-
core/modules/widgets/fill.js | 30 ++
core/modules/widgets/importvariables.js | 58 +--
core/modules/widgets/macrocall.js | 61 +--
core/modules/widgets/parameters.js | 96 +++++
core/modules/widgets/setvariable.js | 12 +-
core/modules/widgets/slot.js | 82 ++++
core/modules/widgets/transclude.js | 354 ++++++++++++++++--
core/modules/widgets/widget.js | 204 ++++++++--
core/modules/wiki.js | 27 +-
core/ui/Components/VisibleTransclude.tid | 48 +++
core/wiki/macros/tabs.tid | 2 +-
.../prerelease/tiddlers/Release 5.2.8.tid | 60 ---
.../prerelease/tiddlers/Release 5.3.0.tid | 83 ++++
.../custom-operators/NestedParameterised.tid | 24 ++
.../data/custom-operators/Parameterised.tid | 24 ++
.../tests/data/custom-operators/Simple.tid | 21 ++
.../data/functions/FunctionAttributes.tid | 24 ++
.../tests/data/functions/FunctionOperator.tid | 24 ++
.../tests/data/functions/MissingFunction.tid | 15 +
.../functions/RunawayRecursiveFunctions.tid | 18 +
.../data/functions/UndefinedParameters.tid | 22 ++
.../data/functions/WikifiedFunctions.tid | 36 ++
.../tests/data/genesis-widget/RedefineLet.tid | 31 ++
.../tiddlers/tests/data/procedures/Nested.tid | 20 +
.../transclude/CustomWidget-ActionWidget.tid | 27 ++
.../data/transclude/CustomWidget-Fail.tid | 26 ++
.../CustomWidget-Override-Codeblock.tid | 29 ++
.../CustomWidget-OverrideTransclude.tid | 33 ++
.../data/transclude/CustomWidget-Simple.tid | 33 ++
.../transclude/CustomWidget-Slotted-Empty.tid | 20 +
.../data/transclude/CustomWidget-Slotted.tid | 27 ++
.../CustomWidget-TextWidgetOverride.tid | 27 ++
...ustomWidget-TextWidgetOverrideWithSlot.tid | 31 ++
.../CustomWidget-Unoverride-Codeblock.tid | 31 ++
.../CustomWidget-VariableAttribute.tid | 29 ++
.../data/transclude/JavaScript-Macro.tid | 17 +
.../tests/data/transclude/Macro-Plain.tid | 17 +
.../tests/data/transclude/Macro-Simple.tid | 26 ++
.../tests/data/transclude/MissingTarget.tid | 48 +++
.../data/transclude/Parameterised-Depth.tid | 34 ++
.../data/transclude/Parameterised-Mode.tid | 29 ++
.../transclude/Parameterised-Name-Values.tid | 34 ++
.../Parameterised-ParseTreeNodes.tid | 29 ++
...terised-Positional-Shortcut-Parameters.tid | 29 ++
.../Parameterised-Positional-Shortcut.tid | 29 ++
.../Parameterised-Positional-Variables.tid | 30 ++
.../transclude/Parameterised-Positional.tid | 26 ++
.../Parameterised-Shortcut-Parameters.tid | 20 +
.../transclude/Parameterised-Shortcut.tid | 21 ++
.../data/transclude/Parameterised-Simple.tid | 26 ++
.../Parameterised-SlotFillParseTreeNodes.tid | 29 ++
.../Parameterised-Slotted-Missing.tid | 24 ++
.../data/transclude/Parameterised-Slotted.tid | 27 ++
.../data/transclude/Procedures-Whitespace.tid | 25 ++
.../tiddlers/tests/data/transclude/Typed.tid | 38 ++
editions/test/tiddlers/tests/test-filters.js | 4 +-
.../tiddlers/tests/test-parsetextreference.js | 2 +-
editions/test/tiddlers/tests/test-utils.js | 2 +-
editions/test/tiddlers/tests/test-widget.js | 20 +-
.../tiddlers/tests/test-wikitext-parser.js | 106 ++++--
.../tests/test-wikitext-tabs-macro.js | 4 +-
.../tiddlers/{ => concepts}/Brackets.tid | 2 +-
editions/tw5.com/tiddlers/concepts/Macros.tid | 39 +-
editions/tw5.com/tiddlers/concepts/Pragma.tid | 24 +-
.../tiddlers/features/StartupActions.tid | 2 +-
.../tw5.com/tiddlers/filters/function.tid | 21 ++
.../tw5.com/tiddlers/functions/Functions.tid | 28 ++
.../tiddlers/howtos/Visible Transclusions.tid | 14 +
.../tiddlers/images/New Release Banner.png | Bin 81675 -> 50280 bytes
.../macros/import/say-hi-using-variables.tid | 1 +
.../tw5.com/tiddlers/macros/import/say-hi.tid | 3 +-
.../macros/import/tags-of-current-tiddler.tid | 1 +
.../macros/import/tv-wikilink-tooltip.tid | 2 +-
.../tiddlers/pragmas/Pragma_ _define.tid | 51 +++
.../tiddlers/pragmas/Pragma_ _function.tid | 27 ++
.../tiddlers/pragmas/Pragma_ _import.tid | 17 +
.../tiddlers/pragmas/Pragma_ _parameters.tid | 18 +
.../tiddlers/pragmas/Pragma_ _procedure.tid | 55 +++
.../tiddlers/pragmas/Pragma_ _rules.tid | 25 ++
.../tiddlers/pragmas/Pragma_ _whitespace.tid | 20 +
.../tiddlers/pragmas/Pragma_ _widget.tid | 27 ++
editions/tw5.com/tiddlers/pragmas/Pragmas.tid | 13 +
.../tiddlers/procedures/Procedure Calls.tid | 56 +++
.../procedures/Procedure Definitions.tid | 44 +++
.../Procedure Parameter Handling.tid | 25 ++
.../tiddlers/procedures/Procedures.tid | 35 ++
.../tw5.com/tiddlers/variables/Variables.tid | 139 ++++++-
.../tiddlers/widgets/Custom Widgets.tid | 84 +++++
.../tw5.com/tiddlers/widgets/ErrorWidget.tid | 2 +-
.../tw5.com/tiddlers/widgets/FillWidget.tid | 21 ++
.../widgets/ImportVariablesWidget.tid | 2 +-
.../tiddlers/widgets/MacroCallWidget.tid | 37 +-
.../tiddlers/widgets/ParametersWidget.tid | 61 +++
.../tw5.com/tiddlers/widgets/SlotWidget.tid | 19 +
.../tiddlers/widgets/TranscludeWidget.tid | 172 ++++++++-
.../tiddlers/wikitext/HTML in WikiText.tid | 4 +-
.../wikitext/Macro Calls in WikiText.tid | 26 +-
.../tw5.com/tiddlers/wikitext/Macro Calls.tid | 58 +++
.../Macro Definitions in WikiText.tid | 113 +-----
.../tiddlers/wikitext/Macro Definitions.tid | 68 ++++
.../wikitext/Macro Parameter Handling.tid | 79 ++++
.../tiddlers/wikitext/Macro Pitfalls.tid | 39 ++
.../tiddlers/wikitext/Macros in WikiText.tid | 8 +-
.../Transclusion and Substitution.tid | 4 +-
.../wikitext/Transclusion in WikiText.tid | 4 +-
.../wikitext/Variables in WikiText.tid | 33 +-
.../wikitext/parser/Inline Mode WikiText.tid | 6 +-
...aces where the parser ignores WikiText.tid | 6 +-
.../wikitext/parser/WikiText Parser Modes.tid | 2 +-
.../WikiText parser mode transitions.tid | 4 +-
.../WikiText parser mode_ macro examples.tid | 2 +-
package.json | 2 +-
themes/tiddlywiki/vanilla/base.tid | 30 ++
142 files changed, 3869 insertions(+), 653 deletions(-)
create mode 100644 core/modules/filters/function.js
create mode 100644 core/modules/filters/unknown.js
create mode 100644 core/modules/parsers/wikiparser/rules/fnprocdef.js
create mode 100644 core/modules/parsers/wikiparser/rules/parameters.js
create mode 100644 core/modules/widgets/fill.js
create mode 100644 core/modules/widgets/parameters.js
create mode 100644 core/modules/widgets/slot.js
create mode 100644 core/ui/Components/VisibleTransclude.tid
delete mode 100644 editions/prerelease/tiddlers/Release 5.2.8.tid
create mode 100644 editions/prerelease/tiddlers/Release 5.3.0.tid
create mode 100644 editions/test/tiddlers/tests/data/custom-operators/NestedParameterised.tid
create mode 100644 editions/test/tiddlers/tests/data/custom-operators/Parameterised.tid
create mode 100644 editions/test/tiddlers/tests/data/custom-operators/Simple.tid
create mode 100644 editions/test/tiddlers/tests/data/functions/FunctionAttributes.tid
create mode 100644 editions/test/tiddlers/tests/data/functions/FunctionOperator.tid
create mode 100644 editions/test/tiddlers/tests/data/functions/MissingFunction.tid
create mode 100644 editions/test/tiddlers/tests/data/functions/RunawayRecursiveFunctions.tid
create mode 100644 editions/test/tiddlers/tests/data/functions/UndefinedParameters.tid
create mode 100644 editions/test/tiddlers/tests/data/functions/WikifiedFunctions.tid
create mode 100644 editions/test/tiddlers/tests/data/genesis-widget/RedefineLet.tid
create mode 100644 editions/test/tiddlers/tests/data/procedures/Nested.tid
create mode 100644 editions/test/tiddlers/tests/data/transclude/CustomWidget-ActionWidget.tid
create mode 100644 editions/test/tiddlers/tests/data/transclude/CustomWidget-Fail.tid
create mode 100644 editions/test/tiddlers/tests/data/transclude/CustomWidget-Override-Codeblock.tid
create mode 100644 editions/test/tiddlers/tests/data/transclude/CustomWidget-OverrideTransclude.tid
create mode 100644 editions/test/tiddlers/tests/data/transclude/CustomWidget-Simple.tid
create mode 100644 editions/test/tiddlers/tests/data/transclude/CustomWidget-Slotted-Empty.tid
create mode 100644 editions/test/tiddlers/tests/data/transclude/CustomWidget-Slotted.tid
create mode 100644 editions/test/tiddlers/tests/data/transclude/CustomWidget-TextWidgetOverride.tid
create mode 100644 editions/test/tiddlers/tests/data/transclude/CustomWidget-TextWidgetOverrideWithSlot.tid
create mode 100644 editions/test/tiddlers/tests/data/transclude/CustomWidget-Unoverride-Codeblock.tid
create mode 100644 editions/test/tiddlers/tests/data/transclude/CustomWidget-VariableAttribute.tid
create mode 100644 editions/test/tiddlers/tests/data/transclude/JavaScript-Macro.tid
create mode 100644 editions/test/tiddlers/tests/data/transclude/Macro-Plain.tid
create mode 100644 editions/test/tiddlers/tests/data/transclude/Macro-Simple.tid
create mode 100644 editions/test/tiddlers/tests/data/transclude/MissingTarget.tid
create mode 100644 editions/test/tiddlers/tests/data/transclude/Parameterised-Depth.tid
create mode 100644 editions/test/tiddlers/tests/data/transclude/Parameterised-Mode.tid
create mode 100644 editions/test/tiddlers/tests/data/transclude/Parameterised-Name-Values.tid
create mode 100644 editions/test/tiddlers/tests/data/transclude/Parameterised-ParseTreeNodes.tid
create mode 100644 editions/test/tiddlers/tests/data/transclude/Parameterised-Positional-Shortcut-Parameters.tid
create mode 100644 editions/test/tiddlers/tests/data/transclude/Parameterised-Positional-Shortcut.tid
create mode 100644 editions/test/tiddlers/tests/data/transclude/Parameterised-Positional-Variables.tid
create mode 100644 editions/test/tiddlers/tests/data/transclude/Parameterised-Positional.tid
create mode 100644 editions/test/tiddlers/tests/data/transclude/Parameterised-Shortcut-Parameters.tid
create mode 100644 editions/test/tiddlers/tests/data/transclude/Parameterised-Shortcut.tid
create mode 100644 editions/test/tiddlers/tests/data/transclude/Parameterised-Simple.tid
create mode 100644 editions/test/tiddlers/tests/data/transclude/Parameterised-SlotFillParseTreeNodes.tid
create mode 100644 editions/test/tiddlers/tests/data/transclude/Parameterised-Slotted-Missing.tid
create mode 100644 editions/test/tiddlers/tests/data/transclude/Parameterised-Slotted.tid
create mode 100644 editions/test/tiddlers/tests/data/transclude/Procedures-Whitespace.tid
create mode 100644 editions/test/tiddlers/tests/data/transclude/Typed.tid
rename editions/tw5.com/tiddlers/{ => concepts}/Brackets.tid (92%)
create mode 100644 editions/tw5.com/tiddlers/filters/function.tid
create mode 100644 editions/tw5.com/tiddlers/functions/Functions.tid
create mode 100644 editions/tw5.com/tiddlers/howtos/Visible Transclusions.tid
create mode 100644 editions/tw5.com/tiddlers/pragmas/Pragma_ _define.tid
create mode 100644 editions/tw5.com/tiddlers/pragmas/Pragma_ _function.tid
create mode 100644 editions/tw5.com/tiddlers/pragmas/Pragma_ _import.tid
create mode 100644 editions/tw5.com/tiddlers/pragmas/Pragma_ _parameters.tid
create mode 100644 editions/tw5.com/tiddlers/pragmas/Pragma_ _procedure.tid
create mode 100644 editions/tw5.com/tiddlers/pragmas/Pragma_ _rules.tid
create mode 100644 editions/tw5.com/tiddlers/pragmas/Pragma_ _whitespace.tid
create mode 100644 editions/tw5.com/tiddlers/pragmas/Pragma_ _widget.tid
create mode 100644 editions/tw5.com/tiddlers/pragmas/Pragmas.tid
create mode 100644 editions/tw5.com/tiddlers/procedures/Procedure Calls.tid
create mode 100644 editions/tw5.com/tiddlers/procedures/Procedure Definitions.tid
create mode 100644 editions/tw5.com/tiddlers/procedures/Procedure Parameter Handling.tid
create mode 100644 editions/tw5.com/tiddlers/procedures/Procedures.tid
create mode 100644 editions/tw5.com/tiddlers/widgets/Custom Widgets.tid
create mode 100644 editions/tw5.com/tiddlers/widgets/FillWidget.tid
create mode 100644 editions/tw5.com/tiddlers/widgets/ParametersWidget.tid
create mode 100644 editions/tw5.com/tiddlers/widgets/SlotWidget.tid
create mode 100644 editions/tw5.com/tiddlers/wikitext/Macro Calls.tid
create mode 100644 editions/tw5.com/tiddlers/wikitext/Macro Definitions.tid
create mode 100644 editions/tw5.com/tiddlers/wikitext/Macro Parameter Handling.tid
create mode 100644 editions/tw5.com/tiddlers/wikitext/Macro Pitfalls.tid
diff --git a/core/modules/filterrunprefixes/cascade.js b/core/modules/filterrunprefixes/cascade.js
index da6894d21..486e75f45 100644
--- a/core/modules/filterrunprefixes/cascade.js
+++ b/core/modules/filterrunprefixes/cascade.js
@@ -25,20 +25,10 @@ exports.cascade = function(operationSubFunction,options) {
if(!filterFnList[index]) {
filterFnList[index] = options.wiki.compileFilter(filter);
}
- var output = filterFnList[index](options.wiki.makeTiddlerIterator([title]),{
- getVariable: function(name,opts) {
- opts = opts || {};
- opts.variables = {
- "currentTiddler": "" + title,
- "..currentTiddler": widget.getVariable("currentTiddler")
- };
- if(name in opts.variables) {
- return opts.variables[name];
- } else {
- return widget.getVariable(name,opts);
- }
- }
- });
+ var output = filterFnList[index](options.wiki.makeTiddlerIterator([title]),widget.makeFakeWidgetWithVariables({
+ "currentTiddler": "" + title,
+ "..currentTiddler": widget.getVariable("currentTiddler","")
+ }));
if(output.length !== 0) {
result = output[0];
return false;
diff --git a/core/modules/filterrunprefixes/filter.js b/core/modules/filterrunprefixes/filter.js
index 783b699c2..4ab057109 100644
--- a/core/modules/filterrunprefixes/filter.js
+++ b/core/modules/filterrunprefixes/filter.js
@@ -19,23 +19,13 @@ exports.filter = function(operationSubFunction,options) {
var resultsToRemove = [],
index = 0;
results.each(function(title) {
- var filtered = operationSubFunction(options.wiki.makeTiddlerIterator([title]),{
- getVariable: function(name,opts) {
- opts = opts || {};
- opts.variables = {
- "currentTiddler": "" + title,
- "..currentTiddler": widget.getVariable("currentTiddler"),
- "index": "" + index,
- "revIndex": "" + (results.length - 1 - index),
- "length": "" + results.length
- };
- if(name in opts.variables) {
- return opts.variables[name];
- } else {
- return widget.getVariable(name,opts);
- }
- }
- });
+ var filtered = operationSubFunction(options.wiki.makeTiddlerIterator([title]),widget.makeFakeWidgetWithVariables({
+ "currentTiddler": "" + title,
+ "..currentTiddler": widget.getVariable("currentTiddler",""),
+ "index": "" + index,
+ "revIndex": "" + (results.length - 1 - index),
+ "length": "" + results.length
+ }));
if(filtered.length === 0) {
resultsToRemove.push(title);
}
diff --git a/core/modules/filterrunprefixes/map.js b/core/modules/filterrunprefixes/map.js
index efcb5b534..b756d6699 100644
--- a/core/modules/filterrunprefixes/map.js
+++ b/core/modules/filterrunprefixes/map.js
@@ -21,23 +21,13 @@ exports.map = function(operationSubFunction,options) {
flatten = (suffixes[0] && suffixes[0][0] === "flat") ? true : false;
results.clear();
$tw.utils.each(inputTitles,function(title) {
- var filtered = operationSubFunction(options.wiki.makeTiddlerIterator([title]),{
- getVariable: function(name,opts) {
- opts = opts || {};
- opts.variables = {
- "currentTiddler": "" + title,
- "..currentTiddler": widget.getVariable("currentTiddler"),
- "index": "" + index,
- "revIndex": "" + (inputTitles.length - 1 - index),
- "length": "" + inputTitles.length
- };
- if(name in opts.variables) {
- return opts.variables[name];
- } else {
- return widget.getVariable(name,opts);
- }
- }
- });
+ var filtered = operationSubFunction(options.wiki.makeTiddlerIterator([title]),widget.makeFakeWidgetWithVariables({
+ "currentTiddler": "" + title,
+ "..currentTiddler": widget.getVariable("currentTiddler",""),
+ "index": "" + index,
+ "revIndex": "" + (inputTitles.length - 1 - index),
+ "length": "" + inputTitles.length
+ }));
if(filtered.length && flatten) {
$tw.utils.each(filtered,function(value) {
results.push(value);
diff --git a/core/modules/filterrunprefixes/reduce.js b/core/modules/filterrunprefixes/reduce.js
index 8fe819e3f..ee2998837 100644
--- a/core/modules/filterrunprefixes/reduce.js
+++ b/core/modules/filterrunprefixes/reduce.js
@@ -18,24 +18,14 @@ exports.reduce = function(operationSubFunction,options) {
var accumulator = "",
index = 0;
results.each(function(title) {
- var list = operationSubFunction(options.wiki.makeTiddlerIterator([title]),{
- getVariable: function(name,opts) {
- opts = opts || {};
- opts.variables = {
- "currentTiddler": "" + title,
- "..currentTiddler": widget.getVariable("currentTiddler"),
- "index": "" + index,
- "revIndex": "" + (results.length - 1 - index),
- "length": "" + results.length,
- "accumulator": "" + accumulator
- };
- if(name in opts.variables) {
- return opts.variables[name];
- } else {
- return widget.getVariable(name,opts);
- }
- }
- });
+ var list = operationSubFunction(options.wiki.makeTiddlerIterator([title]),widget.makeFakeWidgetWithVariables({
+ "currentTiddler": "" + title,
+ "..currentTiddler": widget.getVariable("currentTiddler"),
+ "index": "" + index,
+ "revIndex": "" + (results.length - 1 - index),
+ "length": "" + results.length,
+ "accumulator": "" + accumulator
+ }));
if(list.length > 0) {
accumulator = "" + list[0];
}
diff --git a/core/modules/filterrunprefixes/sort.js b/core/modules/filterrunprefixes/sort.js
index 6865b175c..d8d376126 100644
--- a/core/modules/filterrunprefixes/sort.js
+++ b/core/modules/filterrunprefixes/sort.js
@@ -25,20 +25,10 @@ exports.sort = function(operationSubFunction,options) {
indexes = new Array(inputTitles.length),
compareFn;
results.each(function(title) {
- var key = operationSubFunction(options.wiki.makeTiddlerIterator([title]),{
- getVariable: function(name,opts) {
- opts = opts || {};
- opts.variables = {
- "currentTiddler": "" + title,
- "..currentTiddler": widget.getVariable("currentTiddler")
- };
- if(name in opts.variables) {
- return opts.variables[name];
- } else {
- return widget.getVariable(name,opts);
- }
- }
- });
+ var key = operationSubFunction(options.wiki.makeTiddlerIterator([title]),widget.makeFakeWidgetWithVariables({
+ "currentTiddler": "" + title,
+ "..currentTiddler": widget.getVariable("currentTiddler")
+ }));
sortKeys.push(key[0] || "");
});
results.clear();
diff --git a/core/modules/filters.js b/core/modules/filters.js
index 1bb5fe9ff..b705c994c 100644
--- a/core/modules/filters.js
+++ b/core/modules/filters.js
@@ -255,19 +255,21 @@ exports.compileFilter = function(filterString) {
var operands = [],
operatorFunction;
if(!operator.operator) {
+ // Use the "title" operator if no operator is specified
operatorFunction = filterOperators.title;
} else if(!filterOperators[operator.operator]) {
- operatorFunction = filterOperators.field;
+ // Unknown operators treated as "[unknown]" - at run time we can distinguish between a custom operator and falling back to the default "field" operator
+ operatorFunction = filterOperators["[unknown]"];
} else {
+ // Use the operator function
operatorFunction = filterOperators[operator.operator];
}
-
$tw.utils.each(operator.operands,function(operand) {
if(operand.indirect) {
operand.value = self.getTextReference(operand.text,"",currTiddlerTitle);
} else if(operand.variable) {
var varTree = $tw.utils.parseFilterVariable(operand.text);
- operand.value = widget.getVariable(varTree.name,{params:varTree.params,defaultValue: ""});
+ operand.value = widget.evaluateVariable(varTree.name,{params: varTree.params, source: source})[0] || "";
} else {
operand.value = operand.text;
}
diff --git a/core/modules/filters/filter.js b/core/modules/filters/filter.js
index 9b69fd83a..f15cbefc5 100644
--- a/core/modules/filters/filter.js
+++ b/core/modules/filters/filter.js
@@ -20,19 +20,10 @@ exports.filter = function(source,operator,options) {
results = [],
target = operator.prefix !== "!";
source(function(tiddler,title) {
- var list = filterFn.call(options.wiki,options.wiki.makeTiddlerIterator([title]),{
- getVariable: function(name,opts) {
- opts = opts || {};
- switch(name) {
- case "currentTiddler":
- return "" + title;
- case "..currentTiddler":
- return options.widget.getVariable("currentTiddler");
- default:
- return options.widget.getVariable(name,opts);
- }
- }
- });
+ var list = filterFn.call(options.wiki,options.wiki.makeTiddlerIterator([title]),options.widget.makeFakeWidgetWithVariables({
+ "currentTiddler": "" + title,
+ "..currentTiddler": options.widget.getVariable("currentTiddler","")
+ }));
if((list.length > 0) === target) {
results.push(title);
}
diff --git a/core/modules/filters/function.js b/core/modules/filters/function.js
new file mode 100644
index 000000000..f6a8c034d
--- /dev/null
+++ b/core/modules/filters/function.js
@@ -0,0 +1,32 @@
+/*\
+title: $:/core/modules/filters/function.js
+type: application/javascript
+module-type: filteroperator
+
+Filter operator returning those input titles that are returned from a function
+
+\*/
+(function(){
+
+/*jslint node: true, browser: true */
+/*global $tw: false */
+"use strict";
+
+/*
+Export our filter function
+*/
+exports.function = function(source,operator,options) {
+ var functionName = operator.operands[0],
+ variableInfo = options.widget && options.widget.getVariableInfo && options.widget.getVariableInfo(functionName);
+ if(variableInfo && variableInfo.srcVariable && variableInfo.srcVariable.isFunctionDefinition) {
+ return options.widget.evaluateVariable(functionName,{params: operator.operands.slice(1), source: source});
+ }
+ // Return the input list if the function wasn't found
+ var results = [];
+ source(function(tiddler,title) {
+ results.push(title);
+ });
+ return results;
+};
+
+})();
diff --git a/core/modules/filters/reduce.js b/core/modules/filters/reduce.js
index 50c501f08..efe8aea4a 100644
--- a/core/modules/filters/reduce.js
+++ b/core/modules/filters/reduce.js
@@ -26,27 +26,14 @@ exports.reduce = function(source,operator,options) {
accumulator = operator.operands[1] || "";
for(var index=0; index 0) {
accumulator = "" + list[0];
}
diff --git a/core/modules/filters/sortsub.js b/core/modules/filters/sortsub.js
index e9f676daa..d328be09c 100644
--- a/core/modules/filters/sortsub.js
+++ b/core/modules/filters/sortsub.js
@@ -25,19 +25,10 @@ exports.sortsub = function(source,operator,options) {
inputTitles.push(title);
var r = filterFn.call(options.wiki,function(iterator) {
iterator(options.wiki.getTiddler(title),title);
- },{
- getVariable: function(name,opts) {
- opts = opts || {};
- switch(name) {
- case "currentTiddler":
- return "" + title;
- case "..currentTiddler":
- return options.widget.getVariable("currentTiddler");
- default:
- return options.widget.getVariable(name,opts);
- }
- }
- });
+ },options.widget.makeFakeWidgetWithVariables({
+ "currentTiddler": "" + title,
+ "..currentTiddler": options.widget.getVariable("currentTiddler")
+ }));
sortKeys.push(r[0] || "");
});
// Rather than sorting the titles array, we'll sort the indexes so that we can consult both arrays
diff --git a/core/modules/filters/unknown.js b/core/modules/filters/unknown.js
new file mode 100644
index 000000000..21856766b
--- /dev/null
+++ b/core/modules/filters/unknown.js
@@ -0,0 +1,45 @@
+/*\
+title: $:/core/modules/filters/unknown.js
+type: application/javascript
+module-type: filteroperator
+
+Filter operator for handling unknown filter operators.
+
+Not intended to be used directly by end users, hence the square brackets around the name.
+
+\*/
+(function(){
+
+/*jslint node: true, browser: true */
+/*global $tw: false */
+"use strict";
+
+var fieldFilterOperatorFn = require("$:/core/modules/filters/field.js").field;
+
+/*
+Export our filter function
+*/
+exports["[unknown]"] = function(source,operator,options) {
+ // Check for a user defined filter operator
+ if(operator.operator.charAt(0) === ".") {
+ var variableInfo = options.widget && options.widget.getVariableInfo && options.widget.getVariableInfo(operator.operator);
+ if(variableInfo && variableInfo.srcVariable && variableInfo.srcVariable.isFunctionDefinition) {
+ var list = options.widget.evaluateVariable(operator.operator,{params: operator.operands, source: source});
+ if(operator.prefix === "!") {
+ var results = [];
+ source(function(tiddler,title) {
+ if(list.indexOf(title) === -1) {
+ results.push(title);
+ }
+ });
+ return results;
+ } else {
+ return list;
+ }
+ }
+ }
+ // Otherwise, use the "field" operator
+ return fieldFilterOperatorFn(source,operator,options);
+};
+
+})();
diff --git a/core/modules/parsers/audioparser.js b/core/modules/parsers/audioparser.js
index 95380bf80..5eb2ff985 100644
--- a/core/modules/parsers/audioparser.js
+++ b/core/modules/parsers/audioparser.js
@@ -28,6 +28,8 @@ var AudioParser = function(type,text,options) {
element.attributes.src = {type: "string", value: "data:" + type + ";base64," + text};
}
this.tree = [element];
+ this.source = text;
+ this.type = type;
};
exports["audio/ogg"] = AudioParser;
diff --git a/core/modules/parsers/binaryparser.js b/core/modules/parsers/binaryparser.js
index fb3d38678..60e7b5ef0 100644
--- a/core/modules/parsers/binaryparser.js
+++ b/core/modules/parsers/binaryparser.js
@@ -23,7 +23,7 @@ var BinaryParser = function(type,text,options) {
children: [{
type: "transclude",
attributes: {
- tiddler: {type: "string", value: BINARY_WARNING_MESSAGE}
+ "$tiddler": {type: "string", value: BINARY_WARNING_MESSAGE}
}
}]
};
@@ -38,7 +38,7 @@ var BinaryParser = function(type,text,options) {
children: [{
type: "transclude",
attributes: {
- tiddler: {type: "string", value: EXPORT_BUTTON_IMAGE}
+ "$tiddler": {type: "string", value: EXPORT_BUTTON_IMAGE}
}
}]
};
@@ -64,6 +64,8 @@ var BinaryParser = function(type,text,options) {
children: [warn, link]
}
this.tree = [element];
+ this.source = text;
+ this.type = type;
};
exports["application/octet-stream"] = BinaryParser;
diff --git a/core/modules/parsers/csvparser.js b/core/modules/parsers/csvparser.js
index 40431d0ae..f40b5f0e5 100644
--- a/core/modules/parsers/csvparser.js
+++ b/core/modules/parsers/csvparser.js
@@ -52,6 +52,8 @@ var CsvParser = function(type,text,options) {
tag = "td";
this.tree[0].children[0].children[0].children.push(row);
}
+ this.source = text;
+ this.type = type;
};
exports["text/csv"] = CsvParser;
diff --git a/core/modules/parsers/htmlparser.js b/core/modules/parsers/htmlparser.js
index 206ab9c78..24c9f5d3e 100644
--- a/core/modules/parsers/htmlparser.js
+++ b/core/modules/parsers/htmlparser.js
@@ -29,6 +29,8 @@ var HtmlParser = function(type,text,options) {
if($tw.wiki.getTiddlerText("$:/config/HtmlParser/DisableSandbox","no") !== "yes") {
this.tree[0].attributes.sandbox = {type: "string", value: $tw.wiki.getTiddlerText("$:/config/HtmlParser/SandboxTokens","")};
}
+ this.source = text;
+ this.type = type;
};
exports["text/html"] = HtmlParser;
diff --git a/core/modules/parsers/imageparser.js b/core/modules/parsers/imageparser.js
index e3b8fb60a..a964a4ba8 100644
--- a/core/modules/parsers/imageparser.js
+++ b/core/modules/parsers/imageparser.js
@@ -28,6 +28,8 @@ var ImageParser = function(type,text,options) {
}
}
this.tree = [element];
+ this.source = text;
+ this.type = type;
};
exports["image/svg+xml"] = ImageParser;
diff --git a/core/modules/parsers/parseutils.js b/core/modules/parsers/parseutils.js
index 925674056..6a0902c6f 100644
--- a/core/modules/parsers/parseutils.js
+++ b/core/modules/parsers/parseutils.js
@@ -123,6 +123,36 @@ exports.parseStringLiteral = function(source,pos) {
}
};
+/*
+Returns an array of {name:} with an optional "default" property. Options include:
+requireParenthesis: require the parameter definition to be wrapped in parenthesis
+*/
+exports.parseParameterDefinition = function(paramString,options) {
+ options = options || {};
+ if(options.requireParenthesis) {
+ var parenMatch = /^\s*\((.*)\)\s*$/g.exec(paramString);
+ if(!parenMatch) {
+ return [];
+ }
+ paramString = parenMatch[1];
+ }
+ var params = [],
+ reParam = /\s*([^:),\s]+)(?:\s*:\s*(?:"""([\s\S]*?)"""|"([^"]*)"|'([^']*)'|([^,"'\s]+)))?/mg,
+ paramMatch = reParam.exec(paramString);
+ while(paramMatch) {
+ // Save the parameter details
+ var paramInfo = {name: paramMatch[1]},
+ defaultValue = paramMatch[2] || paramMatch[3] || paramMatch[4] || paramMatch[5];
+ if(defaultValue !== undefined) {
+ paramInfo["default"] = defaultValue;
+ }
+ params.push(paramInfo);
+ // Look for the next parameter
+ paramMatch = reParam.exec(paramString);
+ }
+ return params;
+};
+
exports.parseMacroParameters = function(node,source,pos) {
// Process parameters
var parameter = $tw.utils.parseMacroParameter(source,pos);
@@ -175,7 +205,36 @@ exports.parseMacroParameter = function(source,pos) {
};
/*
-Look for a macro invocation. Returns null if not found, or {type: "macrocall", name:, parameters:, start:, end:}
+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;
+ }
+ return node;
+};
+
+/*
+Look for a macro invocation. Returns null if not found, or {type: "macrocall", name:, params:, start:, end:}
*/
exports.parseMacroInvocation = function(source,pos) {
var node = {
diff --git a/core/modules/parsers/pdfparser.js b/core/modules/parsers/pdfparser.js
index 19d4253d7..c7830bf69 100644
--- a/core/modules/parsers/pdfparser.js
+++ b/core/modules/parsers/pdfparser.js
@@ -25,6 +25,8 @@ var ImageParser = function(type,text,options) {
element.attributes.src = {type: "string", value: "data:application/pdf;base64," + text};
}
this.tree = [element];
+ this.source = text;
+ this.type = type;
};
exports["application/pdf"] = ImageParser;
diff --git a/core/modules/parsers/textparser.js b/core/modules/parsers/textparser.js
index 4f55f6f0c..06b08f30f 100644
--- a/core/modules/parsers/textparser.js
+++ b/core/modules/parsers/textparser.js
@@ -20,6 +20,8 @@ var TextParser = function(type,text,options) {
language: {type: "string", value: type}
}
}];
+ this.source = text;
+ this.type = type;
};
exports["text/plain"] = TextParser;
diff --git a/core/modules/parsers/videoparser.js b/core/modules/parsers/videoparser.js
index f1c281c7c..1c8a38bb2 100644
--- a/core/modules/parsers/videoparser.js
+++ b/core/modules/parsers/videoparser.js
@@ -28,6 +28,8 @@ var VideoParser = function(type,text,options) {
element.attributes.src = {type: "string", value: "data:" + type + ";base64," + text};
}
this.tree = [element];
+ this.source = text;
+ this.type = type;
};
exports["video/ogg"] = VideoParser;
diff --git a/core/modules/parsers/wikiparser/rules/fnprocdef.js b/core/modules/parsers/wikiparser/rules/fnprocdef.js
new file mode 100644
index 000000000..5d0a8878b
--- /dev/null
+++ b/core/modules/parsers/wikiparser/rules/fnprocdef.js
@@ -0,0 +1,97 @@
+/*\
+title: $:/core/modules/parsers/wikiparser/rules/fnprocdef.js
+type: application/javascript
+module-type: wikirule
+
+Wiki pragma rule for function, procedure and widget definitions
+
+```
+\function name(param:defaultvalue,param2:defaultvalue)
+definition text
+\end
+
+\procedure name(param:defaultvalue,param2:defaultvalue)
+definition text
+\end
+
+\widget $mywidget(param:defaultvalue,param2:defaultvalue)
+definition text
+\end
+```
+
+\*/
+(function(){
+
+/*jslint node: true, browser: true */
+/*global $tw: false */
+"use strict";
+
+exports.name = "fnprocdef";
+exports.types = {pragma: true};
+
+/*
+Instantiate parse rule
+*/
+exports.init = function(parser) {
+ this.parser = parser;
+ // Regexp to match
+ this.matchRegExp = /^\\(function|procedure|widget)\s+([^(\s]+)\((\s*([^)]*))?\)(\s*\r?\n)?/mg;
+};
+
+/*
+Parse the most recent match
+*/
+exports.parse = function() {
+ // Move past the macro name and parameters
+ this.parser.pos = this.matchRegExp.lastIndex;
+ // Parse the parameters
+ var params = [];
+ if(this.match[3]) {
+ params = $tw.utils.parseParameterDefinition(this.match[4]);
+ }
+ // Is this a multiline definition?
+ var reEnd;
+ if(this.match[5]) {
+ // If so, the end of the body is marked with \end
+ reEnd = new RegExp("(\\r?\\n\\\\end[^\\S\\n\\r]*(?:" + $tw.utils.escapeRegExp(this.match[2]) + ")?(?:$|\\r?\\n))","mg");
+ } else {
+ // Otherwise, the end of the definition is marked by the end of the line
+ reEnd = /($|\r?\n)/mg;
+ // Move past any whitespace
+ this.parser.pos = $tw.utils.skipWhiteSpace(this.parser.source,this.parser.pos);
+ }
+ // Find the end of the definition
+ reEnd.lastIndex = this.parser.pos;
+ var text,
+ endMatch = reEnd.exec(this.parser.source);
+ if(endMatch) {
+ text = this.parser.source.substring(this.parser.pos,endMatch.index);
+ this.parser.pos = endMatch.index + endMatch[0].length;
+ } else {
+ // We didn't find the end of the definition, so we'll make it blank
+ text = "";
+ }
+ // Save the macro definition
+ var parseTreeNodes = [{
+ type: "set",
+ attributes: {},
+ children: [],
+ params: params
+ }];
+ $tw.utils.addAttributeToParseTreeNode(parseTreeNodes[0],"name",this.match[2]);
+ $tw.utils.addAttributeToParseTreeNode(parseTreeNodes[0],"value",text);
+ if(this.match[1] === "function") {
+ parseTreeNodes[0].isFunctionDefinition = true;
+ } else if(this.match[1] === "procedure") {
+ parseTreeNodes[0].isProcedureDefinition = true;
+ } else if(this.match[1] === "widget") {
+ parseTreeNodes[0].isWidgetDefinition = true;
+ }
+ if(this.parser.configTrimWhiteSpace) {
+ parseTreeNodes[0].configTrimWhiteSpace = true;
+ }
+ return parseTreeNodes;
+};
+
+})();
+
\ No newline at end of file
diff --git a/core/modules/parsers/wikiparser/rules/html.js b/core/modules/parsers/wikiparser/rules/html.js
index 7fc4bb96e..64469e3b2 100644
--- a/core/modules/parsers/wikiparser/rules/html.js
+++ b/core/modules/parsers/wikiparser/rules/html.js
@@ -93,9 +93,6 @@ exports.parseTag = function(source,pos,options) {
return null;
}
node.tag = token.match[1];
- if(node.tag.slice(1).indexOf("$") !== -1) {
- return null;
- }
if(node.tag.charAt(0) === "$") {
node.type = node.tag.substr(1);
}
diff --git a/core/modules/parsers/wikiparser/rules/macrocallblock.js b/core/modules/parsers/wikiparser/rules/macrocallblock.js
index 6f50fdbb0..a2c10e04a 100644
--- a/core/modules/parsers/wikiparser/rules/macrocallblock.js
+++ b/core/modules/parsers/wikiparser/rules/macrocallblock.js
@@ -27,7 +27,7 @@ exports.findNextMatch = function(startPos) {
var nextStart = startPos;
// Try parsing at all possible macrocall openers until we match
while((nextStart = this.parser.source.indexOf("<<",nextStart)) >= 0) {
- var nextCall = $tw.utils.parseMacroInvocation(this.parser.source,nextStart);
+ var nextCall = $tw.utils.parseMacroInvocationAsTransclusion(this.parser.source,nextStart);
if(nextCall) {
var c = this.parser.source.charAt(nextCall.end);
// Ensure EOL after parsed macro
diff --git a/core/modules/parsers/wikiparser/rules/macrocallinline.js b/core/modules/parsers/wikiparser/rules/macrocallinline.js
index 165a70dce..e9f79f09e 100644
--- a/core/modules/parsers/wikiparser/rules/macrocallinline.js
+++ b/core/modules/parsers/wikiparser/rules/macrocallinline.js
@@ -27,7 +27,7 @@ exports.findNextMatch = function(startPos) {
var nextStart = startPos;
// Try parsing at all possible macrocall openers until we match
while((nextStart = this.parser.source.indexOf("<<",nextStart)) >= 0) {
- this.nextCall = $tw.utils.parseMacroInvocation(this.parser.source,nextStart);
+ this.nextCall = $tw.utils.parseMacroInvocationAsTransclusion(this.parser.source,nextStart);
if(this.nextCall) {
return nextStart;
}
diff --git a/core/modules/parsers/wikiparser/rules/macrodef.js b/core/modules/parsers/wikiparser/rules/macrodef.js
index 1efd3449a..74a94a385 100644
--- a/core/modules/parsers/wikiparser/rules/macrodef.js
+++ b/core/modules/parsers/wikiparser/rules/macrodef.js
@@ -77,16 +77,16 @@ exports.parse = function() {
text = "";
}
// Save the macro definition
- return [{
+ var parseTreeNodes = [{
type: "set",
- attributes: {
- name: {type: "string", value: this.match[1]},
- value: {type: "string", value: text}
- },
+ attributes: {},
children: [],
params: params,
isMacroDefinition: true
}];
+ $tw.utils.addAttributeToParseTreeNode(parseTreeNodes[0],"name",this.match[1]);
+ $tw.utils.addAttributeToParseTreeNode(parseTreeNodes[0],"value",text);
+ return parseTreeNodes;
};
})();
diff --git a/core/modules/parsers/wikiparser/rules/parameters.js b/core/modules/parsers/wikiparser/rules/parameters.js
new file mode 100644
index 000000000..561c1c545
--- /dev/null
+++ b/core/modules/parsers/wikiparser/rules/parameters.js
@@ -0,0 +1,60 @@
+/*\
+title: $:/core/modules/parsers/wikiparser/rules/parameters.js
+type: application/javascript
+module-type: wikirule
+
+Wiki pragma rule for parameter definitions
+
+```
+\parameters(param:defaultvalue,param2:defaultvalue)
+definition text
+```
+
+\*/
+(function(){
+
+/*jslint node: true, browser: true */
+/*global $tw: false */
+"use strict";
+
+exports.name = "parameters";
+exports.types = {pragma: true};
+
+/*
+Instantiate parse rule
+*/
+exports.init = function(parser) {
+ this.parser = parser;
+ // Regexp to match
+ this.matchRegExp = /^\\parameters\s*\(([^)]*)\)\s*\r?\n/mg;
+};
+
+/*
+Parse the most recent match
+*/
+exports.parse = function() {
+ // Move past the macro name and parameters
+ this.parser.pos = this.matchRegExp.lastIndex;
+ // Parse the parameters
+ var params = $tw.utils.parseParameterDefinition(this.match[1]);
+ var attributes = Object.create(null),
+ orderedAttributes = [];
+ $tw.utils.each(params,function(param) {
+ var name = param.name;
+ // Parameter names starting with dollar must be escaped to double dollars for the parameters widget
+ if(name.charAt(0) === "$") {
+ name = "$" + name;
+ }
+ var attribute = {name: name, type: "string", value: param["default"] || ""};
+ attributes[name] = attribute;
+ orderedAttributes.push(attribute);
+ });
+ // Save the macro definition
+ return [{
+ type: "parameters",
+ attributes: attributes,
+ orderedAttributes: orderedAttributes
+ }];
+};
+
+})();
diff --git a/core/modules/parsers/wikiparser/rules/transcludeblock.js b/core/modules/parsers/wikiparser/rules/transcludeblock.js
index 56a4f63b8..c033c2440 100644
--- a/core/modules/parsers/wikiparser/rules/transcludeblock.js
+++ b/core/modules/parsers/wikiparser/rules/transcludeblock.js
@@ -23,7 +23,7 @@ exports.types = {block: true};
exports.init = function(parser) {
this.parser = parser;
// Regexp to match
- this.matchRegExp = /\{\{([^\{\}\|]*)(?:\|\|([^\|\{\}]+))?\}\}(?:\r?\n|$)/mg;
+ this.matchRegExp = /\{\{([^\{\}\|]*)(?:\|\|([^\|\{\}]+))?(?:\|([^\{\}]+))?\}\}(?:\r?\n|$)/mg;
};
exports.parse = function() {
@@ -31,13 +31,22 @@ exports.parse = function() {
this.parser.pos = this.matchRegExp.lastIndex;
// Get the match details
var template = $tw.utils.trim(this.match[2]),
- textRef = $tw.utils.trim(this.match[1]);
+ textRef = $tw.utils.trim(this.match[1]),
+ params = this.match[3] ? this.match[3].split("|") : [];
// Prepare the transclude widget
var transcludeNode = {
type: "transclude",
attributes: {},
isBlock: true
};
+ $tw.utils.each(params,function(paramValue,index) {
+ var name = "" + index;
+ transcludeNode.attributes[name] = {
+ name: name,
+ type: "string",
+ value: paramValue
+ }
+ });
// Prepare the tiddler widget
var tr, targetTitle, targetField, targetIndex, tiddlerNode;
if(textRef) {
@@ -48,14 +57,14 @@ exports.parse = function() {
tiddlerNode = {
type: "tiddler",
attributes: {
- tiddler: {type: "string", value: targetTitle}
+ tiddler: {name: "tiddler", type: "string", value: targetTitle}
},
isBlock: true,
children: [transcludeNode]
};
}
if(template) {
- transcludeNode.attributes.tiddler = {type: "string", value: template};
+ transcludeNode.attributes["$tiddler"] = {name: "$tiddler", type: "string", value: template};
if(textRef) {
return [tiddlerNode];
} else {
@@ -63,12 +72,12 @@ exports.parse = function() {
}
} else {
if(textRef) {
- transcludeNode.attributes.tiddler = {type: "string", value: targetTitle};
+ transcludeNode.attributes["$tiddler"] = {name: "$tiddler", type: "string", value: targetTitle};
if(targetField) {
- transcludeNode.attributes.field = {type: "string", value: targetField};
+ transcludeNode.attributes["$field"] = {name: "$field", type: "string", value: targetField};
}
if(targetIndex) {
- transcludeNode.attributes.index = {type: "string", value: targetIndex};
+ transcludeNode.attributes["$index"] = {name: "$index", type: "string", value: targetIndex};
}
return [tiddlerNode];
} else {
diff --git a/core/modules/parsers/wikiparser/rules/transcludeinline.js b/core/modules/parsers/wikiparser/rules/transcludeinline.js
index dbf39bfb6..3ce9dc78e 100644
--- a/core/modules/parsers/wikiparser/rules/transcludeinline.js
+++ b/core/modules/parsers/wikiparser/rules/transcludeinline.js
@@ -23,7 +23,7 @@ exports.types = {inline: true};
exports.init = function(parser) {
this.parser = parser;
// Regexp to match
- this.matchRegExp = /\{\{([^\{\}\|]*)(?:\|\|([^\|\{\}]+))?\}\}/mg;
+ this.matchRegExp = /\{\{([^\{\}\|]*)(?:\|\|([^\|\{\}]+))?(?:\|([^\{\}]+))?\}\}/mg;
};
exports.parse = function() {
@@ -31,12 +31,21 @@ exports.parse = function() {
this.parser.pos = this.matchRegExp.lastIndex;
// Get the match details
var template = $tw.utils.trim(this.match[2]),
- textRef = $tw.utils.trim(this.match[1]);
+ textRef = $tw.utils.trim(this.match[1]),
+ params = this.match[3] ? this.match[3].split("|") : [];
// Prepare the transclude widget
var transcludeNode = {
type: "transclude",
attributes: {}
};
+ $tw.utils.each(params,function(paramValue,index) {
+ var name = "" + index;
+ transcludeNode.attributes[name] = {
+ name: name,
+ type: "string",
+ value: paramValue
+ }
+ });
// Prepare the tiddler widget
var tr, targetTitle, targetField, targetIndex, tiddlerNode;
if(textRef) {
@@ -47,13 +56,13 @@ exports.parse = function() {
tiddlerNode = {
type: "tiddler",
attributes: {
- tiddler: {type: "string", value: targetTitle}
+ tiddler: {name: "tiddler", type: "string", value: targetTitle}
},
children: [transcludeNode]
};
}
if(template) {
- transcludeNode.attributes.tiddler = {type: "string", value: template};
+ transcludeNode.attributes["$tiddler"] = {name: "$tiddler", type: "string", value: template};
if(textRef) {
return [tiddlerNode];
} else {
@@ -61,12 +70,12 @@ exports.parse = function() {
}
} else {
if(textRef) {
- transcludeNode.attributes.tiddler = {type: "string", value: targetTitle};
+ transcludeNode.attributes["$tiddler"] = {name: "$tiddler", type: "string", value: targetTitle};
if(targetField) {
- transcludeNode.attributes.field = {type: "string", value: targetField};
+ transcludeNode.attributes["$field"] = {name: "$field", type: "string", value: targetField};
}
if(targetIndex) {
- transcludeNode.attributes.index = {type: "string", value: targetIndex};
+ transcludeNode.attributes["$index"] = {name: "$index", type: "string", value: targetIndex};
}
return [tiddlerNode];
} else {
diff --git a/core/modules/parsers/wikiparser/wikiparser.js b/core/modules/parsers/wikiparser/wikiparser.js
index 4c7419030..bb457b205 100644
--- a/core/modules/parsers/wikiparser/wikiparser.js
+++ b/core/modules/parsers/wikiparser/wikiparser.js
@@ -32,6 +32,7 @@ options: see below:
parseAsInline: true to parse text as inline instead of block
wiki: reference to wiki to use
_canonical_uri: optional URI of content if text is missing or empty
+ configTrimWhiteSpace: true to trim whitespace
*/
var WikiParser = function(type,text,options) {
this.wiki = options.wiki;
@@ -46,7 +47,7 @@ var WikiParser = function(type,text,options) {
this.source = text || "";
this.sourceLength = this.source.length;
// Flag for ignoring whitespace
- this.configTrimWhiteSpace = false;
+ this.configTrimWhiteSpace = options.configTrimWhiteSpace !== undefined ? options.configTrimWhiteSpace : false;
// Parser mode
this.parseAsInline = options.parseAsInline;
// Set current parse position
diff --git a/core/modules/widgets/fill.js b/core/modules/widgets/fill.js
new file mode 100644
index 000000000..de88c95af
--- /dev/null
+++ b/core/modules/widgets/fill.js
@@ -0,0 +1,30 @@
+/*\
+title: $:/core/modules/widgets/fill.js
+type: application/javascript
+module-type: widget
+
+Sub-widget used by the transclude widget for specifying values for slots within transcluded content. It doesn't do anything by itself because the transclude widget only ever deals with the parse tree nodes, and doesn't instantiate the widget itself
+
+\*/
+(function(){
+
+/*jslint node: true, browser: true */
+/*global $tw: false */
+"use strict";
+
+var Widget = require("$:/core/modules/widgets/widget.js").widget;
+
+var FillWidget = function(parseTreeNode,options) {
+ // Initialise
+ this.initialise(parseTreeNode,options);
+};
+
+/*
+Inherit from the base widget class
+*/
+FillWidget.prototype = new Widget();
+
+exports.fill = FillWidget;
+
+})();
+
\ No newline at end of file
diff --git a/core/modules/widgets/importvariables.js b/core/modules/widgets/importvariables.js
index a73abfdcf..aafc8ba8b 100644
--- a/core/modules/widgets/importvariables.js
+++ b/core/modules/widgets/importvariables.js
@@ -52,38 +52,44 @@ ImportVariablesWidget.prototype.execute = function(tiddlerList) {
var parser = widgetPointer.wiki.parseTiddler(title,{parseAsInline:true});
if(parser) {
var parseTreeNode = parser.tree[0];
- while(parseTreeNode && parseTreeNode.type === "set") {
+ while(parseTreeNode && ["setvariable","set","parameters"].indexOf(parseTreeNode.type) !== -1) {
var node = {
type: "set",
attributes: parseTreeNode.attributes,
params: parseTreeNode.params,
- isMacroDefinition: parseTreeNode.isMacroDefinition
+ isMacroDefinition: parseTreeNode.isMacroDefinition,
+ isFunctionDefinition: parseTreeNode.isFunctionDefinition,
+ isProcedureDefinition: parseTreeNode.isProcedureDefinition,
+ isWidgetDefinition: parseTreeNode.isWidgetDefinition,
+ configTrimWhiteSpace: parseTreeNode.configTrimWhiteSpace
};
- if (parseTreeNode.isMacroDefinition) {
- // Macro definitions can be folded into
- // current widget instead of adding
- // another link to the chain.
- var widget = widgetPointer.makeChildWidget(node);
- widget.computeAttributes();
- widget.execute();
- // We SHALLOW copy over all variables
- // in widget. We can't use
- // $tw.utils.assign, because that copies
- // up the prototype chain, which we
- // don't want.
- $tw.utils.each(Object.keys(widget.variables), function(key) {
- widgetPointer.variables[key] = widget.variables[key];
- });
- } else {
- widgetPointer.children = [widgetPointer.makeChildWidget(node)];
- // No more regenerating children for
- // this widget. If it needs to refresh,
- // it'll do so along with the the whole
- // importvariable tree.
- if (widgetPointer != this) {
- widgetPointer.makeChildWidgets = function(){};
+ if(parseTreeNode.type === "set" || parseTreeNode.type === "setvariable") {
+ if(parseTreeNode.isMacroDefinition || parseTreeNode.isProcedureDefinition || parseTreeNode.isWidgetDefinition || parseTreeNode.isFunctionDefinition) {
+ // Macro definitions can be folded into
+ // current widget instead of adding
+ // another link to the chain.
+ var widget = widgetPointer.makeChildWidget(node);
+ widget.computeAttributes();
+ widget.execute();
+ // We SHALLOW copy over all variables
+ // in widget. We can't use
+ // $tw.utils.assign, because that copies
+ // up the prototype chain, which we
+ // don't want.
+ $tw.utils.each(Object.keys(widget.variables), function(key) {
+ widgetPointer.variables[key] = widget.variables[key];
+ });
+ } else {
+ widgetPointer.children = [widgetPointer.makeChildWidget(node)];
+ // No more regenerating children for
+ // this widget. If it needs to refresh,
+ // it'll do so along with the the whole
+ // importvariable tree.
+ if (widgetPointer != this) {
+ widgetPointer.makeChildWidgets = function(){};
+ }
+ widgetPointer = widgetPointer.children[0];
}
- widgetPointer = widgetPointer.children[0];
}
parseTreeNode = parseTreeNode.children && parseTreeNode.children[0];
}
diff --git a/core/modules/widgets/macrocall.js b/core/modules/widgets/macrocall.js
index 9de2e5d67..e49eadfe0 100644
--- a/core/modules/widgets/macrocall.js
+++ b/core/modules/widgets/macrocall.js
@@ -37,7 +37,7 @@ MacroCallWidget.prototype.render = function(parent,nextSibling) {
Compute the internal state of the widget
*/
MacroCallWidget.prototype.execute = function() {
- // Get the parse type if specified
+ this.macroName = this.parseTreeNode.name || this.getAttribute("$name"),
this.parseType = this.getAttribute("$type","text/vnd.tiddlywiki");
this.renderOutput = this.getAttribute("$output","text/html");
// Merge together the parameters specified in the parse tree with the specified attributes
@@ -47,49 +47,26 @@ MacroCallWidget.prototype.execute = function() {
params.push({name: name, value: attribute});
}
});
- // Get the macro value
- var macroName = this.parseTreeNode.name || this.getAttribute("$name"),
- variableInfo = this.getVariableInfo(macroName,{params: params}),
- text = variableInfo.text,
- parseTreeNodes;
- // Are we rendering to HTML?
- if(this.renderOutput === "text/html") {
- // If so we'll return the parsed macro
- // Check if we've already cached parsing this macro
- var mode = this.parseTreeNode.isBlock ? "blockParser" : "inlineParser",
- parser;
- if(variableInfo.srcVariable && variableInfo.srcVariable[mode]) {
- parser = variableInfo.srcVariable[mode];
- } else {
- parser = this.wiki.parseText(this.parseType,text,
- {parseAsInline: !this.parseTreeNode.isBlock});
- if(variableInfo.isCacheable && variableInfo.srcVariable) {
- variableInfo.srcVariable[mode] = parser;
- }
- }
- var parseTreeNodes = parser ? parser.tree : [];
- // Wrap the parse tree in a vars widget assigning the parameters to variables named "__paramname__"
- var attributes = {};
- $tw.utils.each(variableInfo.params,function(param) {
- var name = "__" + param.name + "__";
- attributes[name] = {
- name: name,
- type: "string",
- value: param.value
- };
- });
+ // Make a transclude widget
+ var positionalName = 0,
parseTreeNodes = [{
- type: "vars",
- attributes: attributes,
- children: parseTreeNodes
+ type: "transclude",
+ isBlock: this.parseTreeNode.isBlock
}];
- } else if(this.renderOutput === "text/raw") {
- parseTreeNodes = [{type: "text", text: text}];
- } else {
- // Otherwise, we'll render the text
- var plainText = this.wiki.renderText("text/plain",this.parseType,text,{parentWidget: this});
- parseTreeNodes = [{type: "text", text: plainText}];
- }
+ $tw.utils.addAttributeToParseTreeNode(parseTreeNodes[0],"$variable",this.macroName);
+ $tw.utils.addAttributeToParseTreeNode(parseTreeNodes[0],"$type",this.parseType);
+ $tw.utils.addAttributeToParseTreeNode(parseTreeNodes[0],"$output",this.renderOutput);
+ $tw.utils.each(params,function(param) {
+ var name = param.name;
+ if(name) {
+ if(name.charAt(0) === "$") {
+ name = "$" + name;
+ }
+ $tw.utils.addAttributeToParseTreeNode(parseTreeNodes[0],name,param.value);
+ } else {
+ $tw.utils.addAttributeToParseTreeNode(parseTreeNodes[0],(positionalName++) + "",param.value);
+ }
+ });
// Construct the child widgets
this.makeChildWidgets(parseTreeNodes);
};
diff --git a/core/modules/widgets/parameters.js b/core/modules/widgets/parameters.js
new file mode 100644
index 000000000..69194cb9e
--- /dev/null
+++ b/core/modules/widgets/parameters.js
@@ -0,0 +1,96 @@
+/*\
+title: $:/core/modules/widgets/parameters.js
+type: application/javascript
+module-type: widget
+
+Widget for definition of transclusion parameters
+
+\*/
+(function(){
+
+/*jslint node: true, browser: true */
+/*global $tw: false */
+"use strict";
+
+var Widget = require("$:/core/modules/widgets/widget.js").widget,
+ TranscludeWidget = require("$:/core/modules/widgets/transclude.js").transclude;
+
+var ParametersWidget = function(parseTreeNode,options) {
+ // Initialise
+ this.initialise(parseTreeNode,options);
+};
+
+/*
+Inherit from the base widget class
+*/
+ParametersWidget.prototype = new Widget();
+
+/*
+Render this widget into the DOM
+*/
+ParametersWidget.prototype.render = function(parent,nextSibling) {
+ // Call the constructor
+ Widget.call(this);
+ this.parentDomNode = parent;
+ this.computeAttributes();
+ this.execute();
+ this.renderChildren(parent,nextSibling);
+};
+
+/*
+Compute the internal state of the widget
+*/
+ParametersWidget.prototype.execute = function() {
+ var self = this;
+ this.parametersDepth = Math.max(parseInt(this.getAttribute("$depth","1"),10) || 1,1);
+ // Find the parent transclusions
+ var pointer = this.parentWidget,
+ depth = this.parametersDepth;
+ while(pointer) {
+ if(pointer instanceof TranscludeWidget) {
+ depth--;
+ if(depth <= 0) {
+ break;
+ }
+ }
+ pointer = pointer.parentWidget;
+ }
+ // Process each parameter
+ if(pointer instanceof TranscludeWidget) {
+ // Get the value for each defined parameter
+ $tw.utils.each($tw.utils.getOrderedAttributesFromParseTreeNode(self.parseTreeNode),function(attr,index) {
+ var name = attr.name;
+ // If the attribute name starts with $$ then reduce to a single dollar
+ if(name.substr(0,2) === "$$") {
+ name = name.substr(1);
+ }
+ var value = pointer.getTransclusionParameter(name,index,self.getAttribute(attr.name,""));
+ self.setVariable(name,value);
+ });
+ // Assign any metaparameters
+ $tw.utils.each(pointer.getTransclusionMetaParameters(),function(getValue,name) {
+ var variableName = self.getAttribute("$" + name);
+ if(variableName) {
+ self.setVariable(variableName,getValue(name));
+ }
+ });
+ }
+ // Construct the child widgets
+ this.makeChildWidgets();
+};
+
+/*
+Refresh the widget by ensuring our attributes are up to date
+*/
+ParametersWidget.prototype.refresh = function(changedTiddlers) {
+ var changedAttributes = this.computeAttributes();
+ if(Object.keys(changedAttributes).length) {
+ this.refreshSelf();
+ return true;
+ }
+ return this.refreshChildren(changedTiddlers);
+};
+
+exports.parameters = ParametersWidget;
+
+})();
diff --git a/core/modules/widgets/setvariable.js b/core/modules/widgets/setvariable.js
index cc97067c7..f8e98f390 100755
--- a/core/modules/widgets/setvariable.js
+++ b/core/modules/widgets/setvariable.js
@@ -48,7 +48,17 @@ SetWidget.prototype.execute = function() {
this.setValue = this.getAttribute("value");
this.setEmptyValue = this.getAttribute("emptyValue");
// Set context variable
- this.setVariable(this.setName,this.getValue(),this.parseTreeNode.params,!!this.parseTreeNode.isMacroDefinition);
+ if(this.parseTreeNode.isMacroDefinition) {
+ this.setVariable(this.setName,this.getValue(),this.parseTreeNode.params,true);
+ } else if(this.parseTreeNode.isFunctionDefinition) {
+ this.setVariable(this.setName,this.getValue(),this.parseTreeNode.params,undefined,{isFunctionDefinition: true});
+ } else if(this.parseTreeNode.isProcedureDefinition) {
+ this.setVariable(this.setName,this.getValue(),this.parseTreeNode.params,undefined,{isProcedureDefinition: true, configTrimWhiteSpace: this.parseTreeNode.configTrimWhiteSpace});
+ } else if(this.parseTreeNode.isWidgetDefinition) {
+ this.setVariable(this.setName,this.getValue(),this.parseTreeNode.params,undefined,{isWidgetDefinition: true, configTrimWhiteSpace: this.parseTreeNode.configTrimWhiteSpace});
+ } else {
+ this.setVariable(this.setName,this.getValue());
+ }
// Construct the child widgets
this.makeChildWidgets();
};
diff --git a/core/modules/widgets/slot.js b/core/modules/widgets/slot.js
new file mode 100644
index 000000000..6fc402ac2
--- /dev/null
+++ b/core/modules/widgets/slot.js
@@ -0,0 +1,82 @@
+/*\
+title: $:/core/modules/widgets/slot.js
+type: application/javascript
+module-type: widget
+
+Widget for definition of slots within transcluded content. The values provided by the translusion are passed to the slot.
+
+\*/
+(function(){
+
+/*jslint node: true, browser: true */
+/*global $tw: false */
+"use strict";
+
+var Widget = require("$:/core/modules/widgets/widget.js").widget,
+ TranscludeWidget = require("$:/core/modules/widgets/transclude.js").transclude;
+
+var SlotWidget = function(parseTreeNode,options) {
+ // Initialise
+ this.initialise(parseTreeNode,options);
+};
+
+/*
+Inherit from the base widget class
+*/
+SlotWidget.prototype = new Widget();
+
+/*
+Render this widget into the DOM
+*/
+SlotWidget.prototype.render = function(parent,nextSibling) {
+ // Call the constructor
+ Widget.call(this);
+ this.parentDomNode = parent;
+ this.computeAttributes();
+ this.execute();
+ this.renderChildren(parent,nextSibling);
+};
+
+/*
+Compute the internal state of the widget
+*/
+SlotWidget.prototype.execute = function() {
+ var self = this;
+ this.slotName = this.getAttribute("$name");
+ this.slotDepth = parseInt(this.getAttribute("$depth","1"),10) || 1;
+ // Find the parent transclusions
+ var pointer = this.parentWidget,
+ depth = this.slotDepth;
+ while(pointer) {
+ if(pointer instanceof TranscludeWidget) {
+ depth--;
+ if(depth <= 0) {
+ break;
+ }
+ }
+ pointer = pointer.parentWidget;
+ }
+ var parseTreeNodes = [{type: "text", attributes: {text: {type: "string", value: "Missing slot reference!"}}}];
+ if(pointer instanceof TranscludeWidget) {
+ // Get the parse tree nodes comprising the slot contents
+ parseTreeNodes = pointer.getTransclusionSlotFill(this.slotName,this.parseTreeNode.children);
+ }
+ // Construct the child widgets
+ this.makeChildWidgets(parseTreeNodes);
+};
+
+/*
+Refresh the widget by ensuring our attributes are up to date
+*/
+SlotWidget.prototype.refresh = function(changedTiddlers) {
+ var changedAttributes = this.computeAttributes();
+ if(changedAttributes["$name"] || changedAttributes["$depth"]) {
+ this.refreshSelf();
+ return true;
+ }
+ return this.refreshChildren(changedTiddlers);
+};
+
+exports.slot = SlotWidget;
+
+})();
diff --git a/core/modules/widgets/transclude.js b/core/modules/widgets/transclude.js
index d7862d2eb..1831f6b6d 100755
--- a/core/modules/widgets/transclude.js
+++ b/core/modules/widgets/transclude.js
@@ -37,46 +37,347 @@ TranscludeWidget.prototype.render = function(parent,nextSibling) {
Compute the internal state of the widget
*/
TranscludeWidget.prototype.execute = function() {
- // Get our parameters
- this.transcludeTitle = this.getAttribute("tiddler",this.getVariable("currentTiddler"));
- this.transcludeSubTiddler = this.getAttribute("subtiddler");
- this.transcludeField = this.getAttribute("field");
- this.transcludeIndex = this.getAttribute("index");
- this.transcludeMode = this.getAttribute("mode");
- this.recursionMarker = this.getAttribute("recursionMarker","yes");
- // Parse the text reference
+ // Get our attributes, string parameters, and slot values into properties of the widget object
+ this.collectAttributes();
+ this.collectStringParameters();
+ this.collectSlotFillParameters();
+ // Get the parse tree nodes that we are transcluding
+ var target = this.getTransclusionTarget(),
+ parseTreeNodes = target.parseTreeNodes;
+ this.sourceText = target.text;
+ this.sourceType = target.type;
+ this.parseAsInline = target.parseAsInline;
+ // Process the transclusion according to the output type
+ switch(this.transcludeOutput || "text/html") {
+ case "text/html":
+ // No further processing required
+ break;
+ case "text/raw":
+ // Just return the raw text
+ parseTreeNodes = [{type: "text", text: this.sourceText}];
+ break;
+ default:
+ // text/plain
+ var plainText = this.wiki.renderText("text/plain",this.sourceType,this.sourceText,{parentWidget: this});
+ parseTreeNodes = [{type: "text", text: plainText}];
+ break;
+ }
+ // Set the legacy transclusion context variables only if we're not transcluding a variable
+ if(!this.transcludeVariable) {
+ var recursionMarker = this.makeRecursionMarker();
+ this.setVariable("transclusion",recursionMarker);
+ }
+ // Construct the child widgets
+ this.makeChildWidgets(parseTreeNodes);
+};
+
+/*
+Collect the attributes we need, in the process determining whether we're being used in legacy mode
+*/
+TranscludeWidget.prototype.collectAttributes = function() {
+ var self = this;
+ // Detect legacy mode
+ this.legacyMode = true;
+ $tw.utils.each(this.attributes,function(value,name) {
+ if(name.charAt(0) === "$") {
+ self.legacyMode = false;
+ }
+ });
+ // Get the attributes for the appropriate mode
+ if(this.legacyMode) {
+ this.transcludeTitle = this.getAttribute("tiddler",this.getVariable("currentTiddler"));
+ this.transcludeSubTiddler = this.getAttribute("subtiddler");
+ this.transcludeField = this.getAttribute("field");
+ this.transcludeIndex = this.getAttribute("index");
+ this.transcludeMode = this.getAttribute("mode");
+ this.recursionMarker = this.getAttribute("recursionMarker","yes");
+ } else {
+ this.transcludeVariable = this.getAttribute("$variable");
+ this.transcludeType = this.getAttribute("$type");
+ this.transcludeOutput = this.getAttribute("$output","text/html");
+ this.transcludeTitle = this.getAttribute("$tiddler",this.getVariable("currentTiddler"));
+ this.transcludeSubTiddler = this.getAttribute("$subtiddler");
+ this.transcludeField = this.getAttribute("$field");
+ this.transcludeIndex = this.getAttribute("$index");
+ this.transcludeMode = this.getAttribute("$mode");
+ this.recursionMarker = this.getAttribute("$recursionMarker","yes");
+ }
+};
+
+/*
+Collect string parameters
+*/
+TranscludeWidget.prototype.collectStringParameters = function() {
+ var self = this;
+ this.stringParametersByName = Object.create(null);
+ if(!this.legacyMode) {
+ $tw.utils.each(this.attributes,function(value,name) {
+ if(name.charAt(0) === "$") {
+ if(name.charAt(1) === "$") {
+ // Attributes starting $$ represent parameters starting with a single $
+ name = name.slice(1);
+ } else {
+ // Attributes starting with a single $ are reserved for the widget
+ return;
+ }
+ }
+ self.stringParametersByName[name] = value;
+ });
+ }
+};
+
+/*
+Collect slot value parameters
+*/
+TranscludeWidget.prototype.collectSlotFillParameters = function() {
+ var self = this;
+ this.slotFillParseTrees = Object.create(null);
+ if(this.legacyMode) {
+ this.slotFillParseTrees["ts-missing"] = this.parseTreeNode.children;
+ } else {
+ this.slotFillParseTrees["ts-raw"] = this.parseTreeNode.children;
+ var noFillWidgetsFound = true,
+ searchParseTreeNodes = function(nodes) {
+ $tw.utils.each(nodes,function(node) {
+ if(node.type === "fill") {
+ if(node.attributes["$name"] && node.attributes["$name"].type === "string") {
+ var slotValueName = node.attributes["$name"].value;
+ self.slotFillParseTrees[slotValueName] = node.children || [];
+ }
+ noFillWidgetsFound = false;
+ } else {
+ searchParseTreeNodes(node.children);
+ }
+ });
+ };
+ searchParseTreeNodes(this.parseTreeNode.children);
+ if(noFillWidgetsFound) {
+ this.slotFillParseTrees["ts-missing"] = this.parseTreeNode.children;
+ }
+ }
+};
+
+/*
+Get transcluded parse tree nodes as an object {parser:,text:,type:}
+*/
+TranscludeWidget.prototype.getTransclusionTarget = function() {
+ var self = this;
+ // Determine whether we're being used in inline or block mode
var parseAsInline = !this.parseTreeNode.isBlock;
if(this.transcludeMode === "inline") {
parseAsInline = true;
} else if(this.transcludeMode === "block") {
parseAsInline = false;
}
- var parser = this.wiki.parseTextReference(
+ var parser;
+ // Get the parse tree
+ if(this.transcludeVariable) {
+ // Transcluding a variable
+ var variableInfo = this.getVariableInfo(this.transcludeVariable,{params: this.getOrderedTransclusionParameters()}),
+ srcVariable = variableInfo && variableInfo.srcVariable;
+ if(srcVariable) {
+ if(srcVariable.isFunctionDefinition) {
+ // Function to return parameters by name or position
+ var fnGetParam = function(name,index) {
+ // Parameter names starting with dollar must be escaped to double dollars
+ if(name.charAt(0) === "$") {
+ name = "$" + name;
+ }
+ // Look for the parameter by name
+ if(self.hasAttribute(name)) {
+ return self.getAttribute(name);
+ // Look for the parameter by index
+ } else if(self.hasAttribute(index + "")) {
+ return self.getAttribute(index + "");
+ } else {
+ return undefined;
+ }
+ },
+ result = this.evaluateVariable(this.transcludeVariable,{params: fnGetParam})[0] || "";
+ parser = {
+ tree: [{
+ type: "text",
+ text: result
+ }],
+ source: result,
+ type: "text/vnd.tiddlywiki"
+ };
+ if(parseAsInline) {
+ parser.tree[0] = {
+ type: "text",
+ text: result
+ };
+ } else {
+ parser.tree[0] = {
+ type: "element",
+ tag: "p",
+ children: [{
+ type: "text",
+ text: result
+ }]
+ }
+ }
+ } else {
+ var cacheKey = (parseAsInline ? "inlineParser" : "blockParser") + (this.transcludeType || "");
+ if(variableInfo.isCacheable && srcVariable[cacheKey]) {
+ parser = srcVariable[cacheKey];
+ } else {
+ parser = this.wiki.parseText(this.transcludeType,variableInfo.text || "",{parseAsInline: parseAsInline, configTrimWhiteSpace: srcVariable.configTrimWhiteSpace});
+ if(variableInfo.isCacheable) {
+ srcVariable[cacheKey] = parser;
+ }
+ }
+ }
+ if(parser) {
+ // Add parameters widget for procedures and custom widgets
+ if(srcVariable.isProcedureDefinition || srcVariable.isWidgetDefinition) {
+ parser = {
+ tree: [
+ {
+ type: "parameters",
+ children: parser.tree
+ }
+ ],
+ source: parser.source,
+ type: parser.type
+ }
+ $tw.utils.each(srcVariable.params,function(param) {
+ var name = param.name;
+ // Parameter names starting with dollar must be escaped to double dollars
+ if(name.charAt(0) === "$") {
+ name = "$" + name;
+ }
+ $tw.utils.addAttributeToParseTreeNode(parser.tree[0],name,param["default"])
+ });
+ } else {
+ // For macros and ordinary variables, wrap the parse tree in a vars widget assigning the parameters to variables named "__paramname__"
+ parser = {
+ tree: [
+ {
+ type: "vars",
+ children: parser.tree
+ }
+ ],
+ source: parser.source,
+ type: parser.type
+ }
+ $tw.utils.each(variableInfo.params,function(param) {
+ $tw.utils.addAttributeToParseTreeNode(parser.tree[0],"__" + param.name + "__",param.value)
+ });
+ }
+ }
+ }
+ } else {
+ // Transcluding a text reference
+ parser = this.wiki.parseTextReference(
this.transcludeTitle,
this.transcludeField,
this.transcludeIndex,
{
parseAsInline: parseAsInline,
- subTiddler: this.transcludeSubTiddler
- }),
- parseTreeNodes = parser ? parser.tree : this.parseTreeNode.children;
- this.sourceText = parser ? parser.source : null;
- this.parserType = parser? parser.type : null;
- // Set context variables for recursion detection
- var recursionMarker = this.makeRecursionMarker();
- if(this.recursionMarker === "yes") {
- this.setVariable("transclusion",recursionMarker);
+ subTiddler: this.transcludeSubTiddler,
+ defaultType: this.transcludeType
+ });
}
- // Check for recursion
+ // Return the parse tree
if(parser) {
- if(this.parentWidget && this.parentWidget.hasVariable("transclusion",recursionMarker)) {
- parseTreeNodes = [{type: "error", attributes: {
- "$message": {type: "string", value: $tw.language.getString("Error/RecursiveTransclusion")}
- }}];
+ return {
+ parser: parser,
+ parseTreeNodes: parser.tree,
+ parseAsInline: parseAsInline,
+ text: parser.source,
+ type: parser.type
+ };
+ } else {
+ // If there's no parse tree then return the missing slot value
+ return {
+ parser: null,
+ parseTreeNodes: (this.slotFillParseTrees["ts-missing"] || []),
+ parseAsInline: parseAsInline,
+ text: null,
+ type: null
+ };
+ }
+};
+
+/*
+Fetch all the string parameters as an ordered array of {name:, value:} where the name is optional
+*/
+TranscludeWidget.prototype.getOrderedTransclusionParameters = function() {
+ var result = [];
+ // Collect the parameters
+ for(var name in this.stringParametersByName) {
+ var value = this.stringParametersByName[name];
+ result.push({name: name, value: value});
+ }
+ // Sort numerical parameter names first
+ result.sort(function(a,b) {
+ var aIsNumeric = !isNaN(a.name),
+ bIsNumeric = !isNaN(b.name);
+ if(aIsNumeric && bIsNumeric) {
+ return a.name - b.name;
+ } else if(aIsNumeric) {
+ return -1;
+ } else if(bIsNumeric) {
+ return 1;
+ } else {
+ return a.name === b.name ? 0 : (a.name < b.name ? -1 : 1);
+ }
+ });
+ // Remove names from numerical parameters
+ $tw.utils.each(result,function(param,index) {
+ if(!isNaN(param.name)) {
+ delete param.name;
+ }
+ });
+ return result;
+};
+
+/*
+Fetch the value of a parameter
+*/
+TranscludeWidget.prototype.getTransclusionParameter = function(name,index,defaultValue) {
+ if(name in this.stringParametersByName) {
+ return this.stringParametersByName[name];
+ } else {
+ var name = "" + index;
+ if(name in this.stringParametersByName) {
+ return this.stringParametersByName[name];
}
}
- // Construct the child widgets
- this.makeChildWidgets(parseTreeNodes);
+ return defaultValue;
+};
+
+/*
+Get one of the special parameters to be provided by the parameters widget
+*/
+TranscludeWidget.prototype.getTransclusionMetaParameters = function() {
+ var self = this;
+ return {
+ "parseMode": function() {
+ return self.parseAsInline ? "inline" : "block";
+ },
+ "parseTreeNodes": function() {
+ return JSON.stringify(self.parseTreeNode.children || []);
+ },
+ "slotFillParseTreeNodes": function() {
+ return JSON.stringify(self.slotFillParseTrees);
+ },
+ "params": function() {
+ return JSON.stringify(self.stringParametersByName);
+ }
+ };
+};
+
+/*
+Fetch the value of a slot
+*/
+TranscludeWidget.prototype.getTransclusionSlotFill = function(name,defaultParseTreeNodes) {
+ if(name && this.slotFillParseTrees[name] && this.slotFillParseTrees[name].length > 0) {
+ return this.slotFillParseTrees[name];
+ } else {
+ return defaultParseTreeNodes || [];
+ }
};
/*
@@ -99,6 +400,7 @@ TranscludeWidget.prototype.makeRecursionMarker = function() {
};
TranscludeWidget.prototype.parserNeedsRefresh = function() {
+ // Doesn't need to consider transcluded variables because a parent variable can't change once a widget has been created
var parserInfo = this.wiki.getTextReferenceParserInfo(this.transcludeTitle,this.transcludeField,this.transcludeIndex,{subTiddler:this.transcludeSubTiddler});
return (this.sourceText === undefined || parserInfo.sourceText !== this.sourceText || parserInfo.parserType !== this.parserType)
};
@@ -108,7 +410,7 @@ Selectively refreshes the widget if needed. Returns true if the widget or any of
*/
TranscludeWidget.prototype.refresh = function(changedTiddlers) {
var changedAttributes = this.computeAttributes();
- if(($tw.utils.count(changedAttributes) > 0) || (changedTiddlers[this.transcludeTitle] && this.parserNeedsRefresh())) {
+ if(($tw.utils.count(changedAttributes) > 0) || (!this.transcludeVariable && changedTiddlers[this.transcludeTitle] && this.parserNeedsRefresh())) {
this.refreshSelf();
return true;
} else {
diff --git a/core/modules/widgets/widget.js b/core/modules/widgets/widget.js
index 60f55e8bb..741914fdc 100755
--- a/core/modules/widgets/widget.js
+++ b/core/modules/widgets/widget.js
@@ -41,10 +41,7 @@ Widget.prototype.initialise = function(parseTreeNode,options) {
this.parseTreeNode = parseTreeNode;
this.wiki = options.wiki;
this.parentWidget = options.parentWidget;
- this.variables = Object.create(null);
- if(this.parentWidget) {
- Object.setPrototypeOf(this.variables,this.parentWidget.variables);
- }
+ this.variables = Object.create(this.parentWidget ? this.parentWidget.variables : null);
this.document = options.document;
this.attributes = {};
this.children = [];
@@ -92,9 +89,22 @@ name: name of the variable
value: value of the variable
params: array of {name:, default:} for each parameter
isMacroDefinition: true if the variable is set via a \define macro pragma (and hence should have variable substitution performed)
+options includes:
+ isProcedureDefinition: true if the variable is set via a \procedure pragma (and hence should not have variable substitution performed)
+ isFunctionDefinition: true if the variable is set via a \function pragma (and hence should not have variable substitution performed)
+ isWidgetDefinition: true if the variable is set via a \widget pragma (and hence should not have variable substitution performed)
*/
-Widget.prototype.setVariable = function(name,value,params,isMacroDefinition) {
- this.variables[name] = {value: value, params: params, isMacroDefinition: !!isMacroDefinition};
+Widget.prototype.setVariable = function(name,value,params,isMacroDefinition,options) {
+ options = options || {};
+ this.variables[name] = {
+ value: value,
+ params: params,
+ isMacroDefinition: !!isMacroDefinition,
+ isFunctionDefinition: !!options.isFunctionDefinition,
+ isProcedureDefinition: !!options.isProcedureDefinition,
+ isWidgetDefinition: !!options.isWidgetDefinition,
+ configTrimWhiteSpace: !!options.configTrimWhiteSpace
+ };
};
/*
@@ -104,6 +114,7 @@ options: see below
Options include
params: array of {name:, value:} for each parameter
defaultValue: default value if the variable is not defined
+allowSelfAssigned: if true, includes the current widget in the context chain instead of just the parent
Returns an object with the following fields:
@@ -112,21 +123,27 @@ text: text of variable, with parameters properly substituted
*/
Widget.prototype.getVariableInfo = function(name,options) {
options = options || {};
- var actualParams = options.params || [],
- parentWidget = this.parentWidget;
+ var self = this,
+ actualParams = options.params || [],
+ variable;
+ if(options.allowSelfAssigned) {
+ variable = this.variables[name];
+ } else {
+ variable = this.parentWidget && this.parentWidget.variables[name];
+ }
// Check for the variable defined in the parent widget (or an ancestor in the prototype chain)
- if(parentWidget && name in parentWidget.variables) {
- var variable = parentWidget.variables[name],
- originalValue = variable.value,
+ if(variable) {
+ var originalValue = variable.value,
value = originalValue,
- params = this.resolveVariableParameters(variable.params,actualParams);
- // Substitute any parameters specified in the definition
- $tw.utils.each(params,function(param) {
- value = $tw.utils.replaceString(value,new RegExp("\\$" + $tw.utils.escapeRegExp(param.name) + "\\$","mg"),param.value);
- });
- // Only substitute variable references if this variable was defined with the \define pragma
+ params = [];
+ // Only substitute parameter and variable references if this variable was defined with the \define pragma
if(variable.isMacroDefinition) {
- value = this.substituteVariableReferences(value,options);
+ params = self.resolveVariableParameters(variable.params,actualParams);
+ // Substitute any parameters specified in the definition
+ $tw.utils.each(params,function(param) {
+ value = $tw.utils.replaceString(value,new RegExp("\\$" + $tw.utils.escapeRegExp(param.name) + "\\$","mg"),param.value);
+ });
+ value = self.substituteVariableReferences(value,options);
}
return {
text: value,
@@ -136,8 +153,13 @@ Widget.prototype.getVariableInfo = function(name,options) {
};
}
// If the variable doesn't exist in the parent widget then look for a macro module
+ var text = this.evaluateMacroModule(name,actualParams);
+ if(text === undefined) {
+ text = options.defaultValue;
+ }
return {
- text: this.evaluateMacroModule(name,actualParams,options.defaultValue)
+ text: text,
+ srcVariable: {}
};
};
@@ -148,6 +170,11 @@ Widget.prototype.getVariable = function(name,options) {
return this.getVariableInfo(name,options).text;
};
+/*
+Maps actual parameters onto formal parameters, returning an array of {name:,value:} objects
+formalParams - Array of {name:,default:} (default value is optional)
+actualParams - Array of string values or {name:,value:} (name is optional)
+*/
Widget.prototype.resolveVariableParameters = function(formalParams,actualParams) {
formalParams = formalParams || [];
actualParams = actualParams || [];
@@ -160,7 +187,7 @@ Widget.prototype.resolveVariableParameters = function(formalParams,actualParams)
paramInfo = formalParams[p];
paramValue = undefined;
for(var m=0; m 0) {
+ var letVariableWidget = {
+ type: "let",
attributes: {
- name: {type: "string", value: name},
- value: {type: "string", value: value}
},
children: []
};
- currWidgetNode.children = [setVariableWidget];
- currWidgetNode = setVariableWidget;
- });
+ $tw.utils.each(options.variables,function(value,name) {
+ $tw.utils.addAttributeToParseTreeNode(letVariableWidget,name,"" + value);
+ });
+ currWidgetNode.children = [letVariableWidget];
+ currWidgetNode = letVariableWidget;
+ }
// Add in the supplied parse tree nodes
currWidgetNode.children = parser ? parser.tree : [];
// Create the widget
diff --git a/core/ui/Components/VisibleTransclude.tid b/core/ui/Components/VisibleTransclude.tid
new file mode 100644
index 000000000..cbc981abe
--- /dev/null
+++ b/core/ui/Components/VisibleTransclude.tid
@@ -0,0 +1,48 @@
+title: $:/core/ui/VisibleTransclude
+
+
+\widget $transclude()
+
+<$parameters tiddler="" $$tiddler="" mode="" $$mode="" $parseMode="@parseMode" $params="@params">
+
+ <$let
+ mode={{{ [[$mode]is[variable]then<$mode>!is[blank]] :else[[mode]is[variable]then!is[blank]] :else[<@parseMode>] }}}
+ outputTag={{{ [match[inline]then[span]else[div]] }}}
+ outputColour={{{ [match[inline]then[green]else[red]] }}}
+ >
+
+ <$genesis $type=<> style="color:white;padding:4px;" style.background=<>>
+ <$genesis $type=<> style="display: inline-block;">
+
+
+ <$list filter="[<@params>jsonindexes[]]" emptyMessage="(none)">
+
+ <$text text=<>/><$text text=": "/><$text text={{{ [<@params>jsonget] }}}/>
+
+ $list>
+
+ $genesis>
+ <$genesis $type=<> style="background:white;color:black;padding:4px;">
+
+ <$list filter="[<@params>jsonindexes[]] :filter[prefix[$]] +[limit[1]]" variable="ignore" emptyMessage="""
+
+ <$genesis $type="$transclude" $remappable="no" $names="[<@params>jsonindexes[]]" $values="[<@params>jsonindexes[]] :map[<@params>jsonget]" recursionMarker="no" mode=<>>
+
+ <$slot $name="ts-raw" $depth="2"/>
+ $genesis>
+ """>
+
+ <$genesis $type="$transclude" $remappable="no" $names="[<@params>jsonindexes[]]" $values="[<@params>jsonindexes[]] :map[<@params>jsonget]" $$recursionMarker="no" $$mode=<>>
+
+ <$slot $name="ts-raw" $depth="2"/>
+ $genesis>
+ $list>
+ $genesis>
+ $genesis>
+ $let>
+$parameters>
+\end
diff --git a/core/wiki/macros/tabs.tid b/core/wiki/macros/tabs.tid
index f439e541d..bc8a0255f 100644
--- a/core/wiki/macros/tabs.tid
+++ b/core/wiki/macros/tabs.tid
@@ -60,4 +60,4 @@ code-body: yes
$let>
$qualify>
-\end
+\end
\ No newline at end of file
diff --git a/editions/prerelease/tiddlers/Release 5.2.8.tid b/editions/prerelease/tiddlers/Release 5.2.8.tid
deleted file mode 100644
index 18ca202b5..000000000
--- a/editions/prerelease/tiddlers/Release 5.2.8.tid
+++ /dev/null
@@ -1,60 +0,0 @@
-caption: 5.2.8
-created: 20230326093239710
-modified: 20230326093239710
-tags: ReleaseNotes
-title: Release 5.2.8
-type: text/vnd.tiddlywiki
-
-//[[See GitHub for detailed change history of this release|https://github.com/Jermolene/TiddlyWiki5/compare/v5.2.7...master]]//
-
-! Major Improvements
-
-! Translation Improvements
-
-Improvements to the following translations:
-
-*
-
-! Plugin Improvements
-
-*
-
-! Accessibility Improvements
-
-*
-
-! Usability Improvements
-
-*
-
-! Widget Improvements
-
-*
-
-! Filter improvements
-
-*
-
-! Hackability Improvements
-
-*
-
-! Bug Fixes
-
-*
-
-! Node.js Improvements
-
-*
-
-! Performance Improvements
-
-*
-
-! Acknowledgements
-
-[[@Jermolene|https://github.com/Jermolene]] would like to thank the contributors to this release who have generously given their time to help improve TiddlyWiki:
-
-<<.contributors """
-
-""">>
\ No newline at end of file
diff --git a/editions/prerelease/tiddlers/Release 5.3.0.tid b/editions/prerelease/tiddlers/Release 5.3.0.tid
new file mode 100644
index 000000000..63a57cd4d
--- /dev/null
+++ b/editions/prerelease/tiddlers/Release 5.3.0.tid
@@ -0,0 +1,83 @@
+caption: 5.3.0
+created: 20230419103154368
+modified: 20230419103154368
+tags: ReleaseNotes
+title: Release 5.3.0
+type: text/vnd.tiddlywiki
+
+//[[See GitHub for detailed change history of this release|https://github.com/Jermolene/TiddlyWiki5/compare/master...parameterised-transclusions]]//
+
+! About v5.3.0
+
+This pre-release introduces a number of significant improvements and new features related to some of TiddlyWiki's most fundamental components: macros, widgets, operators and transclusion.
+
+! Introduction to v5.3.0
+
+The motivation of these changes is to fix one of ~TiddlyWiki 5's early design flaws: the reliance on macros using textual substitution as the primary way to modularise and reuse wikitext and filters.
+
+Experience has shown that while macros are a good match for a small number of tasks, they are brittle and error prone for many common operations. See [[Macro Pitfalls]] for a discussion of the problems that accompany this approach. Over the years we have introduced mitigations for the worst problems but these have come at a cost of increased complexity.
+
+The changes in this release provide powerful new ways to achieve common tasks, and unlock completely new capabilities that were previously impossible in wikitext.
+
+* [[Procedures]], which are essentially what macros should have been; they work in exactly the same way except that parameters are exposed as simple variables (without the double underscores) and no textual substitution takes place
+* [[Custom Widgets]], allowing the creation of widgets in wikitext, and the redefinition of built-in widgets
+* [[Functions]], a new way to encapsulate filter expressions with named parameters, including the ability to make custom filter operators
+* Parameterised [[Transclusions|Transclusion]], allowing strings and wikitext trees to be passed to transclusions
+
+The approach taken by this release is to add new functionality by extending and augmenting the system without disturbing existing functionality. All of these changes are thus intended to be backwards compatible. While they represent a new field of opportunities for wikitext authors, it is possible for authors to ignore all these new features and continue to use ~TiddlyWiki 5 in the way that they have always done.
+
+These changes lay the groundwork for macros and related features to be deprecated (which is the point at which users are advised not to use old features, and instead given clear pointers to the equivalent modern functionality).
+
+The new transclusion architecture is not by itself sufficient to enable us to fully deprecate macros yet. To handle the remaining use cases we propose a new backtick quoted attribute format that allows for the substitution of variable values. See https://github.com/Jermolene/TiddlyWiki5/issues/6663 for details.
+
+! Plugin Improvements
+
+*
+
+! Translation improvement
+
+Improvements to the following translations:
+
+*
+
+! Accessibility Improvements
+
+*
+
+! Usability Improvements
+
+*
+
+! Widget Improvements
+
+*
+
+! Filter improvements
+
+*
+
+! Hackability Improvements
+
+*
+
+! Bug Fixes
+
+*
+
+! Developer Improvements
+
+*
+
+! Node.js Improvements
+
+*
+
+! Performance Improvements
+
+*
+! Acknowledgements
+
+[[@Jermolene|https://github.com/Jermolene]] would like to thank the contributors to this release who have generously given their time to help improve TiddlyWiki:
+
+<<.contributors """
+""">>
diff --git a/editions/test/tiddlers/tests/data/custom-operators/NestedParameterised.tid b/editions/test/tiddlers/tests/data/custom-operators/NestedParameterised.tid
new file mode 100644
index 000000000..3e4d610d0
--- /dev/null
+++ b/editions/test/tiddlers/tests/data/custom-operators/NestedParameterised.tid
@@ -0,0 +1,24 @@
+title: CustomOperators/NestedParameterised
+description: Nested parameterised custom operator usage
+type: text/vnd.tiddlywiki-multiple
+tags: [[$:/tags/wiki-test-spec]]
+
+title: Output
+
+\whitespace trim
+\function .dividebysomething(first:ignored,factor:0.5)
+[divide[2]multiply]
+\end
+
+\function .multiplebysomething(first:ignored,factor:2)
+[multiply[2].dividebysomething[],]
+\end
+
+<$text text={{{ [[123].multiplebysomething[]] }}}/>
+-
+<$text text={{{ [[123].multiplebysomething[x],[4]] }}}/>
+
++
+title: ExpectedResult
+
+246-492
\ No newline at end of file
diff --git a/editions/test/tiddlers/tests/data/custom-operators/Parameterised.tid b/editions/test/tiddlers/tests/data/custom-operators/Parameterised.tid
new file mode 100644
index 000000000..2f8337b0f
--- /dev/null
+++ b/editions/test/tiddlers/tests/data/custom-operators/Parameterised.tid
@@ -0,0 +1,24 @@
+title: CustomOperators/Parameterised
+description: Parameterised custom operator usage
+type: text/vnd.tiddlywiki-multiple
+tags: [[$:/tags/wiki-test-spec]]
+
+title: Output
+
+\whitespace trim
+\function .multiplybysomething(first:ignored,factor:2)
+[multiply[2]multiply]
+\end
+
+<$text text={{{ [[123].multiplybysomething[]] }}}/>
+-
+<$text text={{{ [[123].multiplybysomething[x],[4]] }}}/>
+|
+<$text text={{{ [[123]function[.multiplybysomething]] }}}/>
+-
+<$text text={{{ [[123]function[.multiplybysomething],[x],[4]] }}}/>
+
++
+title: ExpectedResult
+
+492-984|492-984
\ No newline at end of file
diff --git a/editions/test/tiddlers/tests/data/custom-operators/Simple.tid b/editions/test/tiddlers/tests/data/custom-operators/Simple.tid
new file mode 100644
index 000000000..089701295
--- /dev/null
+++ b/editions/test/tiddlers/tests/data/custom-operators/Simple.tid
@@ -0,0 +1,21 @@
+title: CustomOperators/Simple
+description: Simple custom operator usage
+type: text/vnd.tiddlywiki-multiple
+tags: [[$:/tags/wiki-test-spec]]
+
+title: Output
+
+\whitespace trim
+
+\function .multiplybytwo()
+[multiply[2]]
+\end
+
+<$text text={{{ [[123].multiplybytwo[]] }}}/>
+|
+<$text text={{{ [[123]function[.multiplybytwo]] }}}/>
+
++
+title: ExpectedResult
+
+246|246
\ No newline at end of file
diff --git a/editions/test/tiddlers/tests/data/functions/FunctionAttributes.tid b/editions/test/tiddlers/tests/data/functions/FunctionAttributes.tid
new file mode 100644
index 000000000..2deb49bdc
--- /dev/null
+++ b/editions/test/tiddlers/tests/data/functions/FunctionAttributes.tid
@@ -0,0 +1,24 @@
+title: Functions/FunctionAttributes
+description: Attributes specified as function invocations
+type: text/vnd.tiddlywiki-multiple
+tags: [[$:/tags/wiki-test-spec]]
+
+title: Output
+
+\whitespace trim
+\function .dividebysomething(factor:0.5)
+[divide]
+\end
+
+\function multiplebysomething(first:ignored,factor:2)
+[multiply[2].dividebysomething[0.25]]
+\end
+
+<$text text=<>/>
+|
+<$text text=<>/>
+
++
+title: ExpectedResult
+
+16|32
\ No newline at end of file
diff --git a/editions/test/tiddlers/tests/data/functions/FunctionOperator.tid b/editions/test/tiddlers/tests/data/functions/FunctionOperator.tid
new file mode 100644
index 000000000..e2a0038dc
--- /dev/null
+++ b/editions/test/tiddlers/tests/data/functions/FunctionOperator.tid
@@ -0,0 +1,24 @@
+title: Functions/FunctionOperator
+description: Calling a function via the function operator
+type: text/vnd.tiddlywiki-multiple
+tags: [[$:/tags/wiki-test-spec]]
+
+title: Output
+
+\whitespace trim
+\function .dividebysomething(factor:0.5)
+[divide]
+\end
+
+\function multiplebysomething(first:ignored,factor:2)
+[multiplymultiply[2].dividebysomething[0.25]]
+\end
+
+<$text text={{{ [[4]function[multiplebysomething]] }}}/>
+|
+<$text text={{{ [[6]function[multiplebysomething],[ignored],[4]] }}}/>
+
++
+title: ExpectedResult
+
+64|192
\ No newline at end of file
diff --git a/editions/test/tiddlers/tests/data/functions/MissingFunction.tid b/editions/test/tiddlers/tests/data/functions/MissingFunction.tid
new file mode 100644
index 000000000..25498e452
--- /dev/null
+++ b/editions/test/tiddlers/tests/data/functions/MissingFunction.tid
@@ -0,0 +1,15 @@
+title: Functions/MissingFunction
+description: Calling a missing function via the function operator
+type: text/vnd.tiddlywiki-multiple
+tags: [[$:/tags/wiki-test-spec]]
+
+title: Output
+
+\whitespace trim
+
+<$text text={{{ [[23]function[missing]] }}}/>
+
++
+title: ExpectedResult
+
+23
\ No newline at end of file
diff --git a/editions/test/tiddlers/tests/data/functions/RunawayRecursiveFunctions.tid b/editions/test/tiddlers/tests/data/functions/RunawayRecursiveFunctions.tid
new file mode 100644
index 000000000..81be22f16
--- /dev/null
+++ b/editions/test/tiddlers/tests/data/functions/RunawayRecursiveFunctions.tid
@@ -0,0 +1,18 @@
+title: Functions/RunawayRecursiveFunctions
+description: Runaway recursive functions
+type: text/vnd.tiddlywiki-multiple
+tags: [[$:/tags/wiki-test-spec]]
+
+title: Output
+
+\whitespace trim
+\function .buffalo(p)
+[.buffalo]
+\end
+
+<$text text=<<.buffalo 8>>/>
+
++
+title: ExpectedResult
+
+/**-- Excessive filter recursion --**/
\ No newline at end of file
diff --git a/editions/test/tiddlers/tests/data/functions/UndefinedParameters.tid b/editions/test/tiddlers/tests/data/functions/UndefinedParameters.tid
new file mode 100644
index 000000000..8a2b0a91a
--- /dev/null
+++ b/editions/test/tiddlers/tests/data/functions/UndefinedParameters.tid
@@ -0,0 +1,22 @@
+title: Functions/UndefinedParameters
+description: Undefined function parameters
+type: text/vnd.tiddlywiki-multiple
+tags: [[$:/tags/wiki-test-spec]]
+
+title: Output
+
+\function greet(who)
+[[hello ]addsuffix]
+\end
+
+<$text text={{{[function[greet],[world]]}}}/>
+
+<>
+
+<$text text={{{[function[greet]]}}}/>
+
+<>
++
+title: ExpectedResult
+
+hello worldhello world
hello hello
\ No newline at end of file
diff --git a/editions/test/tiddlers/tests/data/functions/WikifiedFunctions.tid b/editions/test/tiddlers/tests/data/functions/WikifiedFunctions.tid
new file mode 100644
index 000000000..733fbdaef
--- /dev/null
+++ b/editions/test/tiddlers/tests/data/functions/WikifiedFunctions.tid
@@ -0,0 +1,36 @@
+title: Functions/WikifiedFunctions
+description: Wikified functions
+type: text/vnd.tiddlywiki-multiple
+tags: [[$:/tags/wiki-test-spec]]
+
+title: Output
+
+\whitespace trim
+\function fn-buffalo(param)
+[addsuffix[ with a ''buffalo'']]
+\end
+
+\procedure proc-buffalo(param)
+<> with a ''buffalo''
+\end
+
+\define macro-buffalo(param)
+$param$ with a ''buffalo''
+\end
+
+<>
+
+<>
+
+<>
+
+<$transclude $variable="fn-buffalo" param="Going to lunch" $output="text/plain"/>
+
+<$transclude $variable="proc-buffalo" param="Going to breakfast" $output="text/plain"/>
+
+<$transclude $variable="macro-buffalo" param="Going to dinner" $output="text/plain"/>
+
++
+title: ExpectedResult
+
+Going to lunch with a ''buffalo''
Going to breakfastwith abuffalo
Going to dinner with a buffalo
Going to lunch with a buffalo with a buffaloGoing to dinner with a buffalo
\ No newline at end of file
diff --git a/editions/test/tiddlers/tests/data/genesis-widget/RedefineLet.tid b/editions/test/tiddlers/tests/data/genesis-widget/RedefineLet.tid
new file mode 100644
index 000000000..f6834998d
--- /dev/null
+++ b/editions/test/tiddlers/tests/data/genesis-widget/RedefineLet.tid
@@ -0,0 +1,31 @@
+title: Genesis/RedefineLet
+description: Using the genesis widget to override the let widget
+type: text/vnd.tiddlywiki-multiple
+tags: [[$:/tags/wiki-test-spec]]
+
+title: Output
+
+\whitespace trim
+\widget $let()
+\whitespace trim
+<$parameters $params="@params">
+<$setmultiplevariables $names="[<@params>jsonindexes[]]" $values="[<@params>jsonindexes[]] :map[<@params>jsongetaddprefix[--]addsuffix[--]]">
+<$slot $name="ts-raw"/>
+$setmultiplevariables>
+$parameters>
+\end
+<$let
+ one="Elephant"
+ $two="Kangaroo"
+ $$three="Giraffe"
+>
+(<$text text=<>/>)
+(<$text text=<<$two>>/>)
+(<$text text=<<$$three>>/>)
+$let>
++
+title: ExpectedResult
+
+(--Elephant--)
+(--Kangaroo--)
+(--Giraffe--)
\ No newline at end of file
diff --git a/editions/test/tiddlers/tests/data/procedures/Nested.tid b/editions/test/tiddlers/tests/data/procedures/Nested.tid
new file mode 100644
index 000000000..f63c634af
--- /dev/null
+++ b/editions/test/tiddlers/tests/data/procedures/Nested.tid
@@ -0,0 +1,20 @@
+title: Procedures/Nested
+description: Nested Procedures
+type: text/vnd.tiddlywiki-multiple
+tags: [[$:/tags/wiki-test-spec]]
+
+title: Output
+
+\whitespace trim
+\procedure alpha(x)
+\procedure beta(y)
+<$text text=<>/>
+\end beta
+<$transclude $variable="beta" y={{{ [addprefix] }}}/>
+\end alpha
+
+<>
++
+title: ExpectedResult
+
+ElephantElephant
\ No newline at end of file
diff --git a/editions/test/tiddlers/tests/data/transclude/CustomWidget-ActionWidget.tid b/editions/test/tiddlers/tests/data/transclude/CustomWidget-ActionWidget.tid
new file mode 100644
index 000000000..0be77a9a3
--- /dev/null
+++ b/editions/test/tiddlers/tests/data/transclude/CustomWidget-ActionWidget.tid
@@ -0,0 +1,27 @@
+title: Transclude/CustomWidget/ActionWidget
+description: Custom widget definition
+type: text/vnd.tiddlywiki-multiple
+tags: [[$:/tags/wiki-test-spec]]
+
+title: Output
+
+\whitespace trim
+<$transclude $tiddler='Result'>
+$transclude>
++
+title: Actions
+
+\whitespace trim
+
+\widget $$action-mywidget(one:'Jaguar')
+\whitespace trim
+<$action-setfield $tiddler="Result" $field="text" $value=<>/>
+\end
+
+<$$action-mywidget one="Dingo">
+ Crocodile
+$$action-mywidget>
++
+title: ExpectedResult
+
+Dingo
\ No newline at end of file
diff --git a/editions/test/tiddlers/tests/data/transclude/CustomWidget-Fail.tid b/editions/test/tiddlers/tests/data/transclude/CustomWidget-Fail.tid
new file mode 100644
index 000000000..3d0759013
--- /dev/null
+++ b/editions/test/tiddlers/tests/data/transclude/CustomWidget-Fail.tid
@@ -0,0 +1,26 @@
+title: Transclude/CustomWidget/Fail
+description: Custom widget failed definition
+type: text/vnd.tiddlywiki-multiple
+tags: [[$:/tags/wiki-test-spec]]
+
+title: Output
+
+\whitespace trim
+
+\widget $non-existent-widget(one:'Jaguar')
+\whitespace trim
+<$text text=<>/>
+<$slot $name="ts-raw">
+ Whale
+$slot>
+\end
+<$non-existent-widget one="Dingo">
+ Crocodile
+$non-existent-widget>
+<$non-existent-widget one="BumbleBee">
+ Squirrel
+$non-existent-widget>
++
+title: ExpectedResult
+
+Undefined widget 'non-existent-widget'Undefined widget 'non-existent-widget'
\ No newline at end of file
diff --git a/editions/test/tiddlers/tests/data/transclude/CustomWidget-Override-Codeblock.tid b/editions/test/tiddlers/tests/data/transclude/CustomWidget-Override-Codeblock.tid
new file mode 100644
index 000000000..c4730622b
--- /dev/null
+++ b/editions/test/tiddlers/tests/data/transclude/CustomWidget-Override-Codeblock.tid
@@ -0,0 +1,29 @@
+title: CustomWidget-Override-Codeblock
+description: Usage of genesis widget with attributes starting with dollar signs
+type: text/vnd.tiddlywiki-multiple
+tags: [[$:/tags/wiki-test-spec]]
+
+title: Output
+
+\whitespace trim
+\import Definition
+<$codeblock code="Kangaroo"/>
+<$codeblock code={{Subject}}/>
+<$let test="Tiger">
+<$codeblock code=<>/>
+$let>
++
+title: Definition
+
+\whitespace trim
+\widget $codeblock(code)
+<$genesis $type="$codeblock" $remappable="no" code={{{ [addprefix[£]addsuffix[@]] }}}/>
+\end
++
+title: Subject
+
+Python
++
+title: ExpectedResult
+
+£Kangaroo@
£Python@
£Tiger@
\ No newline at end of file
diff --git a/editions/test/tiddlers/tests/data/transclude/CustomWidget-OverrideTransclude.tid b/editions/test/tiddlers/tests/data/transclude/CustomWidget-OverrideTransclude.tid
new file mode 100644
index 000000000..c57e4a9a1
--- /dev/null
+++ b/editions/test/tiddlers/tests/data/transclude/CustomWidget-OverrideTransclude.tid
@@ -0,0 +1,33 @@
+title: Transclude/CustomWidget/OverrideTransclude
+description: Custom widget definition attempting to override transclude
+type: text/vnd.tiddlywiki-multiple
+tags: [[$:/tags/wiki-test-spec]]
+
+title: Output
+
+\whitespace trim
+<$transclude $tiddler='TiddlerOne' one='Ferret'>
+$transclude>
++
+title: TiddlerZero
+
+Antelope
++
+title: TiddlerOne
+
+\whitespace trim
+
+\widget $transclude(one:'Jaguar')
+\whitespace trim
+ <$text text=<>/>
+ <$slot $name="body">
+ Whale
+ $slot>
+\end
+<$genesis $type="$transclude" $remappable="no" $$tiddler="TiddlerZero">
+ Crocodile
+$genesis>
++
+title: ExpectedResult
+
+Antelope
\ No newline at end of file
diff --git a/editions/test/tiddlers/tests/data/transclude/CustomWidget-Simple.tid b/editions/test/tiddlers/tests/data/transclude/CustomWidget-Simple.tid
new file mode 100644
index 000000000..15d0c8d9e
--- /dev/null
+++ b/editions/test/tiddlers/tests/data/transclude/CustomWidget-Simple.tid
@@ -0,0 +1,33 @@
+title: Transclude/CustomWidget/Simple
+description: Custom widget definition
+type: text/vnd.tiddlywiki-multiple
+tags: [[$:/tags/wiki-test-spec]]
+
+title: Output
+
+\whitespace trim
+<$transclude $tiddler='TiddlerOne' one='Ferret'>
+$transclude>
++
+title: TiddlerOne
+
+\whitespace trim
+
+\widget $$mywidget(one:'Jaguar')
+\whitespace trim
+<$text text=<>/>
+<$slot $name="ts-raw">
+ Whale
+$slot>
+\end
+<$$mywidget one="Dingo">
+ Crocodile
+$$mywidget>
+<$$mywidget one="BumbleBee">
+ Squirrel
+$$mywidget>
+<$$mywidget/>
++
+title: ExpectedResult
+
+DingoCrocodileBumbleBeeSquirrelJaguarWhale
\ No newline at end of file
diff --git a/editions/test/tiddlers/tests/data/transclude/CustomWidget-Slotted-Empty.tid b/editions/test/tiddlers/tests/data/transclude/CustomWidget-Slotted-Empty.tid
new file mode 100644
index 000000000..efd1e7041
--- /dev/null
+++ b/editions/test/tiddlers/tests/data/transclude/CustomWidget-Slotted-Empty.tid
@@ -0,0 +1,20 @@
+title: CustomWidget/Slotted/Empty
+description: Custom widget with empty slotted values
+type: text/vnd.tiddlywiki-multiple
+tags: [[$:/tags/wiki-test-spec]]
+
+title: Output
+
+\whitespace trim
+\widget $$mywidget()
+<$slot $name=ts-raw>the body is empty$slot>
+\end
+
+#<$$mywidget/>
+#<$$mywidget>$$mywidget>
+#<$$mywidget>the body is not empty$$mywidget>
+
++
+title: ExpectedResult
+
+- the body is empty
- the body is empty
- the body is not empty
\ No newline at end of file
diff --git a/editions/test/tiddlers/tests/data/transclude/CustomWidget-Slotted.tid b/editions/test/tiddlers/tests/data/transclude/CustomWidget-Slotted.tid
new file mode 100644
index 000000000..c10e84127
--- /dev/null
+++ b/editions/test/tiddlers/tests/data/transclude/CustomWidget-Slotted.tid
@@ -0,0 +1,27 @@
+title: Transclude/CustomWidget/Slotted
+description: Custom widget definition
+type: text/vnd.tiddlywiki-multiple
+tags: [[$:/tags/wiki-test-spec]]
+
+title: Output
+
+\whitespace trim
+\widget $$mywidget(one:'Jaguar')
+\whitespace trim
+<$text text=<>/>
+<$slot $name="ts-stuff">
+ Whale
+$slot>
+\end
+<$$mywidget one="Dingo">
+ <$fill $name="ts-stuff">
+ Crocodile
+ $fill>
+$$mywidget>
+<$$mywidget one="BumbleBee">
+ Squirrel
+$$mywidget>
++
+title: ExpectedResult
+
+DingoCrocodileBumbleBeeWhale
\ No newline at end of file
diff --git a/editions/test/tiddlers/tests/data/transclude/CustomWidget-TextWidgetOverride.tid b/editions/test/tiddlers/tests/data/transclude/CustomWidget-TextWidgetOverride.tid
new file mode 100644
index 000000000..d0a3cc82c
--- /dev/null
+++ b/editions/test/tiddlers/tests/data/transclude/CustomWidget-TextWidgetOverride.tid
@@ -0,0 +1,27 @@
+title: Transclude/CustomWidget/TextWidgetOverride
+description: Custom widget definition redefining the text widget
+type: text/vnd.tiddlywiki-multiple
+tags: [[$:/tags/wiki-test-spec]]
+
+title: Output
+
+\whitespace trim
+<$transclude $tiddler='TiddlerOne'>
+$transclude>
++
+title: TiddlerOne
+
+\whitespace trim
+
+\widget $text(text:'Jaguar')
+\whitespace trim
+<$genesis $type="$text" $remappable="no" text={{{ [addprefix[≤]addsuffix[≥]] }}}/>
+\end
+
+<$text text="Dingo"/>
+
+Crocodile
++
+title: ExpectedResult
+
+≤Dingo≥≤Jaguar≥
\ No newline at end of file
diff --git a/editions/test/tiddlers/tests/data/transclude/CustomWidget-TextWidgetOverrideWithSlot.tid b/editions/test/tiddlers/tests/data/transclude/CustomWidget-TextWidgetOverrideWithSlot.tid
new file mode 100644
index 000000000..c84c5ae9a
--- /dev/null
+++ b/editions/test/tiddlers/tests/data/transclude/CustomWidget-TextWidgetOverrideWithSlot.tid
@@ -0,0 +1,31 @@
+title: Transclude/CustomWidget/TextWidgetOverrideWithSlot
+description: Custom widget definition redefining the text widget
+type: text/vnd.tiddlywiki-multiple
+tags: [[$:/tags/wiki-test-spec]]
+
+title: Output
+
+\whitespace trim
+<$transclude $tiddler='TiddlerOne'>
+$transclude>
++
+title: TiddlerOne
+
+\whitespace trim
+
+\widget $text(text:'Jaguar')
+\whitespace trim
+<$genesis $type="$text" $remappable="no" text=<>/>
+<$set name="$text" value="">
+ <$slot $name="ts-raw">
+ Whale
+ $slot>
+$set>
+\end
+<$text text="Dingo">
+ Crocodile
+$text>
++
+title: ExpectedResult
+
+DingoCrocodile
\ No newline at end of file
diff --git a/editions/test/tiddlers/tests/data/transclude/CustomWidget-Unoverride-Codeblock.tid b/editions/test/tiddlers/tests/data/transclude/CustomWidget-Unoverride-Codeblock.tid
new file mode 100644
index 000000000..c6a834205
--- /dev/null
+++ b/editions/test/tiddlers/tests/data/transclude/CustomWidget-Unoverride-Codeblock.tid
@@ -0,0 +1,31 @@
+title: CustomWidget-Unoverride-Codeblock
+description: Usage of genesis widget with attributes starting with dollar signs, and unoverriding a core widget
+type: text/vnd.tiddlywiki-multiple
+tags: [[$:/tags/wiki-test-spec]]
+
+title: Output
+
+\whitespace trim
+\import Definition
+<$let $codeblock="">
+<$codeblock code="Kangaroo"/>
+<$codeblock code={{Subject}}/>
+<$let test="Tiger">
+<$codeblock code=<>/>
+$let>
+$let>
++
+title: Definition
+
+\whitespace trim
+\widget $codeblock(code)
+<$genesis $type="codeblock" $remappable="no" code={{{ [addprefix[£]addsuffix[@]] }}}/>
+\end
++
+title: Subject
+
+Python
++
+title: ExpectedResult
+
+Kangaroo
Python
Tiger
\ No newline at end of file
diff --git a/editions/test/tiddlers/tests/data/transclude/CustomWidget-VariableAttribute.tid b/editions/test/tiddlers/tests/data/transclude/CustomWidget-VariableAttribute.tid
new file mode 100644
index 000000000..8ef700b41
--- /dev/null
+++ b/editions/test/tiddlers/tests/data/transclude/CustomWidget-VariableAttribute.tid
@@ -0,0 +1,29 @@
+title: Transclude/CustomWidget/VariableAttribute
+description: Custom widget definition using an attribute called $variable
+type: text/vnd.tiddlywiki-multiple
+tags: [[$:/tags/wiki-test-spec]]
+
+title: Output
+
+\whitespace trim
+<$transclude $tiddler='TiddlerOne' one='Ferret'>
+$transclude>
++
+title: TiddlerOne
+
+\whitespace trim
+
+\widget $$mywidget($variable:'Jaguar')
+\whitespace trim
+<$text text=<<$variable>>/>
+<$slot $name="ts-raw">
+ Whale
+$slot>
+\end
+<$$mywidget $variable="Dingo">
+ Crocodile
+$$mywidget>
++
+title: ExpectedResult
+
+DingoCrocodile
\ No newline at end of file
diff --git a/editions/test/tiddlers/tests/data/transclude/JavaScript-Macro.tid b/editions/test/tiddlers/tests/data/transclude/JavaScript-Macro.tid
new file mode 100644
index 000000000..216a89dc8
--- /dev/null
+++ b/editions/test/tiddlers/tests/data/transclude/JavaScript-Macro.tid
@@ -0,0 +1,17 @@
+title: Transclude/Macro/JavaScript
+description: Transcluding a javascript macro
+type: text/vnd.tiddlywiki-multiple
+tags: [[$:/tags/wiki-test-spec]]
+
+title: Output
+
+\whitespace trim
+
+<>
+
+<$macrocall $name="makedatauri" text="Wildebeest" type="text/plain"/>
+
++
+title: ExpectedResult
+
+data:text/plain,Wildebeest
data:text/plain,Wildebeest
\ No newline at end of file
diff --git a/editions/test/tiddlers/tests/data/transclude/Macro-Plain.tid b/editions/test/tiddlers/tests/data/transclude/Macro-Plain.tid
new file mode 100644
index 000000000..410144153
--- /dev/null
+++ b/editions/test/tiddlers/tests/data/transclude/Macro-Plain.tid
@@ -0,0 +1,17 @@
+title: Transclude/Macro/Plain
+description: Transcluding a macro as plain text
+type: text/vnd.tiddlywiki-multiple
+tags: [[$:/tags/wiki-test-spec]]
+
+title: Output
+
+\whitespace trim
+<$let currentTab="Jeremy">
+<$macrocall $name="currentTab" $type="text/plain" $output="text/plain"/>
+|
+<$transclude $variable="currentTab" $type="text/plain" $output="text/plain"/>
+$let>
++
+title: ExpectedResult
+
+Jeremy|Jeremy
\ No newline at end of file
diff --git a/editions/test/tiddlers/tests/data/transclude/Macro-Simple.tid b/editions/test/tiddlers/tests/data/transclude/Macro-Simple.tid
new file mode 100644
index 000000000..71db5efe4
--- /dev/null
+++ b/editions/test/tiddlers/tests/data/transclude/Macro-Simple.tid
@@ -0,0 +1,26 @@
+title: Transclude/Macro/Simple
+description: Transcluding a macro
+type: text/vnd.tiddlywiki-multiple
+tags: [[$:/tags/wiki-test-spec]]
+
+title: Output
+
+\whitespace trim
+\define mamacro(one:"red",two:"green")
+It is $one$ and $two$ or <<__one__>> and <<__two__>>.
+\end
+
+<$macrocall $name="mamacro"/>
+
+<$transclude $variable="mamacro"/>
+
+<$transclude $variable="mamacro" one="orange"/>
+
+<$transclude $variable="mamacro" 0="pink"/>
+
+<$transclude $variable="mamacro" one="purple" 1="pink"/>
+
++
+title: ExpectedResult
+
+It is red and green or red and green.
It is red and green or red and green.
It is orange and green or orange and green.
It is pink and green or pink and green.
It is purple and pink or purple and pink.
\ No newline at end of file
diff --git a/editions/test/tiddlers/tests/data/transclude/MissingTarget.tid b/editions/test/tiddlers/tests/data/transclude/MissingTarget.tid
new file mode 100644
index 000000000..8bdc86eaa
--- /dev/null
+++ b/editions/test/tiddlers/tests/data/transclude/MissingTarget.tid
@@ -0,0 +1,48 @@
+title: Transclude/MissingTarget
+description: Transcluding a missing target
+type: text/vnd.tiddlywiki-multiple
+tags: [[$:/tags/wiki-test-spec]]
+
+title: Output
+
+\whitespace trim
+<$transclude $tiddler='TiddlerOne' one='Ferret'>
+ <$parameters one='Ferret'>
+ Badger
+ <$text text=<>/>
+ $parameters>
+$transclude>
+<$transclude $tiddler='TiddlerOne' one='Ferret'>
+ <$fill $name="ts-missing">
+ <$parameters one='Ferret'>
+ Badger
+ <$text text=<>/>
+ $parameters>
+ $fill>
+$transclude>
+<$transclude $tiddler='MissingTiddler' one='Ferret'>
+ <$parameters one='Ferret'>
+ Badger
+ <$text text=<>/>
+ $parameters>
+$transclude>
+<$transclude $tiddler='MissingTiddler' one='Ferret'>
+ <$fill $name="ts-missing">
+ <$parameters one='Ferret'>
+ Badger
+ <$text text=<>/>
+ $parameters>
+ $fill>
+$transclude>
++
+title: TiddlerOne
+
+\whitespace trim
+<$parameters one='Kangaroo'>
+ Piranha
+ <$text text=<>/>
+$parameters>
++
+title: ExpectedResult
+
+PiranhaFerretPiranhaFerretBadgerFerretBadgerFerret
\ No newline at end of file
diff --git a/editions/test/tiddlers/tests/data/transclude/Parameterised-Depth.tid b/editions/test/tiddlers/tests/data/transclude/Parameterised-Depth.tid
new file mode 100644
index 000000000..064e225c8
--- /dev/null
+++ b/editions/test/tiddlers/tests/data/transclude/Parameterised-Depth.tid
@@ -0,0 +1,34 @@
+title: Transclude/Parameterised/Depth
+description: Parameterised transclusion using the $depth attribute
+type: text/vnd.tiddlywiki-multiple
+tags: [[$:/tags/wiki-test-spec]]
+
+title: Output
+
+\whitespace trim
+<$transclude $tiddler='TiddlerOne' one='Ferret'/>
+|
+<$transclude $tiddler='TiddlerOne'/>
+|
+<$transclude $tiddler='TiddlerOne' one='Ferret' $$two="Osprey"/>
+|
+<$transclude $tiddler='TiddlerOne' $$two="Falcon"/>
++
+title: TiddlerOne
+
+\whitespace trim
+{{TiddlerTwo}}
++
+title: TiddlerTwo
+
+\whitespace trim
+<$parameters one='Jaguar' $$two='Piranha' $depth="2">
+ <$text text=<>/>:<$text text=<<$two>>/>
+$parameters>
+<$parameters one='Leopard' $$two='Coelacanth'>
+ (<$text text=<>/>|<$text text=<<$two>>/>)
+$parameters>
++
+title: ExpectedResult
+
+Ferret:Piranha(Leopard|Coelacanth)|Jaguar:Piranha(Leopard|Coelacanth)|Ferret:Osprey(Leopard|Coelacanth)|Jaguar:Falcon(Leopard|Coelacanth)
\ No newline at end of file
diff --git a/editions/test/tiddlers/tests/data/transclude/Parameterised-Mode.tid b/editions/test/tiddlers/tests/data/transclude/Parameterised-Mode.tid
new file mode 100644
index 000000000..04f5bbb04
--- /dev/null
+++ b/editions/test/tiddlers/tests/data/transclude/Parameterised-Mode.tid
@@ -0,0 +1,29 @@
+title: Transclude/Parameterised/Mode
+description: Parameterised transclusion using the $parseMode attribute
+type: text/vnd.tiddlywiki-multiple
+tags: [[$:/tags/wiki-test-spec]]
+
+title: Output
+
+\whitespace trim
+
+<$transclude $tiddler='TiddlerOne' one='Ferret'>
+
+This is a block
+
+$transclude>
+
+<$transclude $tiddler='TiddlerOne'>
+This is inline
+$transclude>
++
+title: TiddlerOne
+
+\whitespace trim
+<$parameters $parseMode="@parseMode">
+ <$text text=<<@parseMode>>/>
+$parameters>
++
+title: ExpectedResult
+
+block
inline
\ No newline at end of file
diff --git a/editions/test/tiddlers/tests/data/transclude/Parameterised-Name-Values.tid b/editions/test/tiddlers/tests/data/transclude/Parameterised-Name-Values.tid
new file mode 100644
index 000000000..9d62a7897
--- /dev/null
+++ b/editions/test/tiddlers/tests/data/transclude/Parameterised-Name-Values.tid
@@ -0,0 +1,34 @@
+title: Transclude/Parameterised/Name/Values
+description: Parameterised transclusion accessing parameters as name/value pairs
+type: text/vnd.tiddlywiki-multiple
+tags: [[$:/tags/wiki-test-spec]]
+
+title: Output
+
+\whitespace trim
+<$transclude $tiddler="TiddlerOne" 0="" 1="" 2=""/>
+
+{{TiddlerOne}}
+{{TiddlerOne|Ferret}}
+{{TiddlerOne|Butterfly|Moth}}
+{{TiddlerOne|Beetle|Scorpion|Snake}}
+{{TiddlerOne||TiddlerTwo|Beetle|Scorpion|Snake}}
++
+title: TiddlerOne
+
+\whitespace trim
+<$parameters zero='Jaguar' $$one='Lizard' two='Mole' $params="@params">
+<$list filter="[<@params>jsonindexes[]]">
+{<$text text=<>/>: <$text text={{{ [<@params>jsonget] }}}/>}
+$list>
+$parameters>
++
+title: TiddlerTwo
+
+\whitespace trim
+\parameters(zero:'Mouse',$one:'Horse',two:'Owl')
+(<$transclude $tiddler=<> zero=<> $$one=<<$one>> two=<>/>)
++
+title: ExpectedResult
+
+{0:}{1:}{2:}
{0:Ferret}
{0:Butterfly}{1:Moth}
{0:Beetle}{1:Scorpion}{2:Snake}
({$one:Scorpion}{two:Snake}{zero:Beetle})
\ No newline at end of file
diff --git a/editions/test/tiddlers/tests/data/transclude/Parameterised-ParseTreeNodes.tid b/editions/test/tiddlers/tests/data/transclude/Parameterised-ParseTreeNodes.tid
new file mode 100644
index 000000000..916e2abfb
--- /dev/null
+++ b/editions/test/tiddlers/tests/data/transclude/Parameterised-ParseTreeNodes.tid
@@ -0,0 +1,29 @@
+title: Transclude/Parameterised/ParseTreeNodes
+description: Parameterised transclusion using the $parseTreeNodes attribute
+type: text/vnd.tiddlywiki-multiple
+tags: [[$:/tags/wiki-test-spec]]
+
+title: Output
+
+\whitespace trim
+
+<$transclude $tiddler='TiddlerOne' one='Ferret'>
+
+This is a block
+
+$transclude>
+
+<$transclude $tiddler='TiddlerOne'>
+This is inline
+$transclude>
++
+title: TiddlerOne
+
+\whitespace trim
+<$parameters $parseTreeNodes="@parseTreeNodes">
+ <$text text=<<@parseTreeNodes>>/>
+$parameters>
++
+title: ExpectedResult
+
+[{"type":"element","tag":"p","children":[{"type":"text","text":"This is a block","start":68,"end":83}],"start":68,"end":83}]
[{"type":"text","text":"This is inline","start":136,"end":152}]
\ No newline at end of file
diff --git a/editions/test/tiddlers/tests/data/transclude/Parameterised-Positional-Shortcut-Parameters.tid b/editions/test/tiddlers/tests/data/transclude/Parameterised-Positional-Shortcut-Parameters.tid
new file mode 100644
index 000000000..abf444adb
--- /dev/null
+++ b/editions/test/tiddlers/tests/data/transclude/Parameterised-Positional-Shortcut-Parameters.tid
@@ -0,0 +1,29 @@
+title: Transclude/Parameterised/Positional/Shortcut/Parameters
+description: Positional parameterised transclusion using shortcut syntax and parameters pragma
+type: text/vnd.tiddlywiki-multiple
+tags: [[$:/tags/wiki-test-spec]]
+
+title: Output
+
+\whitespace trim
+{{TiddlerOne}}
+{{TiddlerOne|Ferret}}
+{{TiddlerOne|Butterfly|Moth}}
+{{TiddlerOne|Beetle|Scorpion|Snake}}
+{{TiddlerOne||TiddlerTwo|Beetle|Scorpion|Snake}}
++
+title: TiddlerOne
+
+\whitespace trim
+\parameters(zero:Jaguar,one:'Lizard',two:'Mole')
+[{<$text text=<>/>}{<$text text=<>/>}{<$text text=<>/>}]
++
+title: TiddlerTwo
+
+\whitespace trim
+\parameters(zero:'Mouse',one:Horse,two:'Owl')
+(<$transclude $tiddler=<> zero=<> one=<> two=<>/>)
++
+title: ExpectedResult
+
+[{Jaguar}{Lizard}{Mole}]
[{Ferret}{Lizard}{Mole}]
[{Butterfly}{Moth}{Mole}]
[{Beetle}{Scorpion}{Snake}]
([{Beetle}{Scorpion}{Snake}])
\ No newline at end of file
diff --git a/editions/test/tiddlers/tests/data/transclude/Parameterised-Positional-Shortcut.tid b/editions/test/tiddlers/tests/data/transclude/Parameterised-Positional-Shortcut.tid
new file mode 100644
index 000000000..7792e6c66
--- /dev/null
+++ b/editions/test/tiddlers/tests/data/transclude/Parameterised-Positional-Shortcut.tid
@@ -0,0 +1,29 @@
+title: Transclude/Parameterised/Positional/Shortcut
+description: Positional parameterised transclusion using shortcut syntax
+type: text/vnd.tiddlywiki-multiple
+tags: [[$:/tags/wiki-test-spec]]
+
+title: Output
+
+\whitespace trim
+{{TiddlerOne}}
+{{TiddlerOne|Ferret}}
+{{TiddlerOne|Butterfly|Moth}}
+{{TiddlerOne|Beetle|Scorpion|Snake}}
+{{TiddlerOne||TiddlerTwo|Beetle|Scorpion|Snake}}
++
+title: TiddlerOne
+
+\whitespace trim
+<$parameters zero='Jaguar' one='Lizard' two='Mole'>[{<$text text=<>/>}{<$text text=<>/>}{<$text text=<>/>}]$parameters>
++
+title: TiddlerTwo
+
+\whitespace trim
+<$parameters zero='Mouse' one='Horse' two='Owl'>
+(<$transclude $tiddler=<> zero=<> one=<> two=<>/>)
+$parameters>
++
+title: ExpectedResult
+
+[{Jaguar}{Lizard}{Mole}]
[{Ferret}{Lizard}{Mole}]
[{Butterfly}{Moth}{Mole}]
[{Beetle}{Scorpion}{Snake}]
([{Beetle}{Scorpion}{Snake}])
\ No newline at end of file
diff --git a/editions/test/tiddlers/tests/data/transclude/Parameterised-Positional-Variables.tid b/editions/test/tiddlers/tests/data/transclude/Parameterised-Positional-Variables.tid
new file mode 100644
index 000000000..ad2b7be52
--- /dev/null
+++ b/editions/test/tiddlers/tests/data/transclude/Parameterised-Positional-Variables.tid
@@ -0,0 +1,30 @@
+title: Transclude/Parameterised/Positional/Variables
+description: Positional parameterised transclusion of variables
+type: text/vnd.tiddlywiki-multiple
+tags: [[$:/tags/wiki-test-spec]]
+
+title: Output
+
+\whitespace trim
+\function myfunction(alpha:"apple",beta:"banana",gamma:"grenadine") []
+\define mymacro(alpha:"apple",beta:"banana",gamma:"grenadine") $beta$
+\function f(a) []
+
+(Functions:
+<$text text={{{ [] }}}/>
+,
+<$text text=<>/>
+,
+<>
+)(Macros:
+<$text text={{{ [] }}}/>
+,
+<$text text=<>/>
+,
+<>
+)
+
++
+title: ExpectedResult
+
+(Functions:f1,f1,f1)(Macros:banana,banana,banana)
\ No newline at end of file
diff --git a/editions/test/tiddlers/tests/data/transclude/Parameterised-Positional.tid b/editions/test/tiddlers/tests/data/transclude/Parameterised-Positional.tid
new file mode 100644
index 000000000..d7eb9090e
--- /dev/null
+++ b/editions/test/tiddlers/tests/data/transclude/Parameterised-Positional.tid
@@ -0,0 +1,26 @@
+title: Transclude/Parameterised/Positional
+description: Positional parameterised transclusion
+type: text/vnd.tiddlywiki-multiple
+tags: [[$:/tags/wiki-test-spec]]
+
+title: Output
+
+\whitespace trim
+<$transclude $tiddler='TiddlerOne' zero='Ferret'/>
+<$transclude zero='Ferret' $tiddler='TiddlerOne'/>
+<$transclude $tiddler='TiddlerOne' 0='Pigeon'/>
+<$transclude 0='Pigeon' $tiddler='TiddlerOne'/>
+<$transclude $tiddler='TiddlerOne' zero='Ferret' 0='Pigeon'/>
+<$transclude zero='Ferret' 0='Pigeon' $tiddler='TiddlerOne'/>
+<$transclude $tiddler='TiddlerOne'/>
++
+title: TiddlerOne
+
+\whitespace trim
+<$parameters zero='Jaguar'>
+ <$text text=<>/>
+$parameters>
++
+title: ExpectedResult
+
+FerretFerretPigeonPigeonFerretFerretJaguar
\ No newline at end of file
diff --git a/editions/test/tiddlers/tests/data/transclude/Parameterised-Shortcut-Parameters.tid b/editions/test/tiddlers/tests/data/transclude/Parameterised-Shortcut-Parameters.tid
new file mode 100644
index 000000000..375964199
--- /dev/null
+++ b/editions/test/tiddlers/tests/data/transclude/Parameterised-Shortcut-Parameters.tid
@@ -0,0 +1,20 @@
+title: Transclude/Parameterised/Shortcut/Parameters
+description: Simple parameterised transclusion using the parameters pragma
+type: text/vnd.tiddlywiki-multiple
+tags: [[$:/tags/wiki-test-spec]]
+
+title: Output
+
+\whitespace trim
+<$transclude $tiddler='TiddlerOne' one='Ferret'/>
+<$transclude $tiddler='TiddlerOne'/>
++
+title: TiddlerOne
+
+\whitespace trim
+\parameters(one:'Jaguar')
+<$text text=<>/>
++
+title: ExpectedResult
+
+FerretJaguar
\ No newline at end of file
diff --git a/editions/test/tiddlers/tests/data/transclude/Parameterised-Shortcut.tid b/editions/test/tiddlers/tests/data/transclude/Parameterised-Shortcut.tid
new file mode 100644
index 000000000..0499cf2d6
--- /dev/null
+++ b/editions/test/tiddlers/tests/data/transclude/Parameterised-Shortcut.tid
@@ -0,0 +1,21 @@
+title: Transclude/Parameterised/Shortcut
+description: Simple parameterised transclusion
+type: text/vnd.tiddlywiki-multiple
+tags: [[$:/tags/wiki-test-spec]]
+
+title: Output
+
+\whitespace trim
+\procedure test(one:'Jaguar')
+{<$text text=<>/>}
+\end
+
+<$transclude $variable='test' one='Ferret'/>
+<$transclude $variable='test'/>
+<>
+<>
+
++
+title: ExpectedResult
+
+{Ferret}{Jaguar}{Rat}{Mouse}
\ No newline at end of file
diff --git a/editions/test/tiddlers/tests/data/transclude/Parameterised-Simple.tid b/editions/test/tiddlers/tests/data/transclude/Parameterised-Simple.tid
new file mode 100644
index 000000000..0268f9e59
--- /dev/null
+++ b/editions/test/tiddlers/tests/data/transclude/Parameterised-Simple.tid
@@ -0,0 +1,26 @@
+title: Transclude/Parameterised/Simple
+description: Simple parameterised transclusion
+type: text/vnd.tiddlywiki-multiple
+tags: [[$:/tags/wiki-test-spec]]
+
+title: Output
+
+\whitespace trim
+<$transclude $tiddler='TiddlerOne' one='Ferret'/>
+|
+<$transclude $tiddler='TiddlerOne'/>
+|
+<$transclude $tiddler='TiddlerOne' one='Ferret' $$two="Osprey"/>
+|
+<$transclude $tiddler='TiddlerOne' $$two="Falcon"/>
++
+title: TiddlerOne
+
+\whitespace trim
+<$parameters one='Jaguar' $$two='Piranha'>
+ <$text text=<>/>:<$text text=<<$two>>/>
+$parameters>
++
+title: ExpectedResult
+
+Ferret:Piranha|Jaguar:Piranha|Ferret:Osprey|Jaguar:Falcon
\ No newline at end of file
diff --git a/editions/test/tiddlers/tests/data/transclude/Parameterised-SlotFillParseTreeNodes.tid b/editions/test/tiddlers/tests/data/transclude/Parameterised-SlotFillParseTreeNodes.tid
new file mode 100644
index 000000000..679748375
--- /dev/null
+++ b/editions/test/tiddlers/tests/data/transclude/Parameterised-SlotFillParseTreeNodes.tid
@@ -0,0 +1,29 @@
+title: Transclude/Parameterised/SlotFillParseTreeNodes
+description: Parameterised transclusion using the $slotFillParseTreeNodes attribute
+type: text/vnd.tiddlywiki-multiple
+tags: [[$:/tags/wiki-test-spec]]
+
+title: Output
+
+\whitespace trim
+
+<$transclude $tiddler='TiddlerOne' one='Ferret'>
+<$fill $name="one">This is first$fill>
+<$fill $name="two">But this is second$fill>
+$transclude>
+
+<$transclude $tiddler='TiddlerOne'>
+<$fill $name="one">This is first
+<$fill $name="two">But this is second$fill>$fill>
+$transclude>
++
+title: TiddlerOne
+
+\whitespace trim
+<$parameters $slotFillParseTreeNodes="@slotFillParseTreeNodes">
+ <$text text={{{ [<@slotFillParseTreeNodes>jsonindexes[]join[,]] }}}/>
+$parameters>
++
+title: ExpectedResult
+
+one,ts-raw,two
one,ts-raw
\ No newline at end of file
diff --git a/editions/test/tiddlers/tests/data/transclude/Parameterised-Slotted-Missing.tid b/editions/test/tiddlers/tests/data/transclude/Parameterised-Slotted-Missing.tid
new file mode 100644
index 000000000..fe399d572
--- /dev/null
+++ b/editions/test/tiddlers/tests/data/transclude/Parameterised-Slotted-Missing.tid
@@ -0,0 +1,24 @@
+title: Transclude/Parameterised/Slotted/Missing
+description: Parameterised transclusion with slotted missing values
+type: text/vnd.tiddlywiki-multiple
+tags: [[$:/tags/wiki-test-spec]]
+
+title: Output
+
+\whitespace trim
+<$transclude $tiddler='TiddlerOne' one='Ferret'>
+$transclude>
++
+title: TiddlerOne
+
+\whitespace trim
+<$parameters one='Jaguar'>
+ <$text text=<>/>
+ <$slot $name="content">
+ Whale
+ $slot>
+$parameters>
++
+title: ExpectedResult
+
+FerretWhale
\ No newline at end of file
diff --git a/editions/test/tiddlers/tests/data/transclude/Parameterised-Slotted.tid b/editions/test/tiddlers/tests/data/transclude/Parameterised-Slotted.tid
new file mode 100644
index 000000000..c795621ef
--- /dev/null
+++ b/editions/test/tiddlers/tests/data/transclude/Parameterised-Slotted.tid
@@ -0,0 +1,27 @@
+title: Transclude/Parameterised/Slotted
+description: Parameterised transclusion with slotted values
+type: text/vnd.tiddlywiki-multiple
+tags: [[$:/tags/wiki-test-spec]]
+
+title: Output
+
+\whitespace trim
+<$transclude $tiddler='TiddlerOne' one='Ferret'>
+ <$fill $name="content">
+ Hippopotamus
+ $fill>
+$transclude>
++
+title: TiddlerOne
+
+\whitespace trim
+<$parameters one='Jaguar'>
+ <$text text=<>/>
+ <$slot $name="content">
+ Whale
+ $slot>
+$parameters>
++
+title: ExpectedResult
+
+FerretHippopotamus
\ No newline at end of file
diff --git a/editions/test/tiddlers/tests/data/transclude/Procedures-Whitespace.tid b/editions/test/tiddlers/tests/data/transclude/Procedures-Whitespace.tid
new file mode 100644
index 000000000..d2bded70c
--- /dev/null
+++ b/editions/test/tiddlers/tests/data/transclude/Procedures-Whitespace.tid
@@ -0,0 +1,25 @@
+title: Transclude/Procedures/Whitespace
+description: Procedures should inherit whitespace settings from definition site
+type: text/vnd.tiddlywiki-multiple
+tags: [[$:/tags/wiki-test-spec]]
+
+title: Output
+
+\whitespace trim
+\procedure testproc()
+This is a sentence
+\end
+
+\define testmacro()
+This is a sentence
+\end
+This is a sentence
+[<>]
+[<>]
+
++
+title: ExpectedResult
+
+This is a sentence
+[This is a sentence]
+[This is a sentence ]
\ No newline at end of file
diff --git a/editions/test/tiddlers/tests/data/transclude/Typed.tid b/editions/test/tiddlers/tests/data/transclude/Typed.tid
new file mode 100644
index 000000000..c99664b59
--- /dev/null
+++ b/editions/test/tiddlers/tests/data/transclude/Typed.tid
@@ -0,0 +1,38 @@
+title: Transclude/Typed
+description: Typed transclusion
+type: text/vnd.tiddlywiki-multiple
+tags: [[$:/tags/wiki-test-spec]]
+
+title: Output
+
+\procedure testproc()
+This is ''wikitext''
+\end
+
+<$transclude $variable="testproc"/>
+-
+<$transclude $variable="testproc" $type="text/plain"/>
+
+<$transclude $tiddler="Data" $index="testindex"/>
+-
+<$transclude $tiddler="Data" $index="testindex" $type="text/plain"/>
+
+<$transclude $tiddler="Data" $field="custom"/>
+-
+<$transclude $tiddler="Data" $field="custom" $type="text/plain"/>
++
+title: Data
+type: application/x-tiddler-dictionary
+custom: This is ''wikitext''
+
+testindex: This is ''wikitext''
++
+title: ExpectedResult
+
+This is wikitext
+-
+
This is ''wikitext''
This is wikitext
+-
+
This is ''wikitext''
This is wikitext
+-
+
This is ''wikitext''
\ No newline at end of file
diff --git a/editions/test/tiddlers/tests/test-filters.js b/editions/test/tiddlers/tests/test-filters.js
index 2a9080de5..e00d0bf8d 100644
--- a/editions/test/tiddlers/tests/test-filters.js
+++ b/editions/test/tiddlers/tests/test-filters.js
@@ -422,7 +422,7 @@ Tests the filtering mechanism.
expect(wiki.filterTiddlers("[[one]tagging[]sort[title]]").join(",")).toBe("Tiddler Three,Tiddler8,TiddlerOne,TiddlerSeventh");
expect(wiki.filterTiddlers("[[one]tagging[]]").join(",")).toBe("Tiddler Three,TiddlerOne,TiddlerSeventh,Tiddler8");
expect(wiki.filterTiddlers("[[two]tagging[]sort[title]]").join(",")).toBe("$:/TiddlerFive,$:/TiddlerTwo,Tiddler Three");
- var fakeWidget = {getVariable: function() {return "one";}};
+ var fakeWidget = {wiki: wiki, getVariable: function(name) {return name === "currentTiddler" ? "one": undefined;}};
expect(wiki.filterTiddlers("[all[current]tagging[]]",fakeWidget).join(",")).toBe("Tiddler Three,TiddlerOne,TiddlerSeventh,Tiddler8");
});
@@ -625,7 +625,7 @@ Tests the filtering mechanism.
expect(wiki.filterTiddlers("[{!!title}]").join(",")).toBe("");
expect(wiki.filterTiddlers("[prefix{Tiddler8}] +[sort[title]]").join(",")).toBe("Tiddler Three,TiddlerOne");
expect(wiki.filterTiddlers("[modifier{Tiddler8!!test-field}] +[sort[title]]").join(",")).toBe("TiddlerOne");
- var fakeWidget = {wiki: wiki, getVariable: function() {return "Tiddler Three";}};
+ var fakeWidget = {wiki: wiki, getVariable: function(name) {return name === "currentTiddler" ? "Tiddler Three": undefined;}};
expect(wiki.filterTiddlers("[modifier{!!modifier}] +[sort[title]]",fakeWidget).join(",")).toBe("$:/TiddlerTwo,a fourth tiddler,one,Tiddler Three");
});
diff --git a/editions/test/tiddlers/tests/test-parsetextreference.js b/editions/test/tiddlers/tests/test-parsetextreference.js
index 376ad9ec4..59f885232 100644
--- a/editions/test/tiddlers/tests/test-parsetextreference.js
+++ b/editions/test/tiddlers/tests/test-parsetextreference.js
@@ -124,7 +124,7 @@ describe("Wiki.parseTextReference tests", function() {
// Non-existent subtiddler of a plugin
expect(parseAndGetSource("$:/ShadowPlugin","text",null,"MyMissingTiddler")).toEqual(null);
// Plain text tiddler
- expect(parseAndGetSource("TiddlerNine")).toEqual(undefined);
+ expect(parseAndGetSource("TiddlerNine")).toEqual("this is plain text");
});
});
diff --git a/editions/test/tiddlers/tests/test-utils.js b/editions/test/tiddlers/tests/test-utils.js
index 8b7630a54..d41d5047a 100644
--- a/editions/test/tiddlers/tests/test-utils.js
+++ b/editions/test/tiddlers/tests/test-utils.js
@@ -188,4 +188,4 @@ describe("Utility tests", function() {
});
-})();
+})();
\ No newline at end of file
diff --git a/editions/test/tiddlers/tests/test-widget.js b/editions/test/tiddlers/tests/test-widget.js
index 2614d6f52..544ed928f 100755
--- a/editions/test/tiddlers/tests/test-widget.js
+++ b/editions/test/tiddlers/tests/test-widget.js
@@ -143,7 +143,7 @@ describe("Widget module", function() {
var wiki = new $tw.Wiki();
// Add a tiddler
wiki.addTiddlers([
- {title: "TiddlerOne", text: "<$transclude tiddler='TiddlerTwo'/>\n"},
+ {title: "TiddlerOne", text: "<$transclude tiddler='TiddlerTwo'/>"},
{title: "TiddlerTwo", text: "<$transclude tiddler='TiddlerOne'/>"}
]);
// Test parse tree
@@ -157,7 +157,7 @@ describe("Widget module", function() {
// Render the widget node to the DOM
var wrapper = renderWidgetNode(widgetNode);
// Test the rendering
- expect(wrapper.innerHTML).toBe("Recursive transclusion error in transclude widget\n");
+ expect(wrapper.innerHTML).toBe("Recursive transclusion error in transclude widget");
});
it("should deal with SVG elements", function() {
@@ -683,7 +683,7 @@ describe("Widget module", function() {
expect(wrapper.innerHTML).toBe("New value
");
});
- it("should can mix setWidgets and macros when importing", function() {
+ it("should support mixed setWidgets and macros when importing", function() {
var wiki = new $tw.Wiki();
// Add some tiddlers
wiki.addTiddlers([
@@ -699,6 +699,20 @@ describe("Widget module", function() {
expect(wrapper.innerHTML).toBe("Aval Bval Cval
");
});
+ it("should skip parameters widgets when importing", function() {
+ var wiki = new $tw.Wiki();
+ // Add some tiddlers
+ wiki.addTiddlers([
+ {title: "B", text: "<$parameters bee=nothing><$set name='B' value='Bval'>\n\ndummy text$set>$parameters>"},
+ ]);
+ var text = "\\import B\n<>";
+ var widgetNode = createWidgetNode(parseText(text,wiki),wiki);
+ // Render the widget node to the DOM
+ var wrapper = renderWidgetNode(widgetNode);
+ // Test the rendering
+ expect(wrapper.innerHTML).toBe("Bval
");
+ });
+
it("can have more than one macroDef variable imported", function() {
var wiki = new $tw.Wiki();
wiki.addTiddlers([
diff --git a/editions/test/tiddlers/tests/test-wikitext-parser.js b/editions/test/tiddlers/tests/test-wikitext-parser.js
index 7f1551c28..bc3d9acd8 100644
--- a/editions/test/tiddlers/tests/test-wikitext-parser.js
+++ b/editions/test/tiddlers/tests/test-wikitext-parser.js
@@ -19,7 +19,8 @@ describe("WikiText parser tests", function() {
// Define a parsing shortcut
var parse = function(text) {
- return wiki.parseText("text/vnd.tiddlywiki",text).tree;
+ var tree = wiki.parseText("text/vnd.tiddlywiki",text).tree;
+ return tree;
};
it("should parse tags", function() {
@@ -114,7 +115,70 @@ describe("WikiText parser tests", function() {
it("should parse macro definitions", function() {
expect(parse("\\define myMacro()\nnothing\n\\end\n")).toEqual(
- [ { type : 'set', attributes : { name : { type : 'string', value : 'myMacro' }, value : { type : 'string', value : 'nothing' } }, children : [ ], params : [ ], isMacroDefinition : true } ]
+ [{"type":"set","attributes":{"name":{"name":"name","type":"string","value":"myMacro"},"value":{"name":"value","type":"string","value":"nothing"}},"children":[],"params":[],"isMacroDefinition":true,"orderedAttributes":[{"name":"name","type":"string","value":"myMacro"},{"name":"value","type":"string","value":"nothing"}]}]
+
+ );
+ });
+
+ it("should parse procedure definitions with no parameters", function() {
+ expect(parse("\\procedure myMacro()\nnothing\n\\end\n")).toEqual(
+
+ [{"type":"set","attributes":{"name":{"name":"name","type":"string","value":"myMacro"},"value":{"name":"value","type":"string","value":"nothing"}},"children":[],"params":[],"orderedAttributes":[{"name":"name","type":"string","value":"myMacro"},{"name":"value","type":"string","value":"nothing"}],"isProcedureDefinition":true}]
+
+ );
+ });
+
+ it("should parse single line procedure definitions with no parameters", function() {
+ expect(parse("\\procedure myMacro() nothing\n")).toEqual(
+
+ [{"type":"set","attributes":{"name":{"name":"name","type":"string","value":"myMacro"},"value":{"name":"value","type":"string","value":"nothing"}},"children":[],"params":[],"orderedAttributes":[{"name":"name","type":"string","value":"myMacro"},{"name":"value","type":"string","value":"nothing"}],"isProcedureDefinition":true}]
+
+ );
+ });
+
+ it("should parse procedure definitions with parameters", function() {
+ expect(parse("\\procedure myMacro(one,two,three,four:elephant)\nnothing\n\\end\n")).toEqual(
+
+ [{"type":"set","attributes":{"name":{"name":"name","type":"string","value":"myMacro"},"value":{"name":"value","type":"string","value":"nothing"}},"children":[],"params":[{"name":"one"},{"name":"two"},{"name":"three"},{"name":"four","default":"elephant"}],"orderedAttributes":[{"name":"name","type":"string","value":"myMacro"},{"name":"value","type":"string","value":"nothing"}],"isProcedureDefinition":true}]
+
+ );
+ });
+
+ it("should parse procedure definitions", function() {
+ expect(parse("\\procedure myMacro(one:'Jaguar')\n<$text text=<>/>\n\\end\n\n")).toEqual(
+
+ [{"type":"set","attributes":{"name":{"name":"name","type":"string","value":"myMacro"},"value":{"name":"value","type":"string","value":"<$text text=<>/>"}},"children":[],"params":[{"name":"one","default":"Jaguar"}],"orderedAttributes":[{"name":"name","type":"string","value":"myMacro"},{"name":"value","type":"string","value":"<$text text=<>/>"}],"isProcedureDefinition":true}]
+
+ );
+
+ }); it("should parse function definitions with no parameters", function() {
+ expect(parse("\\function myMacro()\nnothing\n\\end\n")).toEqual(
+
+ [{"type":"set","attributes":{"name":{"name":"name","type":"string","value":"myMacro"},"value":{"name":"value","type":"string","value":"nothing"}},"children":[],"params":[],"orderedAttributes":[{"name":"name","type":"string","value":"myMacro"},{"name":"value","type":"string","value":"nothing"}],"isFunctionDefinition":true}]
+
+ );
+ });
+
+ it("should parse single line function definitions with no parameters", function() {
+ expect(parse("\\function myMacro() nothing\n")).toEqual(
+
+ [{"type":"set","attributes":{"name":{"name":"name","type":"string","value":"myMacro"},"value":{"name":"value","type":"string","value":"nothing"}},"children":[],"params":[],"orderedAttributes":[{"name":"name","type":"string","value":"myMacro"},{"name":"value","type":"string","value":"nothing"}],"isFunctionDefinition":true}]
+
+ );
+ });
+
+ it("should parse function definitions with parameters", function() {
+ expect(parse("\\function myMacro(one,two,three,four:elephant)\nnothing\n\\end\n")).toEqual(
+
+ [{"type":"set","attributes":{"name":{"name":"name","type":"string","value":"myMacro"},"value":{"name":"value","type":"string","value":"nothing"}},"children":[],"params":[{"name":"one"},{"name":"two"},{"name":"three"},{"name":"four","default":"elephant"}],"orderedAttributes":[{"name":"name","type":"string","value":"myMacro"},{"name":"value","type":"string","value":"nothing"}],"isFunctionDefinition":true}]
+
+ );
+ });
+
+ it("should parse function definitions", function() {
+ expect(parse("\\function myMacro(one:'Jaguar')\n<$text text=<>/>\n\\end\n\n")).toEqual(
+
+ [{"type":"set","attributes":{"name":{"name":"name","type":"string","value":"myMacro"},"value":{"name":"value","type":"string","value":"<$text text=<>/>"}},"children":[],"params":[{"name":"one","default":"Jaguar"}],"orderedAttributes":[{"name":"name","type":"string","value":"myMacro"},{"name":"value","type":"string","value":"<$text text=<>/>"}],"isFunctionDefinition":true}]
);
});
@@ -122,7 +186,7 @@ describe("WikiText parser tests", function() {
it("should parse comment in pragma area. Comment will be invisible", function() {
expect(parse("\n\\define aMacro()\nnothing\n\\end\n")).toEqual(
- [ { type : 'set', attributes : { name : { type : 'string', value : 'aMacro' }, value : { type : 'string', value : 'nothing' } }, children : [ ], params : [ ], isMacroDefinition : true } ]
+ [{"type":"set","attributes":{"name":{"name":"name","type":"string","value":"aMacro"},"value":{"name":"value","type":"string","value":"nothing"}},"children":[],"params":[],"isMacroDefinition":true,"orderedAttributes":[{"name":"name","type":"string","value":"aMacro"},{"name":"value","type":"string","value":"nothing"}]}]
);
});
@@ -143,38 +207,38 @@ describe("WikiText parser tests", function() {
it("should parse inline macro calls", function() {
expect(parse("<><><><>")).toEqual(
- [ { type: 'element', tag: 'p', start: 0, end: 35, children: [ { type: 'macrocall', start: 0, params: [ ], name: 'john', end: 8 }, { type: 'macrocall', start: 8, params: [ ], name: 'paul', end: 16 }, { type: 'macrocall', start: 16, params: [ ], name: 'george', end: 26 }, { type: 'macrocall', start: 26, params: [ ], name: 'ringo', end: 35 } ] } ]
+ [{"type":"element","tag":"p","children":[{"type":"transclude","start":0,"end":8,"attributes":{"$variable":{"name":"$variable","type":"string","value":"john"}},"orderedAttributes":[{"name":"$variable","type":"string","value":"john"}]},{"type":"transclude","start":8,"end":16,"attributes":{"$variable":{"name":"$variable","type":"string","value":"paul"}},"orderedAttributes":[{"name":"$variable","type":"string","value":"paul"}]},{"type":"transclude","start":16,"end":26,"attributes":{"$variable":{"name":"$variable","type":"string","value":"george"}},"orderedAttributes":[{"name":"$variable","type":"string","value":"george"}]},{"type":"transclude","start":26,"end":35,"attributes":{"$variable":{"name":"$variable","type":"string","value":"ringo"}},"orderedAttributes":[{"name":"$variable","type":"string","value":"ringo"}]}],"start":0,"end":35}]
);
expect(parse("text <>")).toEqual(
- [{ type: 'element', tag: 'p', start: 0, end: 92, children: [ { type: 'text', text: 'text ', start: 0, end: 5 }, { type: 'macrocall', name: 'john', start: 5, params: [ { type: 'macro-parameter', start: 11, value: 'val1', name: 'one', end: 20 }, { type: 'macro-parameter', start: 20, value: 'val "2"', name: 'two', end: 35 }, { type: 'macro-parameter', start: 35, value: 'val \'3\'', name: 'three', end: 52 }, { type: 'macro-parameter', start: 52, value: 'val 4"5\'', name: 'four', end: 73 }, { type: 'macro-parameter', start: 73, value: 'val 5', name: 'five', end: 89 } ], end: 92 } ] } ]
+ [{"type":"element","tag":"p","children":[{"type":"text","text":"text ","start":0,"end":5},{"type":"transclude","start":5,"end":92,"attributes":{"$variable":{"name":"$variable","type":"string","value":"john"},"one":{"name":"one","type":"string","value":"val1","start":11,"end":20},"two":{"name":"two","type":"string","value":"val \"2\"","start":20,"end":35},"three":{"name":"three","type":"string","value":"val '3'","start":35,"end":52},"four":{"name":"four","type":"string","value":"val 4\"5'","start":52,"end":73},"five":{"name":"five","type":"string","value":"val 5","start":73,"end":89}},"orderedAttributes":[{"name":"$variable","type":"string","value":"john"},{"name":"one","type":"string","value":"val1","start":11,"end":20},{"name":"two","type":"string","value":"val \"2\"","start":20,"end":35},{"name":"three","type":"string","value":"val '3'","start":35,"end":52},{"name":"four","type":"string","value":"val 4\"5'","start":52,"end":73},{"name":"five","type":"string","value":"val 5","start":73,"end":89}]}],"start":0,"end":92}]
);
expect(parse("ignored << carrots <>")).toEqual(
- [ { type: 'element', tag: 'p', start: 0, end: 27, children: [ { type: 'text', text: 'ignored << carrots ', start: 0, end: 19 }, { type: 'macrocall', name: 'john', start: 19, params: [ ], end: 27 } ] } ]
+ [{"type":"element","tag":"p","children":[{"type":"text","text":"ignored << carrots ","start":0,"end":19},{"type":"transclude","start":19,"end":27,"attributes":{"$variable":{"name":"$variable","type":"string","value":"john"}},"orderedAttributes":[{"name":"$variable","type":"string","value":"john"}]}],"start":0,"end":27}]
);
expect(parse("text <<>")).toEqual(
- [ { type: 'element', tag: 'p', start: 0, end: 14, children: [ { type: 'text', text: 'text ', start: 0, end: 5 }, { type: 'macrocall', name: '>")).toEqual(
- [ { type: 'element', tag: 'p', start: 0, end: 15, children: [ { type: 'text', text: 'before\n', start: 0, end: 7 }, { type: 'macrocall', start: 7, params: [ ], name: 'john', end: 15 } ] } ]
+ [{"type":"element","tag":"p","children":[{"type":"text","text":"before\n","start":0,"end":7},{"type":"transclude","start":7,"end":15,"attributes":{"$variable":{"name":"$variable","type":"string","value":"john"}},"orderedAttributes":[{"name":"$variable","type":"string","value":"john"}]}],"start":0,"end":15}]
);
// A single space will cause it to be inline
expect(parse("<> ")).toEqual(
- [ { type: 'element', tag: 'p', start: 0, end: 9, children: [ { type: 'macrocall', start: 0, params: [ ], name: 'john', end: 8 }, { type: 'text', text: ' ', start: 8, end: 9 } ] } ]
+ [{"type":"element","tag":"p","children":[{"type":"transclude","start":0,"end":8,"attributes":{"$variable":{"name":"$variable","type":"string","value":"john"}},"orderedAttributes":[{"name":"$variable","type":"string","value":"john"}]},{"type":"text","text":" ","start":8,"end":9}],"start":0,"end":9}]
);
expect(parse("text <>' >>")).toEqual(
- [ { type: 'element', tag: 'p', start: 0, end: 34, children: [ { type: 'text', text: 'text ', start: 0, end: 5 }, { type: 'macrocall', start: 5, params: [ { type: 'macro-parameter', start: 12, value: 'my <>', name: 'one', end: 31 } ], name: 'outie', end: 34 } ] } ]
+ [{"type":"element","tag":"p","children":[{"type":"text","text":"text ","start":0,"end":5},{"type":"transclude","start":5,"end":34,"attributes":{"$variable":{"name":"$variable","type":"string","value":"outie"},"one":{"name":"one","type":"string","value":"my <>","start":12,"end":31}},"orderedAttributes":[{"name":"$variable","type":"string","value":"outie"},{"name":"one","type":"string","value":"my <>","start":12,"end":31}]}],"start":0,"end":34}]
);
@@ -183,37 +247,37 @@ describe("WikiText parser tests", function() {
it("should parse block macro calls", function() {
expect(parse("<>\n<>\r\n<>\n<>")).toEqual(
- [ { type: 'macrocall', start: 0, name: 'john', params: [ ], end: 8, isBlock: true }, { type: 'macrocall', start: 9, name: 'paul', params: [ ], end: 17, isBlock: true }, { type: 'macrocall', start: 19, name: 'george', params: [ ], end: 29, isBlock: true }, { type: 'macrocall', start: 30, name: 'ringo', params: [ ], end: 39, isBlock: true } ]
+ [ { type: 'transclude', start: 0, attributes: { $variable: { name: "$variable", type: "string", value: "john" }}, orderedAttributes: [ { name: "$variable", type: "string", value: "john" }], end: 8, isBlock: true }, { type: 'transclude', start: 9, attributes: { $variable: { name: "$variable", type: "string", value: "paul" }}, orderedAttributes: [ { name: "$variable", type: "string", value: "paul" }], end: 17, isBlock: true }, { type: 'transclude', start: 19, attributes: { $variable: { name: "$variable", type: "string", value: "george" }}, orderedAttributes: [ { name: "$variable", type: "string", value: "george" }], end: 29, isBlock: true }, { type: 'transclude', start: 30, attributes: { $variable: { name: "$variable", type: "string", value: "ringo" }}, orderedAttributes: [ { name: "$variable", type: "string", value: "ringo" }], end: 39, isBlock: true } ]
);
expect(parse("<>")).toEqual(
- [ { type: 'macrocall', start: 0, name: 'john', params: [ { type: 'macro-parameter', start: 6, value: 'val1', name: 'one', end: 15 }, { type: 'macro-parameter', start: 15, value: 'val "2"', name: 'two', end: 30 }, { type: 'macro-parameter', start: 30, value: 'val \'3\'', name: 'three', end: 47 }, { type: 'macro-parameter', start: 47, value: 'val 4"5\'', name: 'four', end: 68 }, { type: 'macro-parameter', start: 68, value: 'val 5', name: 'five', end: 84 }], end: 87, isBlock: true } ]
+ [{"type":"transclude","start":0,"end":87,"attributes":{"$variable":{"name":"$variable","type":"string","value":"john"},"one":{"name":"one","type":"string","value":"val1","start":6,"end":15},"two":{"name":"two","type":"string","value":"val \"2\"","start":15,"end":30},"three":{"name":"three","type":"string","value":"val '3'","start":30,"end":47},"four":{"name":"four","type":"string","value":"val 4\"5'","start":47,"end":68},"five":{"name":"five","type":"string","value":"val 5","start":68,"end":84}},"orderedAttributes":[{"name":"$variable","type":"string","value":"john"},{"name":"one","type":"string","value":"val1","start":6,"end":15},{"name":"two","type":"string","value":"val \"2\"","start":15,"end":30},{"name":"three","type":"string","value":"val '3'","start":30,"end":47},{"name":"four","type":"string","value":"val 4\"5'","start":47,"end":68},{"name":"five","type":"string","value":"val 5","start":68,"end":84}],"isBlock":true}]
);
expect(parse("<< carrots\n\n<>")).toEqual(
- [ { type: 'element', tag: 'p', start : 0, end : 10, children: [ { type: 'text', text: '<< carrots', start : 0, end : 10 } ] }, { type: 'macrocall', start: 12, params: [ ], name: 'john', end: 20, isBlock: true } ]
+ [ { type: 'element', tag: 'p', start : 0, end : 10, children: [ { type: 'text', text: '<< carrots', start : 0, end : 10 } ] }, { type: 'transclude', start: 12, attributes: { $variable: {name: "$variable", type:"string", value: "john"} }, orderedAttributes: [ {name: "$variable", type:"string", value: "john"} ], end: 20, isBlock: true } ]
);
expect(parse("before\n\n<>")).toEqual(
- [ { type: 'element', tag: 'p', start : 0, end : 6, children: [ { type: 'text', text: 'before', start : 0, end : 6 } ] }, { type: 'macrocall', start: 8, name: 'john', params: [ ], end: 16, isBlock: true } ]
+ [ { type: 'element', tag: 'p', start : 0, end : 6, children: [ { type: 'text', text: 'before', start : 0, end : 6 } ] }, { type: 'transclude', start: 8, attributes: { $variable: {name: "$variable", type:"string", value: "john"} }, orderedAttributes: [ {name: "$variable", type:"string", value: "john"} ], end: 16, isBlock: true } ]
);
expect(parse("<>\nafter")).toEqual(
- [ { type: 'macrocall', start: 0, name: 'john', params: [ ], end: 8, isBlock: true }, { type: 'element', tag: 'p', start: 9, end: 14, children: [ { type: 'text', text: 'after', start: 9, end: 14 } ] } ]
+ [ { type: 'transclude', start: 0, attributes: { $variable: {name: "$variable", type:"string", value: "john"} }, orderedAttributes: [ {name: "$variable", type:"string", value: "john"} ], end: 8, isBlock: true }, { type: 'element', tag: 'p', start: 9, end: 14, children: [ { type: 'text', text: 'after', start: 9, end: 14 } ] } ]
);
expect(parse("<>")).toEqual(
- [ { type: 'macrocall', start: 0, params: [ { type: 'macro-parameter', start: 11, value: '\n\nwikitext\n', name: 'arg', end: 33 } ], name: 'multiline', end: 36, isBlock: true }]
+ [{"type":"transclude","start":0,"end":36,"attributes":{"$variable":{"name":"$variable","type":"string","value":"multiline"},"arg":{"name":"arg","type":"string","value":"\n\nwikitext\n","start":11,"end":33}},"orderedAttributes":[{"name":"$variable","type":"string","value":"multiline"},{"name":"arg","type":"string","value":"\n\nwikitext\n","start":11,"end":33}],"isBlock":true}]
);
expect(parse("<>' >>")).toEqual(
- [ { type: 'macrocall', start: 0, params: [ { type: 'macro-parameter', start: 7, value: 'my <>', name: 'one', end: 26 } ], name: 'outie', end: 29, isBlock: true } ]
+ [ { type: 'transclude', start: 0, attributes: { $variable: {name: "$variable", type:"string", value: "outie"}, one: {name: "one", type:"string", value: "my <>", start: 7, end: 26} }, orderedAttributes: [ {name: "$variable", type:"string", value: "outie"}, {name: "one", type:"string", value: "my <>", start: 7, end: 26} ], end: 29, isBlock: true } ]
);
});
@@ -221,23 +285,23 @@ describe("WikiText parser tests", function() {
it("should parse tricky macrocall parameters", function() {
expect(parse("<am>>")).toEqual(
- [ { type: 'macrocall', start: 0, params: [ { type: 'macro-parameter', start: 6, value: 'pa>am', end: 12 } ], name: 'john', end: 14, isBlock: true } ]
+ [{"type":"transclude","start":0,"end":14,"attributes":{"0":{"name":"0","type":"string","value":"pa>am","start":6,"end":12},"$variable":{"name":"$variable","type":"string","value":"john"}},"orderedAttributes":[{"name":"$variable","type":"string","value":"john"},{"name":"0","type":"string","value":"pa>am","start":6,"end":12}],"isBlock":true}]
);
expect(parse("< >>")).toEqual(
- [ { type: 'macrocall', start: 0, params: [ { type: 'macro-parameter', start: 6, value: 'param>', end: 13 } ], name: 'john', end: 16, isBlock: true } ]
+ [{"type":"transclude","start":0,"end":16,"attributes":{"0":{"name":"0","type":"string","value":"param>","start":6,"end":13},"$variable":{"name":"$variable","type":"string","value":"john"}},"orderedAttributes":[{"name":"$variable","type":"string","value":"john"},{"name":"0","type":"string","value":"param>","start":6,"end":13}],"isBlock":true}]
);
expect(parse("<>>")).toEqual(
- [ { type: 'element', tag: 'p', start: 0, end: 15, children: [ { type: 'macrocall', start: 0, params: [ { type: 'macro-parameter', start: 6, value: 'param', end: 12 } ], name: 'john', end: 14 }, { type: 'text', text: '>', start: 14, end: 15 } ] } ]
+ [{"type":"element","tag":"p","children":[{"type":"transclude","start":0,"end":14,"attributes":{"0":{"name":"0","type":"string","value":"param","start":6,"end":12},"$variable":{"name":"$variable","type":"string","value":"john"}},"orderedAttributes":[{"name":"$variable","type":"string","value":"john"},{"name":"0","type":"string","value":"param","start":6,"end":12}]},{"type":"text","text":">","start":14,"end":15}],"start":0,"end":15}]
);
// equals signs should be allowed
expect(parse("<=4 >>")).toEqual(
- [ { type: 'macrocall', start: 0, params: [ { type: 'macro-parameter', start: 6, value: 'var>=4', end: 13 } ], name: 'john', end: 16, isBlock: true } ]
+ [{"type":"transclude","start":0,"end":16,"attributes":{"0":{"name":"0","type":"string","value":"var>=4","start":6,"end":13},"$variable":{"name":"$variable","type":"string","value":"john"}},"orderedAttributes":[{"name":"$variable","type":"string","value":"john"},{"name":"0","type":"string","value":"var>=4","start":6,"end":13}],"isBlock":true}]
);
diff --git a/editions/test/tiddlers/tests/test-wikitext-tabs-macro.js b/editions/test/tiddlers/tests/test-wikitext-tabs-macro.js
index b37f402cc..39f061d11 100644
--- a/editions/test/tiddlers/tests/test-wikitext-tabs-macro.js
+++ b/editions/test/tiddlers/tests/test-wikitext-tabs-macro.js
@@ -1,7 +1,7 @@
/*\
title: test-wikitext-tabs-macro.js
type: application/javascript
-tags: [[$:/tags/test-spec]]
+tags: [[$:/tags/test-spec-disabled]]
Tests the core tabs macro by comparing the HTML output with a stored template.
Intended to permit future readability improvements.
@@ -74,7 +74,7 @@ describe("Tabs-macro HTML tests", function() {
expect(wiki.renderTiddler("text/html","test-tabs-macro-horizontal")).toBe(expected.fields.text.replace(/\n/g,""));
});
- it("should render 'horizontal' tabs from v5.2.2 and up with whitespace trim", function() {
+ it("should render all 'horizontal' tabs from v5.2.2 and up with whitespace trim", function() {
expect(wiki.renderTiddler("text/html","test-tabs-macro-horizontal-all")).toBe(expectedAll.fields.text.replace(/\n/g,""));
});
diff --git a/editions/tw5.com/tiddlers/Brackets.tid b/editions/tw5.com/tiddlers/concepts/Brackets.tid
similarity index 92%
rename from editions/tw5.com/tiddlers/Brackets.tid
rename to editions/tw5.com/tiddlers/concepts/Brackets.tid
index 529adb17e..fee36313c 100644
--- a/editions/tw5.com/tiddlers/Brackets.tid
+++ b/editions/tw5.com/tiddlers/concepts/Brackets.tid
@@ -10,5 +10,5 @@ WikiText syntax uses a number of different types of brackets. Their names are sh
|`()` |Round brackets |Parenthesis |Not used in WikiText |
|`[]` |Square brackets |Brackets |[[Links|Linking in WikiText]], [[Filters|Filters]] |
|`{}` |Curly brackets |Braces |[[Text references|TextReference]], [[Filtered attributes|HTML in WikiText]] |
-|`<>` |Angle brackets |Chevrons |[[HTML elements and widgets|HTML in WikiText]], [[Macros|Macros in WikiText]] |
+|`<>` |Angle brackets |Chevrons |[[HTML elements and widgets|HTML in WikiText]], [[Macros]] |
diff --git a/editions/tw5.com/tiddlers/concepts/Macros.tid b/editions/tw5.com/tiddlers/concepts/Macros.tid
index 1d06f9755..8377046f6 100644
--- a/editions/tw5.com/tiddlers/concepts/Macros.tid
+++ b/editions/tw5.com/tiddlers/concepts/Macros.tid
@@ -1,31 +1,42 @@
created: 20140211171341271
-modified: 20220505082754270
+modified: 20230419103154328
tags: Concepts Reference
title: Macros
type: text/vnd.tiddlywiki
-A <<.def macro>> is a named snippet of text. WikiText can use the name as a shorthand way of [[transcluding|Transclusion]] the snippet. Such transclusions are known as <<.def "macro calls">>, and each call can supply a different set of parameters that get substituted for special placeholders within the snippet.
+!! Introduction
-For the syntax, see [[Macros in WikiText]].
+A <<.def macro>> is a named snippet of text. They are typically defined with the [[Pragma: \define]]:
-Most macros are in fact just parameterised [[variables|Variables]].
+```
+\define my-macro(parameter:"Default value")
+This is the macro, and the parameter is $parameter$.
+\end
+```
-They are created using the `\define` [[pragma|Pragma]]. (Behind the scenes, this is transformed into a <<.wlink SetWidget>>, i.e. macros and variables are two sides of the same coin.)
+The name wrapped in double angled [[brackets|Brackets]] is used a shorthand way of [[transcluding|Transclusion]] the snippet. Such transclusions are known as <<.def "macro calls">>, and each call can supply a different set of parameters:
-The snippet and its incoming parameter values are treated as simple strings of characters with no WikiText meaning, at least until the placeholders have been filled in and the macro call has returned. This means that a macro can assemble and return the complete syntax of a ~WikiText component, such as a [[link|Linking in WikiText]]. (See [[Transclusion and Substitution]] for further discussion of this.)
+```
+<>
+<>
+```
-Within a snippet itself, the only markup detected is `$name$` (a placeholder for a macro parameter) and `$(name)$` (a placeholder for a [[variable|Variables]]).
+The parameters that are specified in the macro call are substituted for special placeholders within the snippet:
-The <<.mlink dumpvariables>> macro lists all variables (including macros) that are available at that position in the widget tree.
+* `$parameter-name$` is replaced with the value of the named parameter
+* `$(variable-name)$` is replaced with the value of the named [[variable|Variables]]).
-An <<.wlink ImportVariablesWidget>> widget can be used to copy macro definitions to another branch of the [[widget tree|Widgets]]. ~TiddlyWiki uses this technique internally to implement global macros -- namely any macros defined in tiddlers with the <<.tag $:/tags/Macro>> tag.
+<<.from-version "5.3.0">> Macros have been [[superseded|Macro Pitfalls]] by [[Procedures]], [[Custom Widgets]] and [[Functions]] which together provide more robust and flexible ways to encapsulate and re-use code. It is now recommended to only use macros when textual substitution is specifically required.
-The tag <<.tag $:/tags/Macro/View>> is used to define macros that should only be available within the main view template and the preview panel.
+!! How Macros Work
-The tag <<.tag $:/tags/Macro/View/Body>> is used to define macros that should only be available within the main view template body and the preview panel.
+Macros are implemented as a special kind of [[variable|Variables]]. The only thing that distinguishes them from ordinary variables is the way that the parameters are handled.
-For maximum flexibility, macros can also be <<.js-macro-link "written as JavaScript modules">>.
+!! Using Macros
-A similar effect to a parameterised macro call can be produced by setting [[variables|Variables]] around a [[transclusion|Transclusion]].
+* [[Macro Definitions]] describes how to create macros
+* [[Macro Calls]] describes how to use macros
+* [[Macro Parameter Handling]] describes how macro parameters work
+* [[Macro Pitfalls]] describes some of the pitfalls of using macros
+* [[Core Macros]] lists the built-in core macros
-~TiddlyWiki's core has [[several macros|Core Macros]] built in.
diff --git a/editions/tw5.com/tiddlers/concepts/Pragma.tid b/editions/tw5.com/tiddlers/concepts/Pragma.tid
index 3a9e1de12..868cf9667 100644
--- a/editions/tw5.com/tiddlers/concepts/Pragma.tid
+++ b/editions/tw5.com/tiddlers/concepts/Pragma.tid
@@ -1,24 +1,8 @@
+
created: 20150219175930000
-modified: 20230117112239663
-tags: Concepts [[WikiText Parser Modes]]
+modified: 20220122182842041
+tags:
title: Pragma
type: text/vnd.tiddlywiki
-A <<.def pragma>> is a special component of WikiText that provides control over the way the remaining text is parsed.
-
-Pragmas occupy lines that start with `\`. They can only appear at the start of the text, but blank lines are allowed between them. If a pragma line appears in the main body of the text, it is treated as if it was ordinary text.
-<<.from-version "5.2.6">> Pragmas can have preceding optional whitespace characters.
-
-
-The following pragmas are available:
-
-;`\define`
-: for defining a [[macro|Macros]]
-;`\rules`
-: for adjusting the set of rules used to parse the text
-;`\whitespace trim` or `\whitespace notrim`
-: <<.from-version "5.1.15">> Control whether whitespace is trimmed from the start and end of text runs (the default is ''notrim''). This setting can be useful when the whitespace generated by linebreaks disturbs formatting
-;`\import `
-: <<.from-version "5.1.18">> Import macro definitions from tiddlers identified by a filter expression
-;`\parsermode block` or `\parsermode inline`
-: <<.from-version "5.2.4">> Adjust whether the remaining text is parsed in block mode or inline mode.
\ No newline at end of file
+See [[Pragmas]].
diff --git a/editions/tw5.com/tiddlers/features/StartupActions.tid b/editions/tw5.com/tiddlers/features/StartupActions.tid
index 79a23b3d9..29edb8378 100644
--- a/editions/tw5.com/tiddlers/features/StartupActions.tid
+++ b/editions/tw5.com/tiddlers/features/StartupActions.tid
@@ -33,7 +33,7 @@ The initial startup actions are useful for customising TiddlyWiki according to e
<$action-setfield $tiddler="$:/language" text={{{ [[$:/languages/en-GB]] [plugin-type[language]sort[description]removeprefix[$:/languages/]] +[prefix{$:/info/browser/language}] ~[[en-GB]] +[addprefix[$:/languages/]] }}}/>
```
-Note that global macros are not available within initial startup action tiddlers by default. If you need to access them then you'll need to explicitly include them with an ''import'' [[pragma|Pragma]] at the top of the tiddler:
+Note that global macros are not available within initial startup action tiddlers by default. If you need to access them then you'll need to explicitly include them with an [[Pragma: \import]] at the top of the tiddler:
```
\import [[$:/core/ui/PageMacros]] [all[shadows+tiddlers]tag[$:/tags/Macro]!has[draft.of]]
diff --git a/editions/tw5.com/tiddlers/filters/function.tid b/editions/tw5.com/tiddlers/filters/function.tid
new file mode 100644
index 000000000..f86d21f4f
--- /dev/null
+++ b/editions/tw5.com/tiddlers/filters/function.tid
@@ -0,0 +1,21 @@
+caption: function
+created: 20220909111836951
+modified: 20230419103154328
+op-input: a [[selection of titles|Title Selection]] passed as input to the function <<.place F>>
+op-output: the [[selection of titles|Title Selection]] returned from the function <<.place F>>
+op-parameter: first parameter is the [[function name|Functions]], subsequent parameters are passed to the function by position
+op-parameter-name: F
+op-purpose: apply a [[function|Functions]] to the input list, and return the result
+tags: [[Filter Operators]]
+title: function Operator
+type: text/vnd.tiddlywiki
+
+<<.from-version "5.3.0">> The <<.op function>> operator applies a named [[function|Functions]] to the input titles, and returns the results from the function. The function is invoked once with all of the input titles (in contrast, the [[filter Operator]] invokes its function separately for each input title).
+
+The first parameter of the <<.op function>> operator specifies the name of the function to be called. Subsequent parameters are passed to the function.
+
+The mapping between the parameters is //positional//, with each consecutive parameter specified in the function call mapped to the corresponding parameter in the function definition. Any parameters that are not provided are given their default values.
+
+<<.tip "Compare with the similar [[filter|filter Operator]] and [[subfilter|subfilter Operator]] operators which take a filter strings as their parameter instead of a named function, and does not permit parameters to be passed">>
+
+<<.operator-examples "function">>
diff --git a/editions/tw5.com/tiddlers/functions/Functions.tid b/editions/tw5.com/tiddlers/functions/Functions.tid
new file mode 100644
index 000000000..3b06dddc7
--- /dev/null
+++ b/editions/tw5.com/tiddlers/functions/Functions.tid
@@ -0,0 +1,28 @@
+created: 20221009124003601
+modified: 20230419103154328
+tags: Concepts Reference
+title: Functions
+type: text/vnd.tiddlywiki
+
+!! Introduction
+
+<<.from-version "5.3.0">> A <<.def function>> is a named snippet of text containing a [[Filter Expression]]. Functions can have named parameters which are available within the function as variables.
+
+Functions are usually defined with the [[Pragma: \function]]:
+
+```
+\function my-function(parameter:"2")
+[multiply[1.5]]
+\end
+```
+
+Functions can be invoked in several ways:
+
+* Directly transclude functions with the syntax `<>`
+* Assign functions to widget attributes with the syntax `>>`
+* Invoke functions via the [[function Operator]] with the syntax `[function[myfn],[value],...]`
+* Directly invoke functions whose names start with a period as custom filter operators with the syntax `[.myfn[value]]`
+
+!! How Functions Work
+
+Functions are implemented as a special kind of [[variable|Variables]]. The only thing that distinguishes them from ordinary variables is the way that the parameters are handled.
diff --git a/editions/tw5.com/tiddlers/howtos/Visible Transclusions.tid b/editions/tw5.com/tiddlers/howtos/Visible Transclusions.tid
new file mode 100644
index 000000000..e3f46440e
--- /dev/null
+++ b/editions/tw5.com/tiddlers/howtos/Visible Transclusions.tid
@@ -0,0 +1,14 @@
+created: 20220909111836951
+modified: 20230419103154329
+tags: Learning
+title: Visible Transclusions
+type: text/vnd.tiddlywiki
+
+!! Visible Transclusions
+
+Block transclusions are shown in red, and inline transclusions are shown in green.
+
+<$button>
+<$action-setfield $tiddler="$:/temp/VisibleTransclusions" tags="$:/tags/Macro/View/Body" text={{$:/core/ui/VisibleTransclude}}/>
+Click here to make transclusions visible within story river tiddlers
+$button>
diff --git a/editions/tw5.com/tiddlers/images/New Release Banner.png b/editions/tw5.com/tiddlers/images/New Release Banner.png
index 74e2557d8b3766f57e98daf4f2a1616324161bc2..b75c8fc193d00967836f6096d3517e528ac2fcdd 100644
GIT binary patch
literal 50280
zcmXteby(By_x@-}=@Am6b7LSOB2uHfJ4Sa1I(XAC91NrxA=1q#X=z5QG)O5a0)l{|
zQu>>p-}U|Db?v(L$Mc-mdEMuJ?)y9^(NJH5nu3i2002;HX{s3m0E7|%0KvmMr1*b^
zKfZzDf5-zgt)2h?l=S}|g1L>z8vuYCprxj4ig@|8H}oZkM#!(*qvp1^9(-18$2o2;
z6QdaxP4D2$apX==D@&pnK~nlVS|63}@bUT66Y}vH5Gw4F(`wpt$G-zn>Ht(N3ERnK
z
%?p5f
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