1
0
mirror of https://github.com/Jermolene/TiddlyWiki5 synced 2026-02-08 03:00:21 +00:00

Compare commits

..

101 Commits

Author SHA1 Message Date
Jeremy Ruston
a83855329a Minor docs improvement 2026-02-06 16:29:50 +00:00
Jeremy Ruston
364e2f2cb5 Merge branch 'master' into background-actions 2026-02-06 16:25:04 +00:00
Jeremy Ruston
67f13c585d Fix #9055 docs issue identified in https://github.com/TiddlyWiki/TiddlyWiki5/pull/9055#pullrequestreview-3732792103 2026-02-06 16:24:14 +00:00
Jeremy Ruston
6e85d99ed5 Add changenote 2026-02-06 14:54:26 +00:00
Jeremy Ruston
5791627a50 Initial docs from #8702
...which need to also be turned into a changenote
2026-02-06 14:18:51 +00:00
Jeremy Ruston
32405c3a71 Initial commit cherry picked from #8702 2026-02-06 14:11:05 +00:00
Mario Pietsch
1bbb7fd53b Add start- and endactions to link-widget, list-links-draggable and list-tagged-draggable macros (#9621)
* Add start- endactions to link-widget + documentation

* Add start- endactions to list-links-draggable and list-tagged-draggable macros

* Add changenote

* Update docs with actionTiddler info
2026-02-06 14:23:42 +01:00
Saq Imtiaz
599933c34d Remove 'tiddlywiki/dom-to-image' dependency
Removed 'tiddlywiki/dom-to-image' from the tiddlywiki.info
2026-02-06 12:01:22 +01:00
Simon Huber
b1ccb82e0a Update body-toolbar-button - use value={{{ [subfilter{!!button-classes}] :and[join[ ]] }}} (#9585)
* Update body-toolbar-button - use `filter={{!!button-classes}}`

* Update body-toolbar-button.tid

* Create #9566 - button-classes.tid

* Update and rename #9566 - button-classes.tid to #9585 - button-classes.tid

* :and[join[ ]]

* Update how-to guide to mention button-classes
2026-02-06 11:50:43 +01:00
Mohammad Rahmani
ea648c7d15 Update StoryList.tid to Work in Different Layouts (#9094)
* Update StoryList.tid to Work in Different Layouts

See https://talk.tiddlywiki.org/t/stay-organized-by-using-multiple-desktops-in-tiddlywiki/12752/7

All PageTemplate elements are enclosed within that `$navigator` widget, so it should be possible to use those two variables in filters instead of hard-coding the values.

* Apply suggestion from @saqimtiaz

---------

Co-authored-by: Saq Imtiaz <saq.imtiaz@gmail.com>
2026-02-06 11:46:18 +01:00
Jeremy Ruston
a3a4e91751 New dom-to-image plugin for saving DOM nodes as an image (#8810)
* Add tm-save-dom-to-image message

* Temporarily include the geospatial plugin in the Netlify previews

* Scale should default to 1x

* Fix saving SVG images

* Add example of saving in SVG format

* Add library version number

* Document peculiarities of JPEG quality parameter

* Allow format="jpg" as well as the more technically correct "jpeg"

* Document what happens if the selector returns multiple DOM nodes

* Refactor image-to-dom to be a separate plugin

* Add support for oncompletion handler

* Remove ELS marker

Thanks @ericshulman
2026-02-06 10:42:40 +00:00
Mario Pietsch
c96d398712 remove empty.hta from tiddlywiki.info build targets (#9633) 2026-02-06 10:11:23 +00:00
Saq Imtiaz
821dcaf002 Ensures that getting variable info for a function doesnt pollute parent widget (#9639)
* fix: ensure getting variable info for a function doesnt pollute parent widget

* fix: makeFakeWidget should not pollute upstream
2026-02-06 11:10:47 +01:00
Jeremy Ruston
9247a87e11 Missing release note for #8810 2026-02-06 09:09:42 +00:00
Saq Imtiaz
bda54b0ad5 Adds pointer capture and disabling support to eventcatcher widget (#9609)
* feat: eventcatcher with pointer capture support

* fix: various cleanups and improvements

* chore: lint

* chore: lint

* docs: updated eventcatcher docs

* docs: added changenote

* feat: provide access to event properties via JSON blob

* fix: added updated utils file

* fix: guard against missing nodes and not element nodes
2026-02-06 09:16:34 +01:00
Théophile Desmedt
cd8b1faa74 UI: Show cascade filter details in Advanced info tab (#9634)
* UI: Show details on cascade filters in the "Advanced info" tab

* Update CascadeInfo.tid with new formatting

* Remove codeblock for conciseness

* Add change note for #9634

* Refactor CascadeInfo.tid for active filters

Improve filter logic and names of variable

* Change heading names

Updated the CascadeInfo.tid to change 'Active Filter Condition' to 'Active Cascade Filter' and adjusted the corresponding variables.
2026-02-05 19:47:15 +00:00
Saq Imtiaz
0673426f5a fix: remove fn wrappers and unneeded comments (#9637) 2026-02-05 17:06:32 +01:00
Saq Imtiaz
d376ada241 Adds a widget destroy method (#9097)
* feat: widget destroy method

* fix: revert to original implementation

* Invoke onDestroy method during widget destruction

Add custom cleanup method call in destroy process

* Update documentation for destroy method options

* Fix formatting in widget.js comments

* Refactor destroyChildren method for formatting

* Fix indentation in findNextSiblingDomNode method

* docs: added changenote

* chore: lint

* fix: remove deprecated utils method
2026-02-05 17:03:40 +01:00
Mario Pietsch
33b2f514fb Allow title, tags, text as focus for editing existing tiddlers (#9214)
* Allow title, tags, text as focus for editing existing tiddlers

* Add release note

* Update description field

* refresh select widget, it .default parameter is changed

* remove default variable, because it is not needed anymore. select widget refresh handling deals with it

* Undo select widget changes
2026-02-04 13:41:40 +01:00
Saq Imtiaz
46fe3ca988 Revert "Fix RSOE from filter operator errors (#9496)" (#9630)
This reverts commit 86c4770a28.
2026-02-04 12:53:14 +01:00
XLBilly
bf7c0b575c Fix side effects of PR 9316 (#9568)
* Fix color transition not working

* Fix plugin install button

* Fix remove tag button

* Update change note
2026-02-04 12:43:40 +01:00
XLBilly
b236373064 Intergrate tiddlywiki palette colors and settings to custom CSS properties (#9333)
* Expose tiddlywiki palette colors and settings to custom CSS properties

* Rename & use --tc prefix

* Add colors and CSS  settings

* Make CSS settings properties' name readable

* Add all CSS settings

* Add all palette colors

* Remove macrocallblock rule

* Indent with tabs

* Use --tc-color prefix

* Update docs

* Use --tpc prefix

* Add change note

* Simplify palette color rules with list widget

* Update docs

* Hardcode custom properties

* Update docs

* Remove cascades

* Update docs

* Add more docs

* Update docs

* Update docs

* Add --tp-animation-duration

* Update change note
The previous example actually doesn't work at all

* Update docs about limits of CSS variables
2026-02-04 11:37:04 +00:00
Mario Pietsch
d15398fc09 Simple TOC level parameter (#9612)
* Add toc level parameter

* Update TOC documentation

* Add toc level release note

* Update releasenote number and GH links

* Update releasenote number and GH links
2026-02-04 11:26:31 +00:00
Jeremy Ruston
6bc77cf3e2 Dynamic parameters for macro/procedure/function calls (#9055)
* Initial commit

The idea is to extend the macro call syntax to accept dynamic parameter values (ie thing:{{more}} etc). Eventually, this will work in all the contexts in which the double angle bracket syntax is valid.

This initial commit gets the tests passing, but doesn't yet activate the new functionality.

* Test for standalone macro calls with dynamic parameters

* Parse attribute macros with the new parser

This fixes the tests

* Test for attribute macros

* Add some examples

* Tweak examples

* Fix test

* Temporarily disable a broken serializer test

* Fix/dynamic macro calls test (#9459)

* Revert "Temporarily disable a broken serializer test"

This reverts commit b3144300ee.

* restore synamic parameter parse result

* lint

* lint

* remove duplicate

* Update core/modules/parsers/parseutils.js

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

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

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

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

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

* fix: mixed qouted and unquoted

---------

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

* Fix unneeded diff

* Minor docs update

* Genuflecting to the linter

* Remove debug logging

* Add change note

* Allow single closing square brackets within double square brackets quoted strings

* Only allow new style parameter values if the separator is an equals sign

* On reflection, new style values should not be allowed for anonymous parameters

Backwards compatibility

* Docs updates

* Docs updates

---------

Co-authored-by: lin onetwo <linonetwo012@gmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-04 11:24:06 +00:00
XLBilly
dc764b3a4a Replace fill rules in tw5.com edition (#9624) 2026-02-04 11:22:32 +00:00
XLBilly
9c09841eda Make draft title translatable (#8891)
* Make draft title translatable

* Update change note

* Improve number handling
Now generates "Draft of '...' 2" instead of "Draft 2 of '...'"

* Improve whitespace handling
We no longer needs to add whitespace in languages. We now handle it in javascript.

* Update language naming

* Update zh-Hans translation

* Update change note

* Refactor logic to make it less complex
Since we don't need to care about draft numbers, we can have two different templates for draft title with and without attribution. No need to trim the string now.
Also, we can reuse the getSubstitutedText method

* Update translators edition

* fixup! Update translators edition

* Switch to transclude and variable mechanism

* Adapt translators to the new mechanism

* Update change note

* Further simplify the logic
$tw.language.getString can already wikify translatable strings. No need for this.renderText
2026-02-04 11:21:54 +00:00
Cameron Fischer
9d5be2e9f8 Recurse exception handling to better handle '{{}}' in place of recently installed fix (#9548)
* Introduced preliminary idea for infinite recurse exception

* Better handling of infinite recursion

But it could be better still...

* the TransclusionError is a proper error

Moved the magic number to be on the error's class. Not sure if that's
a great idea.

* Fixed minor minor issue that came up in conflict

The minor fix to the jasmine regexp that escaped a '+' somehow
broke some random test.

* Removing patch fix for recursion errors

* Fixed issue where buttton and other widgets don't clean up

* Added release notes for #9548

* Update test-widget.js

If I don't fix those indentations, the entire TW codebase will explode or soemthing.

* Update test-widget.js

These lint problems are wasting my time.

* Fixed all core widgets to not leak when renderChildren fails

* Updated release notes to reflect what I'm actually fixing

* Update test-widget.js

Added warning not to use for-of loop for defining tests. The iterating variable needs to have its own method scope, or it risks being the same value for all tests.
2026-02-04 11:21:16 +00:00
lin onetwo
486d3bd326 Fix sitemap plugin can't provide xml content type in GET route (#9203)
* Update get-tiddler-html.js

* Create #9203.tid
2026-02-04 11:19:56 +00:00
XLBilly
196683915c Replace window.eval with Function (#9611)
* Replace window.eval with Function

* Add back comment
2026-02-04 11:17:10 +00:00
yaisog
42a3928960 Refactor code for EditTemplate fields (#9582)
* Refactored fields.tid

* Add changenote

* Optimize indentation and remove unneeded variable

* Consolidate some variables, procedures and functions

* Streamlined conditional and reveal structures

* Eliminate the fieldmangler widget

* Add tc-edit-field-exists to new field name input if applicable

* Call save-tiddler-actions on Ctrl-Enter in name input field
2026-02-04 11:13:53 +00:00
Saq Imtiaz
2e76cc08a1 Extends image widget with support for data-attributes and on load actions (#9050)
* feat: support for data-attributes and on load actions

* feat: support for data-attributes and on load actions

* fix: typo

* fix: simplify variable assignment

* docs: added changenote
2026-02-04 11:12:58 +00:00
Saq Imtiaz
891e4fcb2b Fixes a regression in correctly evaluating default param values for functions (#9614)
* Fix: bug in multivalue default params

* Enhance release notes for version 5.4.0

Updated GitHub links and contributors for release notes.

* Add FunctionDefaultValues test for default parameters

* Update function default values in FunctionDefaultValues.tid

* Update #8972.tid

* fix: correctly resolve default values for functions

* Update #8972.tid
2026-02-04 11:12:42 +00:00
Saq Imtiaz
f6fd5ff261 Fixes bug in correctly resolving functions in text substitutions (#9598)
* fix: correctly resolve functions in text substitutions

* docs: added changenote

* fix: remove commented code
2026-02-04 11:12:16 +00:00
XLBilly
526aaa3db8 Bump katex to newest version (#9626)
* Bump katex to newest version

* Update fonts
Also migrate to woff2

* Update readme

* Update change note
2026-02-04 10:11:09 +01:00
XLBilly
455f1be3fb Fix missing semicolon in Snow White (#9625)
* Fix missing comma in Snow White

* Update change note
2026-02-04 10:09:39 +01:00
Mohammad Rahmani
bffa0bb95a Update Documentation for list-tagged-draggable and list-links-draggable. (#9553)
* Update documents for list-links-draggable Macro.tid

add : `<<.from-version 5.4.0>> `

* Update documentation for list-tagged-draggable Macro.tid

Added `<<.from-version 5.4.0>> `

* Update list-links-draggable Macro.tid

Correct extra space and remove colon

* Remove duplicate title view field in list.tid

Removed redundant view field for title in list.tid.
This PR should fix issue reported here: https://github.com/TiddlyWiki/TiddlyWiki5/issues/9555
2026-02-04 10:09:02 +01:00
Jeremy Ruston
a1ef2ef6d4 Merge branch 'tiddlywiki-com' 2026-01-29 14:27:59 +00:00
Saq Imtiaz
75edd9b488 Reverts change to getLocationHash utils method (#9622)
* fix: reverted change to getLocationHash utils method

* docs: update changenote
2026-01-26 16:03:50 +01:00
Mario Pietsch
cde9c931c8 Select widget handle default parameter refresh (#9617)
* select widget handle default parameter refresh

* Add change note
2026-01-25 19:58:52 +01:00
Saq Imtiaz
d07fe25cdb feat: extend fakedom implementation (#9616)
* feat: extend fakedom implementation

* docs: updated changenote
2026-01-25 19:05:46 +01:00
KiXaM_刻む
a40ce29451 Add an option to enable CORS (#9277)
* Add an option to disable CORS

* change 'disable' with 'enable' CORS, because that is what this option actually does

* add a change note

* typo
2026-01-25 17:04:16 +01:00
buggyj
75647eb623 Fixes #8092 SelectWidget does not work with multiple options organise… (#8093)
* Fixes #8092 SelectWidget does not work with multiple options organised into group

* Consolidate variables

* Apply suggestion from @saqimtiaz

* Update core/modules/widgets/select.js

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

* added release note

* chore: lint fixes

---------

Co-authored-by: Saq Imtiaz <saq.imtiaz@gmail.com>
Co-authored-by: Mario Pietsch <pmariojo@gmail.com>
2026-01-25 17:02:18 +01:00
Mario Pietsch
70b4557738 [DOCS] Add link to "TiddlyWiki Archive" tiddler to the TiddlyWiki Releases info (#9613) 2026-01-25 16:59:54 +01:00
Mario Pietsch
efe58e41bc [DOCS] Fix some typos in "days Operator (Examples)" (#9479)
* [DOCS] Fix some typos in "days Operator (Examples)"

* Remove created and modified fields from days.tid

Remove created and modified fields from days.tid
2026-01-21 09:09:14 +01:00
Saq Imtiaz
eb3a80968e docs: changenotes for previously merged PRs (#9599)
* docs: changenotes for previously merged PRs

* fix: added text field for changenotes
2026-01-21 09:08:50 +01:00
Mario Pietsch
62ae4b24bc allow space after const {}. Set keyword spacing to warning (#9589) 2026-01-20 22:03:43 +01:00
yaisog
ded76aa84f Fixes Ctrl-Enter not working in EditTemplate tag name input (#9600)
* Assign save-tiddler-actions to inputAcceptVariantActions

* Add changenote
2026-01-20 20:22:42 +01:00
Mario Pietsch
79e3d14698 [DOCS] Make TaskManagementExamples more "hackable" (#9482)
* [DOCS] Make TaskManagementExamples more "hackable"

* Update modified date in TaskManagementExample.tid

* Fix modified date in TaskManagementExampleDraggable
2026-01-20 13:23:17 +01:00
superuser-does
763d717a13 Improvements to DateFormat tiddler (#9583) 2026-01-20 13:18:24 +01:00
XLBilly
e42ed6808e Split escapecss into two platforms (#9475)
* Split escapecss into to platforms

This will reduce core size slightly

* Update change note
2026-01-20 12:13:48 +01:00
yaisog
844564180f Fix LetWidget to always set all staged variables on first render (#9494)
* Always set all staged variables on first render

* Add changenote
2026-01-20 10:24:56 +00:00
Saq Imtiaz
a670de0e95 Fixes regressions in list-tagged-draggable (#9596)
* Fix regressions in list-tagged-draggable

* Remove trailing newline from list.tid
2026-01-20 10:01:02 +01:00
Bram Chen
faee49ee01 Update chinese language files (#9576)
* Update chinese language files
* add chinese translations for alert aria message

* Update chinese language files
* update related change note
2026-01-20 07:33:05 +01:00
yaisog
dd20be49f0 Make impact note for #9337 less verbose / repetitive (#9481)
* Make impact note less verbose / repetitive for #9337

* Correct some fields
2026-01-20 07:16:21 +01:00
Simon Huber
a27f74bbdc Fixes bug in sidebar tab Open to allow disabling drag and drop (#9504)
the variable is called `tv-enable-drag-and-drop` - not `tv-allow-drag-and-drop`
2026-01-20 07:06:29 +01:00
Jeremy Ruston
ae4e99951a Revert #9554 Refactor stylesheets in single <style> tags
These changes break the palette switcher
2026-01-18 11:40:10 +00:00
Mario Pietsch
be84dee26b Allow unused variables in function headers and caught exceptions (#9588) 2026-01-17 17:51:51 +00:00
XLBilly
00e17874f0 Add stylesheet wiki information (#9565)
* Add stylesheet wiki information

* Use 2 spaces

* Update change note

* Use ∈ symbol instead
I think this one is less confusing than the previous one
2026-01-17 15:00:32 +00:00
XLBilly
9041f099a3 Refactor stylesheets in single <style> tags (#9554)
* Create RootStylesheet.tid

* put stylesheets into single <style> tags

* viewHandler should not be a widget

* format RootStylesheet

* add ROOT_STYLESHEET_TITLE constant

* use let widget in RootStylesheet

* use view widget from #8135 for testing

* reformat RootStylesheet

* update view widget

* update view widget

* update view widget

* update view widget

* update view widget

* update view widget

* trying to fix the date parsing

* refactor view widget with more extensible architecture

* remove performance instrumentation

* hardcode reset and base stylesheets

* adopt ES2017 syntax for view widget

* don't hardcode vanilla/base

* remove unused constructors

* remove unused variable

* make refresh method static

* trying to fix missingThis

* add changenote

* revert change in vanilla/base

* Revert changes to the view widget

* Refactor RootStylesheet.tid
* Use transclude widget instead of view widget
* Remove hardcoded noramlize.css

* Add comment for debugging

* Migrate some templates to use RootStylesheet

* Remove PAGE_STYLESHEET_TITLE
Since it is no longer used in render.js

* Update change note

---------

Co-authored-by: Simon Huber <huber.simon@protonmail.com>
2026-01-17 14:59:10 +00:00
yaisog
3ba31be2a8 Add the words and lines modes to $diff-text (#9551)
* Initial commit

* Add line ending configuration for consistent tests

* Initial commit

* Correctly consider editcost parameter

* Move diffPartsToChars() to $tw.utils

* Remove superfluous file

* Correct "efficiency" parameter naming in the documentation

The parameter was incorrectly referred to as "efficent" in several places.

* Update diffPartsToChars to ES2017 style

* Consolidate let/const declarations
2026-01-17 14:57:52 +00:00
XLBilly
99d8afd515 Bump markdown-it to latest version (#9513)
* Bump markdown-it to newest version

* Update change note
2026-01-17 14:56:16 +00:00
Jeremy Ruston
2ab5f26644 Merge branch 'tiddlywiki-com' 2026-01-16 17:36:54 +00:00
Mario Pietsch
419fe68ee2 [Docs] Improve tag-pill documentation and examples (#9580)
* [Docs] Improve tag-pill documentation and examples

* Apply suggestion from @saqimtiaz

---------

Co-authored-by: Saq Imtiaz <saq.imtiaz@gmail.com>
2026-01-14 14:04:34 +01:00
XLBilly
0e765bdbdb Make alert aria message translatable (#9575)
* Make alerts aria message translatable

* Update change notes

* Update change note
2026-01-12 10:21:35 +01:00
Jeremy Ruston
855d8a9638 Fix images loaded from _canonical_uri tiddlers do not have loading and error classes assigned (#9570)
* Fix images loaded from _canonical_uri tiddlers do not have loading and error classes assigned

* Create changenote

* Joyously fix eslint error
2026-01-10 14:50:31 +00:00
Jeremy Ruston
afcf108d29 Add changenote 2026-01-10 14:42:58 +00:00
Jeremy Ruston
8f9acc0ca2 Fix missing file extension 2026-01-10 14:35:26 +00:00
Jeremy Ruston
5e4b8fbb3c Additional fix for #9177 2026-01-08 18:20:57 +00:00
lin onetwo
8e301178a4 Feat/view button condition (#9466)
* feat: support condition field on $:/tags/ViewToolbar button

* Delete convert-markdown.tid

* Create #9466.tid

* Update title.tid
2026-01-08 13:34:58 +01:00
Cameron Fischer
a72d3a09bf Quick fix to some deprecated class methods (#9561) 2026-01-08 10:13:16 +01:00
Jeremy Ruston
56634ffe29 Merge branch 'tiddlywiki-com' 2026-01-05 16:36:49 +00:00
Jeremy Ruston
24c317e1ab Revert "Add macro operator (#9520)"
This reverts commit 3c8ee86e23.
2026-01-05 16:18:01 +00:00
Jeremy Ruston
07329c6849 Revert "Root stylesheet refresh - Stylesheets in single <style> tags (#8130)"
This reverts commit da41a55f29.
2026-01-05 16:05:15 +00:00
yaisog
47ab3476f6 Improve LogWidget and ActionLogWidget documentation (#9550) 2026-01-05 14:31:11 +01:00
Mario Pietsch
f0e64660f2 [DOCS] Add multi-columns class to Widgets in Wikitext (#9516) 2026-01-04 21:45:19 +00:00
Mohammad Rahmani
9663e65f4b list-tagged-draggable to use title field by default (#9177)
* Revert the list-tagged-draggable to use title field by default

* Update list.tid

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

* Update list.tid

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

* Update list.tid

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

* Create #9177

change note for #9177 was created

* Update list.tid

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

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

* Update list-tagged-draggable Macro.tid

Update docs to reflect new displayField parameter

* Update list-links-draggable Macro.tid

Update documentation to reflect the new displayField parameter.

* Update list-tagged-draggable Macro.tid

Update docs. Remove formatting error in doc.
2026-01-04 21:41:38 +00:00
Mario Pietsch
7cb422242a DOCS - Fix typo in Date Fields (#9518) 2026-01-04 21:36:57 +00:00
Jeremy Ruston
f075f24e6b Merge branch 'tiddlywiki-com' 2026-01-04 20:48:58 +00:00
superuser-does
5fa1098c03 tw5.com: remove duplicate text from Forums tiddler (#9523) 2026-01-04 18:15:37 +00:00
superuser-does
92dc927c7b docs: savetiddlers extension is now Firefox-only (#9524)
* tw5.com: removed Chrome browser from savetiddlers

Was deprecated in early 2025, and is now Firefox-only

Additional changes:
* Lowercased buggyj in line with how the name is used elsewhere on tw5.com and the rest of the internet
* Pulled url from the url field (following the example of Timimi resource tiddler)
* Changed formatting of title to match other community resources

* tw5.com: update French & Japanese savetiddlers plugin docs to note it is now Firefox-only

Please note this update was validated using machine translation
2026-01-04 18:12:56 +00:00
XLBilly
98a61f01bb Add locale support for sort operator (#9400)
* Locale support for sort operator

* Add checkLanguageCode util function

* Update docs & add language code validation

* Replace multiple isDescending with reverse

* Revert "Replace multiple isDescending with reverse"

This reverts commit 793177b8bc.

* Simplify sortTiddler with Intl.Collator

* Add change notes

* Remove comment

* Update makeCompareFunction to support locale

* Update checkLanguageCode

* Add locale support for sortsub

* Add locale support for sort filter run prefix

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

This reverts commit 9479a156d7.

* Remove checkLanguageCode
Since filters are able to catch errors now

* Update locale example

* Revert " Add locale support for sortsub"

This reverts commit 4a617188fc.

* Revert "Update makeCompareFunction to support locale"

This reverts commit 0ebca08036.

* Update docs

* Update change note
2026-01-04 18:03:54 +00:00
superuser-does
f3fa69e229 Docs: rename 'definition lists' to 'description lists' (#9535)
* docs: Definition Lists to definition lists

* tw5.com: rename Definition Lists to Description Lists throught tiddler

* apply recommendations by @pmario on #9535
2026-01-04 17:37:54 +00:00
Mario Pietsch
e2fb22ade0 Bug report template: Rename description to about (#9546) 2026-01-04 16:50:01 +00:00
peteratkins
7fb8560908 Signing CLA (#9534)
Co-authored-by: Jeremy Ruston <jeremy@jermolene.com>
2026-01-04 13:30:54 +00:00
Jeremy Ruston
ff7814360e Cleanup whitespace in CLA 2026-01-04 13:19:24 +00:00
hsteve11
2d9303c6ff Signing CLA (#9539) 2026-01-04 13:18:46 +00:00
Mario Pietsch
6aee5eb0c7 Add Filter Syntax History link to Filters tiddler (#9541) 2026-01-04 13:16:54 +00:00
Mario Pietsch
6eb881bffe New issue and bug report templates (#9512)
* New issue and bug report templates

* Change bug_report to .md file

* update bug report text

* Move To Reproduce up next to Problem Description

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

* Update Japanese translation

* Update Japanese translation

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

* Fix wrong aria-selected attribute

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

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

* Update tests

* Further fix tests

* Update docs

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

* Fix Control Panel table text direction

* Switch to logical properties

* Add -webkit-margin-* properties

* Improve backward compatibility

* Use supports CSS at-rule for fallback

* Tiddler controls, alert, TOC update.

* tc-tree & testcase update

* .tc-sidebar-scrollable use new syntax

* Snow white use standard value

* Update divider

* Avoid negative logic

* Remove :dir rules at the moment

* Use property procedures

* Add change note

* Update blockquote

* Update unfold banner

* Update tiddlerinfo close button

* Group supports rule

* Update tiddler title icon

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

* Update acknowledgements

* Update change notes

* Fix editcost attribute not working

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

* Simplify Nodejs atob

* Update change note

* Update docs

* Move base64Decode & base64Encode back to utils.js

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

* Replace unnecessary usage of wikify widget

* Simplify multiple let widget

* fixup! Replace unnecessary usage of wikify widget

* Update tests

* Update change note

* fixup! Replace unnecessary usage of wikify widget

* Update docs

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

* Add changenote
2025-12-29 21:09:43 +00:00
wiki-tutor
838fad916d Signing CLA (#9527) 2025-12-28 18:30:51 +00:00
Jeremy Ruston
935e89bd93 Tweak HelloThere thumbnail ordering 2025-12-20 09:25:25 +00:00
381 changed files with 6029 additions and 3666 deletions

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

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

View File

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

View File

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

View File

@@ -120,7 +120,6 @@ node $TW5_BUILD_TIDDLYWIKI \
|| exit 1
# /empty.html Empty
# /empty.hta For Internet Explorer
# /empty-external-core.html External core empty
# /tiddlywikicore-<version>.js Core plugin javascript
node $TW5_BUILD_TIDDLYWIKI \

View File

@@ -316,8 +316,25 @@ $tw.utils.htmlDecode = function(s) {
return s.toString().replace(/&lt;/mg,"<").replace(/&nbsp;/mg,"\xA0").replace(/&gt;/mg,">").replace(/&quot;/mg,"\"").replace(/&amp;/mg,"&");
};
/** @deprecated Use window.location.hash instead. */
$tw.utils.getLocationHash = () => window.location.hash;
/*
Get the browser location.hash. We don't use location.hash because of the way that Firefox auto-urldecodes it (see http://stackoverflow.com/questions/1703552/encoding-of-window-location-hash)
*/
$tw.utils.getLocationHash = function() {
const href = window.location.href,
idx = href.indexOf("#");
if(idx === -1) {
return "#";
}
const afterHash = href.substring(idx + 1);
if(afterHash.startsWith("#") || afterHash.startsWith("%23")) {
// Special case: ignore location hash if it itself starts with a #
return "#";
}
return href.substring(idx);
};
/** @deprecated Pad a string to a given length with "0"s. Length defaults to 2 */
$tw.utils.pad = function(value,length = 2) {
@@ -596,7 +613,7 @@ $tw.utils.evalGlobal = function(code,context,filename,sandbox,allowGlobals) {
// Compile the code into a function
var fn;
if($tw.browser) {
fn = window["eval"](code + "\n\n//# sourceURL=" + filename); // eslint-disable-line no-eval -- See https://github.com/TiddlyWiki/TiddlyWiki5/issues/6839
fn = Function("return " + code + "\n\n//# sourceURL=" + filename)(); // See https://github.com/TiddlyWiki/TiddlyWiki5/issues/6839
} else {
if(sandbox){
fn = vm.runInContext(code,sandbox,filename)

View File

@@ -33,8 +33,8 @@ exports.handler = function(request,response,state) {
}
var text = state.wiki.renderTiddler(renderType,renderTemplate,{parseAsInline: true, variables: {currentTiddler: title}});
// Naughty not to set a content-type, but it's the easiest way to ensure the browser will see HTML pages as HTML, and accept plain text tiddlers as CSS or JS
state.sendResponse(200,{},text,"utf8");
var headers = {"Content-Type": renderType};
state.sendResponse(200,headers,text,"utf8");
} else {
response.writeHead(404);
response.end();

View File

@@ -42,6 +42,8 @@ function Server(options) {
}
// Setup the default required plugins
this.requiredPlugins = this.get("required-plugins").split(',');
// Initialise CORS
this.corsEnable = this.get("cors-enable") === "yes";
// Initialise CSRF
this.csrfDisable = this.get("csrf-disable") === "yes";
// Initialize Gzip compression
@@ -261,6 +263,13 @@ Server.prototype.requestHandler = function(request,response,options) {
state.urlInfo = url.parse(request.url);
state.queryParameters = querystring.parse(state.urlInfo.query);
state.pathPrefix = options.pathPrefix || this.get("path-prefix") || "";
// Enable CORS
if(this.corsEnable) {
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Headers", "*");
response.setHeader("Access-Control-Allow-Methods", "*");
response.setHeader("Access-Control-Expose-Headers", "*");
}
state.sendResponse = sendResponse.bind(self,request,response);
// Get the principals authorized to access this resource
state.authorizationType = options.authorizationType || this.methodMappings[request.method] || "readers";
@@ -285,6 +294,12 @@ Server.prototype.requestHandler = function(request,response,options) {
response.end();
return;
}
// Reply to OPTIONS
if(this.corsEnable && request.method === "OPTIONS") {
response.writeHead(204);
response.end();
return;
}
// Find the route that matches this path
var route = self.findMatchingRoute(request,state);
// Optionally output debug info

View File

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

View File

@@ -0,0 +1,95 @@
/*\
title: $:/core-server/modules/utils/escapecss.js
type: application/javascript
module-type: utils-node
Provides CSS.escape() functionality.
\*/
"use strict";
exports.escapeCSS = (function() {
// see also https://drafts.csswg.org/cssom/#serialize-an-identifier
/* eslint-disable */
/*! https://mths.be/cssescape v1.5.1 by @mathias | MIT license */
return function(value) {
if (arguments.length == 0) {
throw new TypeError('`CSS.escape` requires an argument.');
}
var string = String(value);
var length = string.length;
var index = -1;
var codeUnit;
var result = '';
var firstCodeUnit = string.charCodeAt(0);
while (++index < length) {
codeUnit = string.charCodeAt(index);
// Note: theres no need to special-case astral symbols, surrogate
// pairs, or lone surrogates.
// If the character is NULL (U+0000), then the REPLACEMENT CHARACTER
// (U+FFFD).
if (codeUnit == 0x0000) {
result += '\uFFFD';
continue;
}
if (
// If the character is in the range [\1-\1F] (U+0001 to U+001F) or is
// U+007F, […]
(codeUnit >= 0x0001 && codeUnit <= 0x001F) || codeUnit == 0x007F ||
// If the character is the first character and is in the range [0-9]
// (U+0030 to U+0039), […]
(index == 0 && codeUnit >= 0x0030 && codeUnit <= 0x0039) ||
// If the character is the second character and is in the range [0-9]
// (U+0030 to U+0039) and the first character is a `-` (U+002D), […]
(
index == 1 &&
codeUnit >= 0x0030 && codeUnit <= 0x0039 &&
firstCodeUnit == 0x002D
)
) {
// https://drafts.csswg.org/cssom/#escape-a-character-as-code-point
result += '\\' + codeUnit.toString(16) + ' ';
continue;
}
if (
// If the character is the first character and is a `-` (U+002D), and
// there is no second character, […]
index == 0 &&
length == 1 &&
codeUnit == 0x002D
) {
result += '\\' + string.charAt(index);
continue;
}
// If the character is not handled by one of the above rules and is
// greater than or equal to U+0080, is `-` (U+002D) or `_` (U+005F), or
// is in one of the ranges [0-9] (U+0030 to U+0039), [A-Z] (U+0041 to
// U+005A), or [a-z] (U+0061 to U+007A), […]
if (
codeUnit >= 0x0080 ||
codeUnit == 0x002D ||
codeUnit == 0x005F ||
codeUnit >= 0x0030 && codeUnit <= 0x0039 ||
codeUnit >= 0x0041 && codeUnit <= 0x005A ||
codeUnit >= 0x0061 && codeUnit <= 0x007A
) {
// the character itself
result += string.charAt(index);
continue;
}
// Otherwise, the escaped character.
// https://drafts.csswg.org/cssom/#escape-a-character
result += '\\' + string.charAt(index);
}
return result;
};
/* eslint-enable */
})();

View File

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

View File

@@ -6,6 +6,7 @@ Appearance/Caption: Appearance
Appearance/Hint: Ways to customise the appearance of your TiddlyWiki.
Basics/AnimDuration/Prompt: Animation duration
Basics/AutoFocus/Prompt: Default focus field for new tiddlers
Basics/AutoFocusEdit/Prompt: Default focus field for existing tiddlers
Basics/Caption: Basics
Basics/DefaultTiddlers/BottomHint: Use &#91;&#91;double square brackets&#93;&#93; for titles with spaces. Or you can choose to {{retain story ordering||$:/snippets/retain-story-ordering-button}}
Basics/DefaultTiddlers/Prompt: Default tiddlers

View File

@@ -0,0 +1,4 @@
title: $:/language/Draft/
Attribution: Draft of '<<draft-title>>' by {{$:/status/UserName}}
Title: Draft of '<<draft-title>>'

View File

@@ -1,5 +1,6 @@
title: $:/language/
Alerts: Alerts
AboveStory/ClassicPlugin/Warning: It looks like you are trying to load a plugin designed for ~TiddlyWiki Classic. Please note that [[these plugins do not work with TiddlyWiki version 5.x.x|https://tiddlywiki.com/#TiddlyWikiClassic]]. ~TiddlyWiki Classic plugins detected:
BinaryWarning/Prompt: This tiddler contains binary data
ClassicWarning/Hint: This tiddler is written in TiddlyWiki Classic wiki text format, which is not fully compatible with TiddlyWiki version 5. See https://tiddlywiki.com/static/Upgrading.html for more details.

View File

@@ -9,6 +9,7 @@ Advanced/ShadowInfo/NotShadow/Hint: The tiddler <$link to=<<infoTiddler>>><$text
Advanced/ShadowInfo/Shadow/Hint: The tiddler <$link to=<<infoTiddler>>><$text text=<<infoTiddler>>/></$link> is a shadow tiddler
Advanced/ShadowInfo/Shadow/Source: It is defined in the plugin <$link to=<<pluginTiddler>>><$text text=<<pluginTiddler>>/></$link>
Advanced/ShadowInfo/OverriddenShadow/Hint: It is overridden by an ordinary tiddler
Advanced/CascadeInfo/Heading: Cascade Details
Fields/Caption: Fields
List/Caption: List
List/Empty: This tiddler does not have a list

View File

@@ -0,0 +1,116 @@
/*\
title: $:/core/modules/background-actions.js
type: application/javascript
module-type: global
Class to dispatch actions when filters change
\*/
"use strict";
class BackgroundActionDispatcher {
constructor(filterTracker, wiki) {
this.filterTracker = filterTracker;
this.wiki = wiki;
this.nextTrackedFilterId = 1;
this.trackedFilters = new Map(); // Use Map for better key management
// Track the filter for the background actions
this.filterTracker.track({
filterString: "[all[tiddlers+shadows]tag[$:/tags/BackgroundAction]!is[draft]]",
fnEnter: title => this.trackFilter(title),
fnLeave: (title, enterValue) => this.untrackFilter(enterValue),
fnChange: (title, enterValue) => {
this.untrackFilter(enterValue);
return this.trackFilter(title);
},
fnProcess: changes => this.process(changes)
});
}
trackFilter(title) {
const tiddler = this.wiki.getTiddler(title);
const id = this.nextTrackedFilterId++;
const tracker = new BackgroundActionTracker({
wiki: this.wiki,
title,
trackFilter: tiddler.fields["track-filter"],
actions: tiddler.fields.text
});
this.trackedFilters.set(id, tracker);
return id;
}
untrackFilter(enterValue) {
const tracker = this.trackedFilters.get(enterValue);
if(tracker) {
tracker.destroy();
}
this.trackedFilters.delete(enterValue);
}
process(changes) {
for(const tracker of this.trackedFilters.values()) {
tracker.process(changes);
}
}
}
/*
Represents an individual tracked filter. Options include:
wiki: wiki to use
title: title of the tiddler being tracked
trackFilter: filter string to track changes
actions: actions to be executed when the filter changes
*/
class BackgroundActionTracker {
constructor({wiki, title, trackFilter, actions}) {
this.wiki = wiki;
this.title = title;
this.trackFilter = trackFilter;
this.actions = actions;
this.filterTracker = new $tw.FilterTracker(this.wiki);
this.hasChanged = false;
this.trackerID = this.filterTracker.track({
filterString: this.trackFilter,
fnEnter: () => { this.hasChanged = true; },
fnLeave: () => { this.hasChanged = true; },
fnProcess: changes => {
if(this.hasChanged) {
this.hasChanged = false;
console.log("Processing background action", this.title);
const tiddler = this.wiki.getTiddler(this.title);
let doActions = true;
if(tiddler && tiddler.fields.platforms) {
doActions = false;
const platforms = $tw.utils.parseStringArray(tiddler.fields.platforms);
if(($tw.browser && platforms.includes("browser")) || ($tw.node && platforms.includes("node"))) {
doActions = true;
}
}
if(doActions) {
this.wiki.invokeActionString(
this.actions,
null,
{
currentTiddler: this.title
},{
parentWidget: $tw.rootWidget
}
);
}
}
}
});
}
process(changes) {
this.filterTracker.handleChangeEvent(changes);
}
destroy() {
this.filterTracker.untrack(this.trackerID);
}
}
exports.BackgroundActionDispatcher = BackgroundActionDispatcher;

View File

@@ -48,8 +48,8 @@ function editTextWidgetFactory(toolbarEngine,nonToolbarEngine) {
this.toolbarNode = this.document.createElement("div");
this.toolbarNode.className = "tc-editor-toolbar";
parent.insertBefore(this.toolbarNode,nextSibling);
this.renderChildren(this.toolbarNode,null);
this.domNodes.push(this.toolbarNode);
this.renderChildren(this.toolbarNode,null);
}
// Create our element
var editInfo = this.getEditInfo(),

View File

@@ -0,0 +1,106 @@
/*\
title: $:/core/modules/filter-tracker.js
type: application/javascript
module-type: global
Class to track the results of a filter string
\*/
"use strict";
class FilterTracker {
constructor(wiki) {
this.wiki = wiki;
this.trackers = new Map();
this.nextTrackerId = 1;
}
handleChangeEvent(changes) {
this.processTrackers();
this.processChanges(changes);
}
/*
Add a tracker to the filter tracker. Returns null if any of the parameters are invalid, or a tracker id if the tracker was added successfully. Options include:
filterString: the filter string to track
fnEnter: function to call when a title enters the filter results. Called even if the tiddler does not actually exist. Called as (title), and should return a truthy value that is stored in the tracker as the "enterValue"
fnLeave: function to call when a title leaves the filter results. Called as (title,enterValue)
fnChange: function to call when a tiddler changes in the filter results. Only called for filter results that identify a tiddler or shadow tiddler. Called as (title,enterValue), and may optionally return a replacement enterValue
fnProcess: function to call each time the tracker is processed, after any enter, leave or change functions are called. Called as (changes)
*/
track(options = {}) {
const {
filterString,
fnEnter,
fnLeave,
fnChange,
fnProcess
} = options;
const id = this.nextTrackerId++;
const tracker = {
id,
filterString,
fnEnter,
fnLeave,
fnChange,
fnProcess,
previousResults: [],
resultValues: {}
};
this.trackers.set(id, tracker);
// Process the tracker
this.processTracker(id);
return id;
}
untrack(id) {
this.trackers.delete(id);
}
processTrackers() {
for(const id of this.trackers.keys()) {
this.processTracker(id);
}
}
processTracker(id) {
const tracker = this.trackers.get(id);
if(!tracker) return;
const results = [];
// Evaluate the filter and remove duplicate results
$tw.utils.each(this.wiki.filterTiddlers(tracker.filterString), title => {
$tw.utils.pushTop(results, title);
});
// Process the newly entered results
results.forEach(title => {
if(!tracker.previousResults.includes(title) && !tracker.resultValues[title] && tracker.fnEnter) {
tracker.resultValues[title] = tracker.fnEnter(title) || true;
}
});
// Process the results that have just left
tracker.previousResults.forEach(title => {
if(!results.includes(title) && tracker.resultValues[title] && tracker.fnLeave) {
tracker.fnLeave(title, tracker.resultValues[title]);
delete tracker.resultValues[title];
}
});
// Update the previous results
tracker.previousResults = results;
}
processChanges(changes) {
for(const tracker of this.trackers.values()) {
Object.keys(changes).forEach(title => {
if(title && tracker.previousResults.includes(title) && tracker.fnChange) {
tracker.resultValues[title] = tracker.fnChange(title, tracker.resultValues[title]) || tracker.resultValues[title];
}
});
if(tracker.fnProcess) {
tracker.fnProcess(changes);
}
}
}
}
exports.FilterTracker = FilterTracker;

View File

@@ -88,8 +88,8 @@ function parseFilterOperation(operators,filterString,p) {
rexMatch = rex.exec(filterString.substring(p));
if(rexMatch) {
operator.regexp = new RegExp(rexMatch[1], rexMatch[2]);
// DEPRECATION WARNING
console.log("WARNING: Filter",operator.operator,"has a deprecated regexp operand",operator.regexp);
// DEPRECATION WARNING
console.log("WARNING: Filter",operator.operator,"has a deprecated regexp operand",operator.regexp);
nextBracketPos = p + rex.lastIndex - 1;
}
else {
@@ -232,12 +232,7 @@ exports.getFilterRunPrefixes = function() {
exports.filterTiddlers = function(filterString,widget,source) {
var fn = this.compileFilter(filterString);
try {
const fnResult = fn.call(this,source,widget);
return fnResult;
} catch(e) {
return [`${$tw.language.getString("Error/Filter")}: ${e}`];
}
return fn.call(this,source,widget);
};
/*
@@ -319,19 +314,19 @@ exports.compileFilter = function(filterString) {
// Invoke the appropriate filteroperator module
results = operatorFunction(accumulator,{
operator: operator.operator,
operand: operands.length > 0 ? operands[0] : undefined,
operands: operands,
multiValueOperands: multiValueOperands,
isMultiValueOperand: isMultiValueOperand,
prefix: operator.prefix,
suffix: operator.suffix,
suffixes: operator.suffixes,
regexp: operator.regexp
},{
wiki: self,
widget: widget
});
operator: operator.operator,
operand: operands.length > 0 ? operands[0] : undefined,
operands: operands,
multiValueOperands: multiValueOperands,
isMultiValueOperand: isMultiValueOperand,
prefix: operator.prefix,
suffix: operator.suffix,
suffixes: operator.suffixes,
regexp: operator.regexp
},{
wiki: self,
widget: widget
});
if($tw.utils.isArray(results)) {
accumulator = self.makeTiddlerIterator(results);
} else {

View File

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

View File

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

View File

@@ -0,0 +1,67 @@
/*\
title: $:/core/modules/info/mediaquerytracker.js
type: application/javascript
module-type: info
Initialise $:/info/ tiddlers derived from media queries via
\*/
"use strict";
exports.getInfoTiddlerFields = function(updateInfoTiddlersCallback) {
if($tw.browser) {
// Functions to start and stop tracking a particular media query tracker tiddler
function track(title) {
var result = {},
tiddler = $tw.wiki.getTiddler(title);
if(tiddler) {
var mediaQuery = tiddler.fields["media-query"],
infoTiddler = tiddler.fields["info-tiddler"],
infoTiddlerAlt = tiddler.fields["info-tiddler-alt"];
if(mediaQuery && infoTiddler) {
// Evaluate and track the media query
result.mqList = window.matchMedia(mediaQuery);
function getResultTiddlers() {
var value = result.mqList.matches ? "yes" : "no",
tiddlers = [];
tiddlers.push({title: infoTiddler, text: value});
if(infoTiddlerAlt) {
tiddlers.push({title: infoTiddlerAlt, text: value});
}
return tiddlers;
};
updateInfoTiddlersCallback(getResultTiddlers());
result.handler = function(event) {
updateInfoTiddlersCallback(getResultTiddlers());
};
result.mqList.addEventListener("change",result.handler);
}
}
return result;
}
function untrack(enterValue) {
if(enterValue.mqList && enterValue.handler) {
enterValue.mqList.removeEventListener("change",enterValue.handler);
}
}
// Track media query tracker tiddlers
function fnEnter(title) {
return track(title);
}
function fnLeave(title,enterValue) {
untrack(enterValue);
}
function fnChange(title,enterValue) {
untrack(enterValue);
return track(title);
}
$tw.filterTracker.track({
filterString: "[all[tiddlers+shadows]tag[$:/tags/MediaQueryTracker]!is[draft]]",
fnEnter: fnEnter,
fnLeave: fnLeave,
fnChange: fnChange
});
}
return [];
};

View File

@@ -33,13 +33,6 @@ exports.getInfoTiddlerFields = function(updateInfoTiddlersCallback) {
// Screen size
infoTiddlerFields.push({title: "$:/info/browser/screen/width", text: window.screen.width.toString()});
infoTiddlerFields.push({title: "$:/info/browser/screen/height", text: window.screen.height.toString()});
// Dark mode through event listener on MediaQueryList
var mqList = window.matchMedia("(prefers-color-scheme: dark)"),
getDarkModeTiddler = function() {return {title: "$:/info/darkmode", text: mqList.matches ? "yes" : "no"};};
infoTiddlerFields.push(getDarkModeTiddler());
mqList.addListener(function(event) {
updateInfoTiddlersCallback([getDarkModeTiddler()]);
});
// Language
infoTiddlerFields.push({title: "$:/info/browser/language", text: navigator.language || ""});
}

View File

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

View File

@@ -107,13 +107,14 @@ exports.parseStringLiteral = function(source,pos) {
type: "string",
start: pos
};
var reString = /(?:"""([\s\S]*?)"""|"([^"]*)")|(?:'([^']*)')/g;
var reString = /(?:"""([\s\S]*?)"""|"([^"]*)")|(?:'([^']*)')|\[\[((?:[^\]]|\](?!\]))*)\]\]/g;
reString.lastIndex = pos;
var match = reString.exec(source);
if(match && match.index === pos) {
node.value = match[1] !== undefined ? match[1] :(
match[2] !== undefined ? match[2] : match[3]
);
match[2] !== undefined ? match[2] : (
match[3] !== undefined ? match[3] : match[4]
));
node.end = pos + match[0].length;
return node;
} else {
@@ -173,7 +174,7 @@ exports.parseMacroParameter = function(source,pos) {
start: pos
};
// Define our regexp
const reMacroParameter = /(?:([A-Za-z0-9\-_]+)\s*:)?(?:\s*(?:"""([\s\S]*?)"""|"([^"]*)"|'([^']*)'|\[\[([^\]]*)\]\]|((?:(?:>(?!>))|[^\s>"'])+)))/y;
const reMacroParameter = /(?:([A-Za-z0-9\-_]+)\s*:)?(?:\s*(?:"""([\s\S]*?)"""|"([^"]*)"|'([^']*)'|\[\[((?:[^\]]|\](?!\]))*)\]\]|((?:(?:>(?!>))|[^\s>"'])+)))/y;
// Skip whitespace
pos = $tw.utils.skipWhiteSpace(source,pos);
// Look for the parameter
@@ -206,28 +207,152 @@ exports.parseMacroParameter = function(source,pos) {
Look for a macro invocation. Returns null if not found, or {type: "transclude", attributes:, start:, end:}
*/
exports.parseMacroInvocationAsTransclusion = function(source,pos) {
var node = $tw.utils.parseMacroInvocation(source,pos);
if(node) {
var positionalName = 0,
transclusion = {
type: "transclude",
start: node.start,
end: node.end
};
$tw.utils.addAttributeToParseTreeNode(transclusion,"$variable",node.name);
$tw.utils.each(node.params,function(param) {
var name = param.name;
if(name) {
if(name.charAt(0) === "$") {
name = "$" + name;
}
$tw.utils.addAttributeToParseTreeNode(transclusion,{name: name,type: "string", value: param.value, start: param.start, end: param.end});
} else {
$tw.utils.addAttributeToParseTreeNode(transclusion,{name: (positionalName++) + "",type: "string", value: param.value, start: param.start, end: param.end});
}
});
return transclusion;
var node = {
type: "transclude",
start: pos,
attributes: {},
orderedAttributes: []
};
// Define our regexps
var reVarName = /([^\s>"'=:]+)/g;
// Skip whitespace
pos = $tw.utils.skipWhiteSpace(source,pos);
// Look for a double opening angle bracket
var token = $tw.utils.parseTokenString(source,pos,"<<");
if(!token) {
return null;
}
pos = token.end;
// Get the variable name for the macro
token = $tw.utils.parseTokenRegExp(source,pos,reVarName);
if(!token) {
return null;
}
$tw.utils.addAttributeToParseTreeNode(node,"$variable",token.match[1]);
pos = token.end;
// Check that the tag is terminated by a space or >>
if(!$tw.utils.parseWhiteSpace(source,pos) && !(source.charAt(pos) === ">" && source.charAt(pos + 1) === ">") ) {
return null;
}
// Process attributes
pos = $tw.utils.parseMacroParametersAsAttributes(node,source,pos);
// Skip whitespace
pos = $tw.utils.skipWhiteSpace(source,pos);
// Look for a double closing angle bracket
token = $tw.utils.parseTokenString(source,pos,">>");
if(!token) {
return null;
}
node.end = token.end;
return node;
};
/*
Parse macro parameters as attributes. Returns the position after the last attribute
*/
exports.parseMacroParametersAsAttributes = function(node,source,pos) {
var position = 0,
attribute = $tw.utils.parseMacroParameterAsAttribute(source,pos);
while(attribute) {
if(!attribute.name) {
attribute.name = (position++) + "";
attribute.isPositional = true;
}
node.orderedAttributes.push(attribute);
node.attributes[attribute.name] = attribute;
pos = attribute.end;
// Get the next attribute
attribute = $tw.utils.parseMacroParameterAsAttribute(source,pos);
}
node.end = pos;
return pos;
};
/*
Parse a macro parameter as an attribute. Returns null if not found, otherwise returns {name:, type: "filtered|string|indirect|macro", value|filter|textReference:, start:, end:,}, with the name being optional
*/
exports.parseMacroParameterAsAttribute = function(source,pos) {
var node = {
start: pos
};
// Define our regexps
var reAttributeName = /([^\/\s>"'`=:]+)/g,
reUnquotedAttribute = /((?:(?:>(?!>))|[^\s>"'])+)/g,
reFilteredValue = /\{\{\{([\S\s]+?)\}\}\}/g,
reIndirectValue = /\{\{([^\}]+)\}\}/g,
reSubstitutedValue = /(?:```([\s\S]*?)```|`([^`]|[\S\s]*?)`)/g;
// Skip whitespace
pos = $tw.utils.skipWhiteSpace(source,pos);
// Get the attribute name and the separator token
var nameToken = $tw.utils.parseTokenRegExp(source,pos,reAttributeName),
namePos = nameToken && $tw.utils.skipWhiteSpace(source,nameToken.end),
separatorToken = nameToken && $tw.utils.parseTokenRegExp(source,namePos,/=|:/g),
isNewStyleSeparator = false; // If there is no separator then we don't allow new style values
// If we have a name and a separator then we have a named attribute
if(nameToken && separatorToken) {
node.name = nameToken.match[1];
// key value separator is `=` or `:`
node.assignmentOperator = separatorToken.match[0];
pos = separatorToken.end;
isNewStyleSeparator = (node.assignmentOperator === "=");
}
// Skip whitespace
pos = $tw.utils.skipWhiteSpace(source,pos);
// Look for a string literal
var stringLiteral = $tw.utils.parseStringLiteral(source,pos);
if(stringLiteral) {
pos = stringLiteral.end;
node.type = "string";
node.value = stringLiteral.value;
// Mark the value as having been quoted in the source
node.quoted = true;
} else {
// Look for a filtered value
var filteredValue = $tw.utils.parseTokenRegExp(source,pos,reFilteredValue);
if(filteredValue && isNewStyleSeparator) {
pos = filteredValue.end;
node.type = "filtered";
node.filter = filteredValue.match[1];
} else {
// Look for an indirect value
var indirectValue = $tw.utils.parseTokenRegExp(source,pos,reIndirectValue);
if(indirectValue && isNewStyleSeparator) {
pos = indirectValue.end;
node.type = "indirect";
node.textReference = indirectValue.match[1];
} else {
// Look for a unquoted value
var unquotedValue = $tw.utils.parseTokenRegExp(source,pos,reUnquotedAttribute);
if(unquotedValue) {
pos = unquotedValue.end;
node.type = "string";
node.value = unquotedValue.match[1];
} else {
// Look for a macro invocation value
var macroInvocation = $tw.utils.parseMacroInvocationAsTransclusion(source,pos);
if(macroInvocation && isNewStyleSeparator) {
pos = macroInvocation.end;
node.type = "macro";
node.value = macroInvocation;
} else {
var substitutedValue = $tw.utils.parseTokenRegExp(source,pos,reSubstitutedValue);
if(substitutedValue && isNewStyleSeparator) {
pos = substitutedValue.end;
node.type = "substituted";
node.rawValue = substitutedValue.match[1] || substitutedValue.match[2];
} else {
}
}
}
}
}
}
// Bail if we don't have a value
if(!node.type) {
return null;
}
// Update the end position
node.end = pos;
return node;
};
@@ -296,7 +421,7 @@ exports.parseFilterVariable = function(source) {
};
/*
Look for an HTML attribute definition. Returns null if not found, otherwise returns {type: "attribute", name:, type: "filtered|string|indirect|macro", value|filter|textReference:, start:, end:,}
Look for an HTML attribute definition. Returns null if not found, otherwise returns {name:, type: "filtered|string|indirect|macro", value|filter|textReference:, start:, end:,}
*/
exports.parseAttribute = function(source,pos) {
var node = {
@@ -354,7 +479,7 @@ exports.parseAttribute = function(source,pos) {
node.value = unquotedValue.match[1];
} else {
// Look for a macro invocation value
var macroInvocation = $tw.utils.parseMacroInvocation(source,pos);
var macroInvocation = $tw.utils.parseMacroInvocationAsTransclusion(source,pos);
if(macroInvocation) {
pos = macroInvocation.end;
node.type = "macro";
@@ -375,6 +500,7 @@ exports.parseAttribute = function(source,pos) {
}
}
} else {
// If there is no equals sign or colon, then this is an attribute with no value, defaulting to "true"
node.type = "string";
node.value = "true";
}

View File

@@ -37,7 +37,7 @@ exports.parse = function() {
var paramString = this.match[2],
params = [];
if(paramString !== "") {
var reParam = /\s*([A-Za-z0-9\-_]+)(?:\s*:\s*(?:"""([\s\S]*?)"""|"([^"]*)"|'([^']*)'|\[\[([^\]]*)\]\]|([^"'\s]+)))?/mg,
var reParam = /\s*([A-Za-z0-9\-_]+)(?:\s*:\s*(?:"""([\s\S]*?)"""|"([^"]*)"|'([^']*)'|\[\[((?:[^\]]|\](?!\]))*)\]\]|([^"'\s]+)))?/mg,
paramMatch = reParam.exec(paramString);
while(paramMatch) {
// Save the parameter details

View File

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

View File

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

View File

@@ -6,10 +6,7 @@ module-type: saver
Handles saving changes via window.postMessage() to the window.parent
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
/*
@@ -63,4 +60,3 @@ exports.create = function(wiki) {
return new PostMessageSaver(wiki);
};
})();

View File

@@ -13,6 +13,11 @@ Load core modules
exports.name = "load-modules";
exports.synchronous = true;
// Set to `true` to enable performance instrumentation
var PERFORMANCE_INSTRUMENTATION_CONFIG_TITLE = "$:/config/Performance/Instrumentation";
var widget = require("$:/core/modules/widgets/widget.js");
exports.startup = function() {
// Load modules
$tw.modules.applyMethods("utils",$tw.utils);
@@ -31,6 +36,27 @@ exports.startup = function() {
$tw.modules.applyMethods("tiddlerdeserializer",$tw.Wiki.tiddlerDeserializerModules);
$tw.macros = $tw.modules.getModulesByTypeAsHashmap("macro");
$tw.wiki.initParsers();
// --------------------------
// The rest of the startup process here is not strictly to do with loading modules, but are needed before other startup
// modules are executed. It is easier to put them here than to introduce a new startup module
// --------------------------
// Create a root widget for attaching event handlers. By using it as the parentWidget for another widget tree, one can reuse the event handlers
$tw.rootWidget = new widget.widget({
type: "widget",
children: []
},{
wiki: $tw.wiki,
document: $tw.browser ? document : $tw.fakeDocument
});
// Set up the performance framework
$tw.perf = new $tw.Performance($tw.wiki.getTiddlerText(PERFORMANCE_INSTRUMENTATION_CONFIG_TITLE,"no") === "yes");
// Kick off the filter tracker
$tw.filterTracker = new $tw.FilterTracker($tw.wiki);
$tw.wiki.addEventListener("change",function(changes) {
$tw.filterTracker.handleChangeEvent(changes);
});
// Kick off the background action dispatcher
$tw.backgroundActionDispatcher = new $tw.BackgroundActionDispatcher($tw.filterTracker,$tw.wiki);
if($tw.node) {
$tw.Commander.initCommands();
}

View File

@@ -18,7 +18,6 @@ exports.synchronous = true;
// Default story and history lists
var PAGE_TITLE_TITLE = "$:/core/wiki/title";
var PAGE_STYLESHEET_TITLE = "$:/core/ui/PageStylesheet";
var ROOT_STYLESHEET_TITLE = "$:/core/ui/RootStylesheet";
var PAGE_TEMPLATE_TITLE = "$:/core/ui/RootTemplate";
// Time (in ms) that we defer refreshing changes to draft tiddlers
@@ -45,13 +44,22 @@ exports.startup = function() {
publishTitle();
}
});
var styleParser = $tw.wiki.parseTiddler(ROOT_STYLESHEET_TITLE,{parseAsInline: true}),
styleWidgetNode = $tw.wiki.makeWidget(styleParser,{document: document});
styleWidgetNode.render(document.head,null);
// Set up the styles
$tw.styleWidgetNode = $tw.wiki.makeTranscludeWidget(PAGE_STYLESHEET_TITLE,{document: $tw.fakeDocument});
$tw.styleContainer = $tw.fakeDocument.createElement("style");
$tw.styleWidgetNode.render($tw.styleContainer,null);
$tw.styleWidgetNode.assignedStyles = $tw.styleContainer.textContent;
$tw.styleElement = document.createElement("style");
$tw.styleElement.innerHTML = $tw.styleWidgetNode.assignedStyles;
document.head.insertBefore($tw.styleElement,document.head.firstChild);
$tw.wiki.addEventListener("change",$tw.perf.report("styleRefresh",function(changes) {
styleWidgetNode.refresh(changes,document.head,null);
if($tw.styleWidgetNode.refresh(changes,$tw.styleContainer,null)) {
var newStyles = $tw.styleContainer.textContent;
if(newStyles !== $tw.styleWidgetNode.assignedStyles) {
$tw.styleWidgetNode.assignedStyles = newStyles;
$tw.styleElement.innerHTML = $tw.styleWidgetNode.assignedStyles;
}
}
}));
// Display the $:/core/ui/PageTemplate tiddler to kick off the display
$tw.perf.report("mainRender",function() {
@@ -60,7 +68,7 @@ exports.startup = function() {
$tw.utils.addClass($tw.pageContainer,"tc-page-container-wrapper");
document.body.insertBefore($tw.pageContainer,document.body.firstChild);
$tw.pageWidgetNode.render($tw.pageContainer,null);
$tw.hooks.invokeHook("th-page-refreshed");
$tw.hooks.invokeHook("th-page-refreshed");
})();
// Remove any splash screen elements
var removeList = document.querySelectorAll(".tc-remove-when-wiki-loaded");

View File

@@ -14,11 +14,6 @@ exports.name = "startup";
exports.after = ["load-modules"];
exports.synchronous = true;
// Set to `true` to enable performance instrumentation
var PERFORMANCE_INSTRUMENTATION_CONFIG_TITLE = "$:/config/Performance/Instrumentation";
var widget = require("$:/core/modules/widgets/widget.js");
exports.startup = function() {
// Minimal browser detection
if($tw.browser) {
@@ -54,16 +49,6 @@ exports.startup = function() {
}
// Initialise version
$tw.version = $tw.utils.extractVersionInfo();
// Set up the performance framework
$tw.perf = new $tw.Performance($tw.wiki.getTiddlerText(PERFORMANCE_INSTRUMENTATION_CONFIG_TITLE,"no") === "yes");
// Create a root widget for attaching event handlers. By using it as the parentWidget for another widget tree, one can reuse the event handlers
$tw.rootWidget = new widget.widget({
type: "widget",
children: []
},{
wiki: $tw.wiki,
document: $tw.browser ? document : $tw.fakeDocument
});
// Kick off the language manager and switcher
$tw.language = new $tw.Language();
$tw.languageSwitcher = new $tw.PluginSwitcher({

View File

@@ -63,16 +63,24 @@ exports.startup = function() {
$tw.eventBus.emit("window:closed",{windowID});
},false);
// Set up the styles
var styleParser = $tw.wiki.parseTiddler("$:/core/ui/RootStylesheet",{parseAsInline: true}),
styleWidgetNode = $tw.wiki.makeWidget(styleParser,{document: srcDocument});
styleWidgetNode.render(srcDocument.head,null);
var styleWidgetNode = $tw.wiki.makeTranscludeWidget("$:/core/ui/PageStylesheet",{
document: $tw.fakeDocument,
variables: variables,
importPageMacros: true}),
styleContainer = $tw.fakeDocument.createElement("style");
styleWidgetNode.render(styleContainer,null);
var styleElement = srcDocument.createElement("style");
styleElement.innerHTML = styleContainer.textContent;
srcDocument.head.insertBefore(styleElement,srcDocument.head.firstChild);
// Render the text of the tiddler
var parser = $tw.wiki.parseTiddler(template),
widgetNode = $tw.wiki.makeWidget(parser,{document: srcDocument, parentWidget: $tw.rootWidget, variables: variables});
widgetNode.render(srcDocument.body,srcDocument.body.firstChild);
// Function to handle refreshes
refreshHandler = function(changes) {
styleWidgetNode.refresh(changes);
if(styleWidgetNode.refresh(changes,styleContainer,null)) {
styleElement.innerHTML = styleContainer.textContent;
}
widgetNode.refresh(changes);
};
$tw.wiki.addEventListener("change",refreshHandler);

View File

@@ -0,0 +1,31 @@
/*\
title: $:/core/modules/utils/base64.js
type: application/javascript
module-type: utils-browser
Base64 utility functions
\*/
"use strict";
/*
Base64 utility functions that work in either browser or Node.js
*/
exports.btoa = binstr => window.btoa(binstr);
exports.atob = b64 => window.atob(b64);
function base64ToBytes(base64) {
const binString = exports.atob(base64);
return Uint8Array.from(binString, m => m.codePointAt(0));
};
function bytesToBase64(bytes) {
const binString = Array.from(bytes, byte => String.fromCodePoint(byte)).join("");
return exports.btoa(binString);
};
exports.base64EncodeUtf8 = str => bytesToBase64(new TextEncoder().encode(str));
exports.base64DecodeUtf8 = str => new TextDecoder().decode(base64ToBytes(str));

View File

@@ -44,15 +44,15 @@ exports.domMatchesSelector = (node,selector) => node.matches(selector);
exports.hasClass = (el,className) => el.classList && el.classList.contains(className);
exports.addClass = function(el,className) {
el.classList && el.classList.add(className);
el.classList && className && el.classList.add(className);
};
exports.removeClass = function(el,className) {
el.classList && el.classList.remove(className);
el.classList && className && el.classList.remove(className);
};
exports.toggleClass = function(el,className,status) {
el.classList && el.classList.toggle(className, status);
el.classList && className && el.classList.toggle(className, status);
};
exports.getLocationPath = () => window.location.origin + window.location.pathname;

File diff suppressed because one or more lines are too long

View File

@@ -6,6 +6,7 @@ module-type: utils
Custom errors for TiddlyWiki.
\*/
function TranscludeRecursionError() {
Error.apply(this,arguments);
this.signatures = Object.create(null);

View File

@@ -1,7 +1,7 @@
/*\
title: $:/core/modules/utils/escapecss.js
type: application/javascript
module-type: utils
module-type: utils-browser
Provides CSS.escape() functionality.
@@ -9,92 +9,6 @@ Provides CSS.escape() functionality.
"use strict";
// TODO -- resolve this construction
exports.escapeCSS = (function() {
// use browser's native CSS.escape() function if available
if ($tw.browser && window.CSS && window.CSS.escape) {
return window.CSS.escape;
}
// otherwise, a utility method is provided
// see also https://drafts.csswg.org/cssom/#serialize-an-identifier
/*! https://mths.be/cssescape v1.5.1 by @mathias | MIT license */
return function(value) {
if (arguments.length == 0) {
throw new TypeError('`CSS.escape` requires an argument.');
}
var string = String(value);
var length = string.length;
var index = -1;
var codeUnit;
var result = '';
var firstCodeUnit = string.charCodeAt(0);
while (++index < length) {
codeUnit = string.charCodeAt(index);
// Note: theres no need to special-case astral symbols, surrogate
// pairs, or lone surrogates.
// If the character is NULL (U+0000), then the REPLACEMENT CHARACTER
// (U+FFFD).
if (codeUnit == 0x0000) {
result += '\uFFFD';
continue;
}
if (
// If the character is in the range [\1-\1F] (U+0001 to U+001F) or is
// U+007F, […]
(codeUnit >= 0x0001 && codeUnit <= 0x001F) || codeUnit == 0x007F ||
// If the character is the first character and is in the range [0-9]
// (U+0030 to U+0039), […]
(index == 0 && codeUnit >= 0x0030 && codeUnit <= 0x0039) ||
// If the character is the second character and is in the range [0-9]
// (U+0030 to U+0039) and the first character is a `-` (U+002D), […]
(
index == 1 &&
codeUnit >= 0x0030 && codeUnit <= 0x0039 &&
firstCodeUnit == 0x002D
)
) {
// https://drafts.csswg.org/cssom/#escape-a-character-as-code-point
result += '\\' + codeUnit.toString(16) + ' ';
continue;
}
if (
// If the character is the first character and is a `-` (U+002D), and
// there is no second character, […]
index == 0 &&
length == 1 &&
codeUnit == 0x002D
) {
result += '\\' + string.charAt(index);
continue;
}
// If the character is not handled by one of the above rules and is
// greater than or equal to U+0080, is `-` (U+002D) or `_` (U+005F), or
// is in one of the ranges [0-9] (U+0030 to U+0039), [A-Z] (U+0041 to
// U+005A), or [a-z] (U+0061 to U+007A), […]
if (
codeUnit >= 0x0080 ||
codeUnit == 0x002D ||
codeUnit == 0x005F ||
codeUnit >= 0x0030 && codeUnit <= 0x0039 ||
codeUnit >= 0x0041 && codeUnit <= 0x005A ||
codeUnit >= 0x0061 && codeUnit <= 0x007A
) {
// the character itself
result += string.charAt(index);
continue;
}
// Otherwise, the escaped character.
// https://drafts.csswg.org/cssom/#escape-a-character
result += '\\' + string.charAt(index);
}
return result;
};
return window.CSS.escape;
})();

View File

@@ -37,6 +37,7 @@ Object.defineProperty(TW_Node.prototype, 'TEXT_NODE', {
var TW_TextNode = function(text) {
bumpSequenceNumber(this);
this.textContent = text + "";
this.children = [];
};
Object.setPrototypeOf(TW_TextNode.prototype,TW_Node.prototype);

View File

@@ -8,10 +8,7 @@ Messaging utilities for use with window.postMessage() etc.
This module intentionally has no dependencies so that it can be included in non-TiddlyWiki projects
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
var RESPONSE_TIMEOUT = 2 * 1000;
@@ -122,5 +119,3 @@ BrowserMessagingPublisher.prototype.close = function() {
};
exports.BrowserMessagingPublisher = BrowserMessagingPublisher;
})();

View File

@@ -55,7 +55,7 @@ Return the dflt (default) parameter if str is not a base-10 number.
exports.getInt = function(str,deflt) {
var i = parseInt(str,10);
return isNaN(i) ? deflt : i;
}
};
/*
Repeatedly replaces a substring within a string. Like String.prototype.replace, but without any of the default special handling of $ sequences in the replace string
@@ -69,12 +69,12 @@ exports.replaceString = function(text,search,replace) {
exports.trimPrefix = function(str,unwanted) {
if(typeof str === "string" && typeof unwanted === "string") {
if(unwanted === "") {
return str.replace(/^\s\s*/, '');
return str.replace(/^\s\s*/, "");
} else {
// Safely regexp-escape the unwanted text
unwanted = unwanted.replace(/[\\^$*+?.()|[\]{}]/g, '\\$&');
var regex = new RegExp('^(' + unwanted + ')+');
return str.replace(regex, '');
unwanted = unwanted.replace(/[\\^$*+?.()|[\]{}]/g, "\\$&");
var regex = new RegExp("^(" + unwanted + ")+");
return str.replace(regex, "");
}
} else {
return str;
@@ -84,12 +84,12 @@ exports.trimPrefix = function(str,unwanted) {
exports.trimSuffix = function(str,unwanted) {
if(typeof str === "string" && typeof unwanted === "string") {
if(unwanted === "") {
return str.replace(/\s\s*$/, '');
return str.replace(/\s\s*$/, "");
} else {
// Safely regexp-escape the unwanted text
unwanted = unwanted.replace(/[\\^$*+?.()|[\]{}]/g, '\\$&');
var regex = new RegExp('(' + unwanted + ')+$');
return str.replace(regex, '');
unwanted = unwanted.replace(/[\\^$*+?.()|[\]{}]/g, "\\$&");
var regex = new RegExp("(" + unwanted + ")+$");
return str.replace(regex, "");
}
} else {
return str;
@@ -101,14 +101,14 @@ Convert a string to sentence case (ie capitalise first letter)
*/
exports.toSentenceCase = function(str) {
return (str || "").replace(/^\S/, function(c) {return c.toUpperCase();});
}
};
/*
Convert a string to title case (ie capitalise each initial letter)
*/
exports.toTitleCase = function(str) {
return (str || "").replace(/(^|\s)\S/g, function(c) {return c.toUpperCase();});
}
};
/*
Find the line break preceding a given position in a string
@@ -242,6 +242,53 @@ exports.slowInSlowOut = function(t) {
return (1 - ((Math.cos(t * Math.PI) + 1) / 2));
};
exports.copyObjectPropertiesSafe = function(object) {
const seen = new Set(),
isDOMElement = value => value instanceof Node || value instanceof Window;
function safeCopy(obj) {
// skip circular references
if(seen.has(obj)) {
return undefined;
}
// primitives and null are safe
if(typeof obj !== "object" || obj === null) {
return obj;
}
// skip DOM elements
if(isDOMElement(obj)) {
return undefined;
}
// copy arrays, preserving positions
if(Array.isArray(obj)) {
return obj.map(item => {
const value = safeCopy(item);
return value === undefined ? null : value;
});
}
seen.add(obj);
const copy = {};
let key,
value;
for(key in obj) {
try {
value = safeCopy(obj[key]);
if(value !== undefined) {
copy[key] = value;
}
} catch(e) {
// silently skip unserializable properties
}
}
return copy;
}
const result = safeCopy(object);
seen.clear();
return result;
};
exports.formatTitleString = function(template,options) {
var base = options.base || "",
separator = options.separator || "",
@@ -358,8 +405,8 @@ exports.formatDateString = function(date,template) {
}],
[/^TZD/, function() {
var tz = date.getTimezoneOffset(),
atz = Math.abs(tz);
return (tz < 0 ? '+' : '-') + $tw.utils.pad(Math.floor(atz / 60)) + ':' + $tw.utils.pad(atz % 60);
atz = Math.abs(tz);
return (tz < 0 ? "+" : "-") + $tw.utils.pad(Math.floor(atz / 60)) + ":" + $tw.utils.pad(atz % 60);
}],
[/^wYY/, function() {
return $tw.utils.pad($tw.utils.getYearForWeekNo(date) - 2000);
@@ -568,9 +615,9 @@ exports.unescapeLineBreaks = function(s) {
exports.escape = function(ch) {
var charCode = ch.charCodeAt(0);
if(charCode <= 0xFF) {
return '\\x' + $tw.utils.pad(charCode.toString(16).toUpperCase());
return "\\x" + $tw.utils.pad(charCode.toString(16).toUpperCase());
} else {
return '\\u' + $tw.utils.pad(charCode.toString(16).toUpperCase(),4);
return "\\u" + $tw.utils.pad(charCode.toString(16).toUpperCase(),4);
}
};
@@ -587,11 +634,11 @@ exports.stringify = function(s, rawUnicode) {
*/
var regex = rawUnicode ? /[\x00-\x1f]/g : /[\x00-\x1f\x80-\uFFFF]/g;
return (s || "")
.replace(/\\/g, '\\\\') // backslash
.replace(/\\/g, "\\\\") // backslash
.replace(/"/g, '\\"') // double quote character
.replace(/'/g, "\\'") // single quote character
.replace(/\r/g, '\\r') // carriage return
.replace(/\n/g, '\\n') // line feed
.replace(/\r/g, "\\r") // carriage return
.replace(/\n/g, "\\n") // line feed
.replace(regex, exports.escape); // non-ASCII characters
};
@@ -601,15 +648,15 @@ exports.jsonStringify = function(s, rawUnicode) {
// See http://www.json.org/
var regex = rawUnicode ? /[\x00-\x1f]/g : /[\x00-\x1f\x80-\uFFFF]/g;
return (s || "")
.replace(/\\/g, '\\\\') // backslash
.replace(/\\/g, "\\\\") // backslash
.replace(/"/g, '\\"') // double quote character
.replace(/\r/g, '\\r') // carriage return
.replace(/\n/g, '\\n') // line feed
.replace(/\x08/g, '\\b') // backspace
.replace(/\x0c/g, '\\f') // formfeed
.replace(/\t/g, '\\t') // tab
.replace(/\r/g, "\\r") // carriage return
.replace(/\n/g, "\\n") // line feed
.replace(/\x08/g, "\\b") // backspace
.replace(/\x0c/g, "\\f") // formfeed
.replace(/\t/g, "\\t") // tab
.replace(regex,function(s) {
return '\\u' + $tw.utils.pad(s.charCodeAt(0).toString(16).toUpperCase(),4);
return "\\u" + $tw.utils.pad(s.charCodeAt(0).toString(16).toUpperCase(),4);
}); // non-ASCII characters
};
@@ -617,7 +664,7 @@ exports.jsonStringify = function(s, rawUnicode) {
Escape the RegExp special characters with a preceding backslash
*/
exports.escapeRegExp = function(s) {
return s.replace(/[\-\/\\\^\$\*\+\?\.\(\)\|\[\]\{\}]/g, '\\$&');
return s.replace(/[\-\/\\\^\$\*\+\?\.\(\)\|\[\]\{\}]/g, "\\$&");
};
/*
@@ -700,7 +747,7 @@ exports.parseTextReference = function(textRef) {
}
} else {
// If we couldn't parse it
result.title = textRef
result.title = textRef;
}
return result;
};
@@ -759,60 +806,17 @@ Cryptographic hash function as used by sha256 filter operator
options.length .. number of characters returned defaults to 64
*/
exports.sha256 = function(str, options) {
options = options || {}
options = options || {};
return $tw.sjcl.codec.hex.fromBits($tw.sjcl.hash.sha256.hash(str)).substr(0,options.length || 64);
}
/*
Base64 utility functions that work in either browser or Node.js
*/
if(typeof window !== 'undefined') {
exports.btoa = function(binstr) { return window.btoa(binstr); }
exports.atob = function(b64) { return window.atob(b64); }
} else {
exports.btoa = function(binstr) {
return Buffer.from(binstr, 'binary').toString('base64');
}
exports.atob = function(b64) {
return Buffer.from(b64, 'base64').toString('binary');
}
}
exports.base64ToBytes = function(base64) {
const binString = exports.atob(base64);
return Uint8Array.from(binString, (m) => m.codePointAt(0));
};
exports.bytesToBase64 = function(bytes) {
const binString = Array.from(bytes, (byte) => String.fromCodePoint(byte)).join("");
return exports.btoa(binString);
};
exports.base64EncodeUtf8 = function(str) {
if ($tw.browser) {
return exports.bytesToBase64(new TextEncoder().encode(str));
} else {
const buff = Buffer.from(str, "utf-8");
return buff.toString("base64");
}
};
exports.base64DecodeUtf8 = function(str) {
if ($tw.browser) {
return new TextDecoder().decode(exports.base64ToBytes(str));
} else {
const buff = Buffer.from(str, "base64");
return buff.toString("utf-8");
}
};
/*
Decode a base64 string
*/
exports.base64Decode = function(string64,binary,urlsafe) {
const encoded = urlsafe ? string64.replace(/_/g,'/').replace(/-/g,'+') : string64;
if(binary) return exports.atob(encoded)
else return exports.base64DecodeUtf8(encoded);
const encoded = urlsafe ? string64.replace(/_/g,"/").replace(/-/g,"+") : string64;
if(binary) return $tw.utils.atob(encoded);
else return $tw.utils.base64DecodeUtf8(encoded);
};
/*
@@ -820,10 +824,10 @@ Encode a string to base64
*/
exports.base64Encode = function(string64,binary,urlsafe) {
let encoded;
if(binary) encoded = exports.btoa(string64);
else encoded = exports.base64EncodeUtf8(string64);
if(binary) encoded = $tw.utils.btoa(string64);
else encoded = $tw.utils.base64EncodeUtf8(string64);
if(urlsafe) {
encoded = encoded.replace(/\+/g,'-').replace(/\//g,'_');
encoded = encoded.replace(/\+/g,"-").replace(/\//g,"_");
}
return encoded;
};
@@ -957,3 +961,56 @@ exports.makeCompareFunction = function(type,options) {
};
return (types[type] || types[options.defaultType] || types.number);
};
/*
Split text into parts (lines or words) for diff operations
Adapted from https://github.com/google/diff-match-patch/wiki/Line-or-Word-Diffs
*/
exports.diffPartsToChars = function(text1,text2,mode) {
const lineArray = [""],
lineHash = Object.create(null);
function diff_linesToPartsMunge_(text,mode) {
let chars = "",
lineStart = 0,
lineEnd = -1,
lineArrayLength = lineArray.length,
regexpResult;
const searchRegexp = /\W+/g;
while(lineEnd < text.length - 1) {
if(mode === "words") {
regexpResult = searchRegexp.exec(text);
lineEnd = searchRegexp.lastIndex;
if(regexpResult === null) {
lineEnd = text.length;
}
lineEnd = --lineEnd;
} else {
lineEnd = text.indexOf("\n", lineStart);
if(lineEnd === -1) {
lineEnd = text.length - 1;
}
}
let line = text.substring(lineStart, lineEnd + 1);
if(line in lineHash) {
chars += String.fromCharCode(lineHash[line]);
} else {
if(lineArrayLength === maxLines) {
line = text.substring(lineStart);
lineEnd = text.length;
}
chars += String.fromCharCode(lineArrayLength);
lineHash[line] = lineArrayLength;
lineArray[lineArrayLength++] = line;
}
lineStart = lineEnd + 1;
}
return chars;
}
let maxLines = 40000;
const chars1 = diff_linesToPartsMunge_(text1,mode);
maxLines = 65535;
const chars2 = diff_linesToPartsMunge_(text2,mode);
return {chars1, chars2, lineArray};
};

View File

@@ -80,8 +80,8 @@ BrowseWidget.prototype.render = function(parent,nextSibling) {
});
// Insert element
parent.insertBefore(domNode,nextSibling);
this.renderChildren(domNode,null);
this.domNodes.push(domNode);
this.renderChildren(domNode,null);
};
/*

View File

@@ -9,6 +9,8 @@ Button widget
"use strict";
const ALLOWED_SELECTED_ARIA_ATTR = ["aria-checked", "aria-selected", "aria-pressed"];
var Widget = require("$:/core/modules/widgets/widget.js").widget;
var Popup = require("$:/core/modules/utils/dom/popup.js");
@@ -44,9 +46,14 @@ ButtonWidget.prototype.render = function(parent,nextSibling) {
var classes = this["class"].split(" ") || [],
isPoppedUp = (this.popup || this.popupTitle) && this.isPoppedUp();
if(this.selectedClass) {
if((this.set || this.setTitle) && this.setTo && this.isSelected()) {
$tw.utils.pushTop(classes, this.selectedClass.split(" "));
domNode.setAttribute("aria-checked", "true");
if((this.set || this.setTitle) && this.setTo) {
const selectedAria = ALLOWED_SELECTED_ARIA_ATTR.includes(this.selectedAria) ? this.selectedAria : "aria-checked";
if(this.isSelected()) {
$tw.utils.pushTop(classes, this.selectedClass.split(" "));
domNode.setAttribute(selectedAria, "true");
} else {
domNode.setAttribute(selectedAria, "false");
}
}
if(isPoppedUp) {
$tw.utils.pushTop(classes,this.selectedClass.split(" "));
@@ -128,8 +135,8 @@ ButtonWidget.prototype.render = function(parent,nextSibling) {
}
// Insert element
parent.insertBefore(domNode,nextSibling);
this.renderChildren(domNode,null);
this.domNodes.push(domNode);
this.renderChildren(domNode,null);
};
/*
@@ -221,6 +228,7 @@ ButtonWidget.prototype.execute = function() {
this.style = this.getAttribute("style");
this["class"] = this.getAttribute("class","");
this.selectedClass = this.getAttribute("selectedClass");
this.selectedAria = this.getAttribute("selectedAria");
this.defaultSetValue = this.getAttribute("default","");
this.buttonTag = this.getAttribute("tag");
this.dragTiddler = this.getAttribute("dragTiddler");

View File

@@ -64,8 +64,8 @@ CheckboxWidget.prototype.render = function(parent,nextSibling) {
]);
// Insert the label into the DOM and render any children
parent.insertBefore(this.labelDomNode,nextSibling);
this.renderChildren(this.spanDomNode,null);
this.domNodes.push(this.labelDomNode);
this.renderChildren(this.spanDomNode,null);
};
CheckboxWidget.prototype.getValue = function() {

View File

@@ -9,8 +9,8 @@ Widget to display a diff between two texts
"use strict";
var Widget = require("$:/core/modules/widgets/widget.js").widget,
dmp = require("$:/core/modules/utils/diff-match-patch/diff_match_patch.js");
var Widget = require("$:/core/modules/widgets/widget.js").widget;
const dmp = require("$:/core/modules/utils/diff-match-patch/diff_match_patch.js");
var DiffTextWidget = function(parseTreeNode,options) {
this.initialise(parseTreeNode,options);
@@ -35,25 +35,32 @@ DiffTextWidget.prototype.render = function(parent,nextSibling) {
this.computeAttributes();
this.execute();
// Create the diff object
var dmpObject = new dmp.diff_match_patch();
dmpObject.Diff_EditCost = $tw.utils.parseNumber(this.getAttribute("editcost","4"));
var diffs = dmpObject.diff_main(this.getAttribute("source",""),this.getAttribute("dest",""));
const editCost = $tw.utils.parseNumber(this.getAttribute("editcost","4"));
const mode = this.getAttribute("mode") || "chars";
let diffs;
if(mode === "lines" || mode === "words") {
diffs = diffLineWordMode(this.getAttribute("source",""),this.getAttribute("dest",""),mode,editCost);
} else {
diffs = dmp.diffMain(this.getAttribute("source",""),this.getAttribute("dest",""),{diffEditCost: editCost});
}
// Apply required cleanup
switch(this.getAttribute("cleanup","semantic")) {
case "none":
// No cleanup
break;
case "efficiency":
dmpObject.diff_cleanupEfficiency(diffs);
dmp.diffCleanupEfficiency(diffs, {diffEditCost: editCost});
break;
default: // case "semantic"
dmpObject.diff_cleanupSemantic(diffs);
dmp.diffCleanupSemantic(diffs);
break;
}
// Create the elements
var domContainer = this.document.createElement("div"),
domDiff = this.createDiffDom(diffs);
parent.insertBefore(domContainer,nextSibling);
// Save our container
this.domNodes.push(domContainer);
// Set variables
this.setVariable("diff-count",diffs.reduce(function(acc,diff) {
if(diff[0] !== dmp.DIFF_EQUAL) {
@@ -65,8 +72,6 @@ DiffTextWidget.prototype.render = function(parent,nextSibling) {
this.renderChildren(domContainer,null);
// Render the diff
domContainer.appendChild(domDiff);
// Save our container
this.domNodes.push(domContainer);
};
/*
@@ -133,7 +138,7 @@ Selectively refreshes the widget if needed. Returns true if the widget or any of
*/
DiffTextWidget.prototype.refresh = function(changedTiddlers) {
var changedAttributes = this.computeAttributes();
if(changedAttributes.source || changedAttributes.dest || changedAttributes.cleanup || changedAttributes.editcost) {
if(changedAttributes.source || changedAttributes.dest || changedAttributes.cleanup || changedAttributes.mode || changedAttributes.editcost) {
this.refreshSelf();
return true;
} else {
@@ -141,4 +146,15 @@ DiffTextWidget.prototype.refresh = function(changedTiddlers) {
}
};
// This function is adapted from https://github.com/google/diff-match-patch/wiki/Line-or-Word-Diffs
function diffLineWordMode(text1,text2,mode,editCost) {
var a = $tw.utils.diffPartsToChars(text1,text2,mode);
var lineText1 = a.chars1;
var lineText2 = a.chars2;
var lineArray = a.lineArray;
var diffs = dmp.diffMain(lineText1,lineText2,{diffEditCost: editCost});
dmp.diffCharsToLines(diffs,lineArray);
return diffs;
}
exports["diff-text"] = DiffTextWidget;

View File

@@ -56,6 +56,7 @@ DraggableWidget.prototype.render = function(parent,nextSibling) {
});
// Insert the node into the DOM and render any children
parent.insertBefore(domNode,nextSibling);
this.domNodes.push(domNode);
this.renderChildren(domNode,null);
// Add event handlers
if(this.dragEnable) {
@@ -70,7 +71,6 @@ DraggableWidget.prototype.render = function(parent,nextSibling) {
selector: self.dragHandleSelector
});
}
this.domNodes.push(domNode);
};
/*

View File

@@ -57,8 +57,8 @@ DroppableWidget.prototype.render = function(parent,nextSibling) {
}
// Insert element
parent.insertBefore(domNode,nextSibling);
this.renderChildren(domNode,null);
this.domNodes.push(domNode);
this.renderChildren(domNode,null);
// Stack of outstanding enter/leave events
this.currentlyEntered = [];
};

View File

@@ -77,8 +77,8 @@ ElementWidget.prototype.render = function(parent,nextSibling) {
// Allow hooks to manipulate the DOM node. Eg: Add debug info
$tw.hooks.invokeHook("th-dom-rendering-element", domNode, this);
parent.insertBefore(domNode,nextSibling);
this.renderChildren(domNode,null);
this.domNodes.push(domNode);
this.renderChildren(domNode,null);
};
/*

View File

@@ -39,75 +39,12 @@ EventWidget.prototype.render = function(parent,nextSibling) {
this.domNode = domNode;
// Assign classes
this.assignDomNodeClasses();
// Add our event handler
$tw.utils.each(this.types,function(type) {
domNode.addEventListener(type,function(event) {
var selector = self.getAttribute("selector"),
matchSelector = self.getAttribute("matchSelector"),
actions = self.getAttribute("$"+type),
stopPropagation = self.getAttribute("stopPropagation","onaction"),
selectedNode = event.target,
selectedNodeRect,
catcherNodeRect,
variables = {};
// Firefox can fire dragover and dragenter events on text nodes instead of their parents
if(selectedNode.nodeType === 3) {
selectedNode = selectedNode.parentNode;
}
// Check that the selected node matches any matchSelector
if(matchSelector && !$tw.utils.domMatchesSelector(selectedNode,matchSelector)) {
return false;
}
if(selector) {
// Search ancestors for a node that matches the selector
while(!$tw.utils.domMatchesSelector(selectedNode,selector) && selectedNode !== domNode) {
selectedNode = selectedNode.parentNode;
}
// Exit if we didn't find one
if(selectedNode === domNode) {
return false;
}
// Only set up variables if we have actions to invoke
if(actions) {
variables = $tw.utils.collectDOMVariables(selectedNode,self.domNode,event);
}
}
// Execute our actions with the variables
if(actions) {
// Add a variable for the modifier key
variables.modifier = $tw.keyboardManager.getEventModifierKeyDescriptor(event);
// Add a variable for the mouse button
if("button" in event) {
if(event.button === 0) {
variables["event-mousebutton"] = "left";
} else if(event.button === 1) {
variables["event-mousebutton"] = "middle";
} else if(event.button === 2) {
variables["event-mousebutton"] = "right";
}
}
variables["event-type"] = event.type.toString();
if(typeof event.detail === "object" && !!event.detail) {
$tw.utils.each(event.detail,function(detailValue,detail) {
variables["event-detail-" + detail] = detailValue.toString();
});
} else if(!!event.detail) {
variables["event-detail"] = event.detail.toString();
}
self.invokeActionString(actions,self,event,variables);
}
if((actions && stopPropagation === "onaction") || stopPropagation === "always") {
event.preventDefault();
event.stopPropagation();
return true;
}
return false;
},false);
});
// Add our event handlers
this.toggleListeners();
// Insert element
parent.insertBefore(domNode,nextSibling);
this.renderChildren(domNode,null);
this.domNodes.push(domNode);
this.renderChildren(domNode,null);
};
/*
@@ -122,11 +59,232 @@ EventWidget.prototype.execute = function() {
self.types.push(key.slice(1));
}
});
this.pointerCaptureMode = this.getAttribute("pointerCapture","no");
this.elementTag = this.getAttribute("tag");
// Make child widgets
this.makeChildWidgets();
};
/*
Cache and pre-create all event listeners, called when first needed
*/
EventWidget.prototype.cacheEventListeners = function() {
if(this._eventListeners) {
return;
}
this._eventListeners = Object.create(null);
this._captureActiveListeners = Object.create(null);
this._dynamicOnlyEvents = ["pointerup","pointercancel","pointermove"];
const clearPointerCapture = event => {
if(Number.isInteger(this._capturePointerId)) {
this.stopPointerCapture(this._capturePointerId);
}
};
const attachDynamicOnlyListeners = () => {
this._dynamicOnlyEvents.forEach(dt => {
const listener = this._eventListeners[dt];
if(listener) {
this._captureActiveListeners[dt] = listener;
this.domNode.addEventListener(dt, listener, false);
}
});
};
// Dynamic pointer capture listeners
if(this.pointerCaptureMode === "dynamic") {
["pointerup","pointercancel"].forEach(type => {
this._eventListeners[type] = event => {
const selectedNode = this.checkEvent(event, type);
if(selectedNode) {
clearPointerCapture(event);
}
// Remove dynamic-only listeners
this.cleanupDynamicListeners();
return this.handleEvent(event, type, selectedNode);
};
});
if(!this.types.includes("pointerdown")) {
this.types.push("pointerdown");
}
}
// Create any listeners not already defined above
this.types.forEach(type => {
if(!this._eventListeners[type]) {
this._eventListeners[type] = event => {
const selectedNode = this.checkEvent(event, type);
if(!selectedNode) {
return false;
}
// Handle pointer capture for pointerdown
if(type === "pointerdown") {
if(this.pointerCaptureMode !== "no") {
this.startPointerCapture(event.pointerId, event.target);
}
if(this.pointerCaptureMode === "dynamic") {
attachDynamicOnlyListeners();
}
} else if(type === "pointerup" || type === "pointercancel") {
clearPointerCapture(event);
}
return this.handleEvent(event, type, selectedNode);
};
}
});
};
/*
Check if an event qualifies and return the matching selected node
*/
EventWidget.prototype.checkEvent = function(event, type) {
const domNode = this.domNode;
let node = event.target;
// Use capture target if valid
if(this._captureTarget && event.pointerId !== undefined) {
if(document.contains(this._captureTarget)) {
node = this._captureTarget;
} else {
// Clear stale reference
this.stopPointerCapture(this._capturePointerId);
node = event.target;
}
}
if(node && node.nodeType === 3) {
node = node.parentNode;
}
if(!node || node.nodeType !== 1) {
return null;
}
const selector = this.getAttribute("selector"),
matchSelector = this.getAttribute("matchSelector");
if(matchSelector && !node.matches(matchSelector)) {
return null;
}
if(selector) {
const match = node.closest(selector);
if(!match || match === domNode || !domNode.contains(match)) {
return null;
}
return match;
}
return node;
};
/*
Handle the event and execute actions
*/
EventWidget.prototype.handleEvent = function(event, type, selectedNode) {
if(!selectedNode) {
return false;
}
let actions = this.getAttribute("$"+type),
stopPropagation = this.getAttribute("stopPropagation","onaction");
if(actions) {
let variables = $tw.utils.extend(
{},
$tw.utils.collectDOMVariables(selectedNode, this.domNode, event),
{
"eventJSON": JSON.stringify($tw.utils.copyObjectPropertiesSafe(event)),
"modifier": $tw.keyboardManager.getEventModifierKeyDescriptor(event),
"event-type": event.type.toString()
}
);
if("button" in event) {
const mouseButtonMap = {0:"left",1:"middle",2:"right"};
variables["event-mousebutton"] = mouseButtonMap[event.button];
}
this.invokeActionString(actions, this, event, variables);
}
if((actions && stopPropagation === "onaction") || stopPropagation === "always") {
event.preventDefault();
event.stopPropagation();
return true;
}
return false;
};
EventWidget.prototype.startPointerCapture = function(pointerId, captureTarget) {
// Start capture only if none active; pointerId can be 0
if(!Number.isInteger(this._capturePointerId) && this.domNode && this.domNode.setPointerCapture) {
this.domNode.setPointerCapture(pointerId);
this._capturePointerId = pointerId;
this._captureTarget = captureTarget;
}
};
EventWidget.prototype.stopPointerCapture = function(pointerId) {
if(this.domNode && this.domNode.hasPointerCapture && this.domNode.hasPointerCapture(pointerId)) {
this.domNode.releasePointerCapture(pointerId);
}
this._capturePointerId = undefined;
this._captureTarget = undefined;
};
/*
Attach all relevant listeners
*/
EventWidget.prototype.attachListeners = function() {
this.cacheEventListeners();
const domNode = this.domNode;
Object.keys(this._eventListeners).forEach(type => {
if(this.pointerCaptureMode === "dynamic" && this._dynamicOnlyEvents.includes(type)) {
return; //skip dynamic-only events
}
domNode.addEventListener(type, this._eventListeners[type], false);
});
};
/*
Remove dynamic active listeners
*/
EventWidget.prototype.cleanupDynamicListeners = function() {
const domNode = this.domNode;
Object.keys(this._captureActiveListeners || {}).forEach(type => {
domNode.removeEventListener(type, this._captureActiveListeners[type], false);
});
this._captureActiveListeners = Object.create(null);
};
/*
Remove all listeners
*/
EventWidget.prototype.removeAllListeners = function() {
if(Number.isInteger(this._capturePointerId)) {
this.stopPointerCapture(this._capturePointerId);
}
const domNode = this.domNode;
Object.keys(this._eventListeners || {}).forEach(type => {
domNode.removeEventListener(type, this._eventListeners[type], false);
});
this.cleanupDynamicListeners();
this._captureTarget = null;
};
/*
Enable or disable listeners
*/
EventWidget.prototype.toggleListeners = function() {
let disabled = this.getAttribute("disabled","no") === "yes";
if(disabled) {
this.removeAllListeners();
} else {
this.attachListeners();
}
};
/*
Assign DOM node classes
*/
EventWidget.prototype.assignDomNodeClasses = function() {
var classes = this.getAttribute("class","").split(" ");
classes.push("tc-eventcatcher");
@@ -134,18 +292,23 @@ EventWidget.prototype.assignDomNodeClasses = function() {
};
/*
Selectively refreshes the widget if needed. Returns true if the widget or any of its children needed re-rendering
Refresh widget
*/
EventWidget.prototype.refresh = function(changedTiddlers) {
var changedAttributes = this.computeAttributes(),
changedAttributesCount = $tw.utils.count(changedAttributes);
if(changedAttributesCount === 1 && changedAttributes["class"]) {
this.assignDomNodeClasses();
} else if(changedAttributesCount > 0) {
this.refreshSelf();
return true;
const changedAttributes = this.computeAttributes(),
changedKeys = Object.keys(changedAttributes),
canUpdateAttributes = changedKeys.every(key => key === "class" || key === "disabled");
if(canUpdateAttributes) {
if(changedAttributes["class"]) {
this.assignDomNodeClasses();
}
if(changedAttributes["disabled"]) {
this.toggleListeners();
}
return this.refreshChildren(changedTiddlers);
}
return this.refreshChildren(changedTiddlers);
this.refreshSelf();
return true;
};
exports.eventcatcher = EventWidget;

View File

@@ -45,7 +45,7 @@ ImageWidget.prototype.render = function(parent,nextSibling) {
this.execute();
// Create element
// Determine what type of image it is
var tag = "img", src = "",
var tag = "img", src = "", self = this,
tiddler = this.wiki.getTiddler(this.imageSource);
if(!tiddler) {
// The source isn't the title of a tiddler, so we'll assume it's a URL
@@ -115,11 +115,21 @@ ImageWidget.prototype.render = function(parent,nextSibling) {
if(this.lazyLoading && tag === "img") {
domNode.setAttribute("loading",this.lazyLoading);
}
this.assignAttributes(domNode,{
sourcePrefix: "data-",
destPrefix: "data-"
});
// Add classes when the image loads or fails
$tw.utils.addClass(domNode,"tc-image-loading");
domNode.addEventListener("load",function() {
domNode.addEventListener("load",function(event) {
$tw.utils.removeClass(domNode,"tc-image-loading");
$tw.utils.addClass(domNode,"tc-image-loaded");
if(self.loadedActions) {
var variables = $tw.utils.collectDOMVariables(domNode,null,event);
variables["img-natural-width"] = domNode.naturalWidth.toString();
variables["img-natural-height"] = domNode.naturalHeight.toString();
self.invokeActionString(self.loadedActions,self,event,variables);
}
},false);
domNode.addEventListener("error",function() {
$tw.utils.removeClass(domNode,"tc-image-loading");
@@ -143,17 +153,31 @@ ImageWidget.prototype.execute = function() {
this.imageTooltip = this.getAttribute("tooltip");
this.imageAlt = this.getAttribute("alt");
this.lazyLoading = this.getAttribute("loading");
this.loadedActions = this.getAttribute("loadActions");
};
/*
Selectively refreshes the widget if needed. Returns true if the widget or any of its children needed re-rendering
*/
ImageWidget.prototype.refresh = function(changedTiddlers) {
var changedAttributes = this.computeAttributes();
if(changedAttributes.source || changedAttributes.width || changedAttributes.height || changedAttributes["class"] || changedAttributes.usemap || changedAttributes.tooltip || changedTiddlers[this.imageSource]) {
var changedAttributes = this.computeAttributes(),
hasChangedAttributes = $tw.utils.count(changedAttributes) > 0;
if(changedAttributes.source || changedAttributes["class"] || changedAttributes.usemap || changedAttributes.tooltip || changedTiddlers[this.imageSource] ||changedAttributes.loadActions) {
this.refreshSelf();
return true;
} else {
} else if(hasChangedAttributes) {
this.assignAttributes(this.domNodes[0],{
sourcePrefix: "data-",
destPrefix: "data-"
});
if(changedAttributes.width) {
this.domNodes[0].setAttribute("width",this.getAttribute("width"));
}
if(changedAttributes.height) {
this.domNodes[0].setAttribute("height",this.getAttribute("height"));
}
}
else {
return false;
}
};

View File

@@ -45,8 +45,8 @@ KeyboardWidget.prototype.render = function(parent,nextSibling) {
]);
// Insert element
parent.insertBefore(domNode,nextSibling);
this.renderChildren(domNode,null);
this.domNodes.push(domNode);
this.renderChildren(domNode,null);
};
KeyboardWidget.prototype.handleChangeEvent = function(event) {

View File

@@ -7,7 +7,6 @@ This widget allows defining multiple variables at once, while allowing
the later variables to depend upon the earlier ones.
```
\define helloworld() Hello world!
<$let currentTiddler="target" value={{!!value}} currentTiddler="different">
{{!!value}} will be different from <<value>>
</$let>
@@ -56,7 +55,7 @@ LetWidget.prototype.computeAttributes = function() {
});
// Run through again, setting variables and looking for differences
$tw.utils.each(this.currentValueFor,function(value,name) {
if(!$tw.utils.isArrayEqual(self.attributes[name],value)) {
if(self.attributes[name] === undefined || !$tw.utils.isArrayEqual(self.attributes[name],value)) {
self.attributes[name] = value;
self.setVariable(name,value);
changedAttributes[name] = true;
@@ -68,7 +67,7 @@ LetWidget.prototype.computeAttributes = function() {
LetWidget.prototype.getVariableInfo = function(name,options) {
// Special handling: If this variable exists in this very $let, we can
// use it, but only if it's been staged.
if ($tw.utils.hop(this.currentValueFor,name)) {
if($tw.utils.hop(this.currentValueFor,name)) {
var value = this.currentValueFor[name];
return {
text: value[0] || "",

View File

@@ -50,8 +50,8 @@ LinkWidget.prototype.render = function(parent,nextSibling) {
destPrefix: "aria-"
});
parent.insertBefore(domNode,nextSibling);
this.renderChildren(domNode,null);
this.domNodes.push(domNode);
this.renderChildren(domNode,null);
}
};
@@ -86,7 +86,7 @@ LinkWidget.prototype.renderLink = function(parent,nextSibling) {
classes.push(this.linkClasses);
}
} else if(this.overrideClasses !== "") {
classes.push(this.overrideClasses)
classes.push(this.overrideClasses);
}
if(classes.length > 0) {
domNode.setAttribute("class",classes.join(" "));
@@ -97,7 +97,7 @@ LinkWidget.prototype.renderLink = function(parent,nextSibling) {
if(wikilinkTransformFilter) {
// Use the filter to construct the href
wikiLinkText = this.wiki.filterTiddlers(wikilinkTransformFilter,this,function(iterator) {
iterator(self.wiki.getTiddler(self.to),self.to)
iterator(self.wiki.getTiddler(self.to),self.to);
})[0];
} else {
// Expand the tv-wikilink-template variable to construct the href
@@ -121,12 +121,12 @@ LinkWidget.prototype.renderLink = function(parent,nextSibling) {
var tooltipWikiText = this.tooltip || this.getVariable("tv-wikilink-tooltip");
if(tooltipWikiText) {
var tooltipText = this.wiki.renderText("text/plain","text/vnd.tiddlywiki",tooltipWikiText,{
parseAsInline: true,
variables: {
currentTiddler: this.to
},
parentWidget: this
});
parseAsInline: true,
variables: {
currentTiddler: this.to
},
parentWidget: this
});
domNode.setAttribute("title",tooltipText);
}
if(this.role) {
@@ -135,7 +135,7 @@ LinkWidget.prototype.renderLink = function(parent,nextSibling) {
this.assignAttributes(domNode,{
sourcePrefix: "aria-",
destPrefix: "aria-"
})
});
// Add a click event handler
$tw.utils.addEventListeners(domNode,[
{name: "click", handlerObject: this, handlerMethod: "handleClickEvent"},
@@ -145,6 +145,8 @@ LinkWidget.prototype.renderLink = function(parent,nextSibling) {
$tw.utils.makeDraggable({
domNode: domNode,
dragTiddlerFn: function() {return self.to;},
startActions: self.startActions,
endActions: self.endActions,
widget: this
});
} else if(this.draggable === "no") {
@@ -157,8 +159,8 @@ LinkWidget.prototype.renderLink = function(parent,nextSibling) {
});
// Insert the link into the DOM and render any children
parent.insertBefore(domNode,nextSibling);
this.renderChildren(domNode,null);
this.domNodes.push(domNode);
this.renderChildren(domNode,null);
};
LinkWidget.prototype.handleClickEvent = function(event) {
@@ -203,6 +205,8 @@ LinkWidget.prototype.execute = function() {
this.overrideClasses = this.getAttribute("overrideClass");
this.tabIndex = this.getAttribute("tabindex");
this.draggable = this.getAttribute("draggable","yes");
this.startActions = this.getAttribute("startactions");
this.endActions = this.getAttribute("endactions");
this.linkTag = this.getAttribute("tag","a");
// Determine the link characteristics
this.isMissing = !this.wiki.tiddlerExists(this.to);

View File

@@ -42,8 +42,8 @@ PasswordWidget.prototype.render = function(parent,nextSibling) {
]);
// Insert the label into the DOM and render any children
parent.insertBefore(domNode,nextSibling);
this.renderChildren(domNode,null);
this.domNodes.push(domNode);
this.renderChildren(domNode,null);
};
PasswordWidget.prototype.handleChangeEvent = function(event) {

View File

@@ -59,8 +59,8 @@ RadioWidget.prototype.render = function(parent,nextSibling) {
]);
// Insert the label into the DOM and render any children
parent.insertBefore(this.labelDomNode,nextSibling);
this.renderChildren(this.spanDomNode,null);
this.domNodes.push(this.labelDomNode);
this.renderChildren(this.spanDomNode,null);
};
RadioWidget.prototype.getValue = function() {

View File

@@ -40,6 +40,7 @@ RevealWidget.prototype.render = function(parent,nextSibling) {
domNode.setAttribute("style",this.style);
}
parent.insertBefore(domNode,nextSibling);
this.domNodes.push(domNode);
this.renderChildren(domNode,null);
if(!domNode.isTiddlyWikiFakeDom && this.type === "popup" && this.isOpen) {
this.positionPopup(domNode);
@@ -48,7 +49,6 @@ RevealWidget.prototype.render = function(parent,nextSibling) {
if(!this.isOpen) {
domNode.setAttribute("hidden","true");
}
this.domNodes.push(domNode);
};
RevealWidget.prototype.positionPopup = function(domNode) {

View File

@@ -168,8 +168,8 @@ ScrollableWidget.prototype.render = function(parent,nextSibling) {
this.outerDomNode.className = this["class"] || "";
// Insert element
parent.insertBefore(this.outerDomNode,nextSibling);
this.renderChildren(this.innerDomNode,null);
this.domNodes.push(this.outerDomNode);
this.renderChildren(this.innerDomNode,null);
// If the scroll position is bound to a tiddler
if(this.scrollableBind) {
// After a delay for rendering, scroll to the bound position

View File

@@ -63,8 +63,8 @@ SelectWidget.prototype.render = function(parent,nextSibling) {
domNode.setAttribute("title",this.selectTooltip);
}
this.parentDomNode.insertBefore(domNode,nextSibling);
this.renderChildren(domNode,null);
this.domNodes.push(domNode);
this.renderChildren(domNode,null);
this.setSelectValue();
if(this.selectFocus == "yes") {
this.getSelectDomNode().focus();
@@ -82,8 +82,8 @@ SelectWidget.prototype.handleChangeEvent = function(event) {
if(this.selectMultiple == false) {
var value = this.getSelectDomNode().value;
} else {
var value = this.getSelectValues()
value = $tw.utils.stringifyList(value);
var value = this.getSelectValues();
value = $tw.utils.stringifyList(value);
}
this.wiki.setText(this.selectTitle,this.selectField,this.selectIndex,value);
// Trigger actions
@@ -118,12 +118,21 @@ SelectWidget.prototype.setSelectValue = function() {
}
}
// Assign it to the select element if it's different than the current value
if (this.selectMultiple) {
if(this.selectMultiple) {
value = value === undefined ? "" : value;
var select = this.getSelectDomNode();
var values = Array.isArray(value) ? value : $tw.utils.parseStringArray(value);
var child,
values = Array.isArray(value) ? value : $tw.utils.parseStringArray(value);
for(var i=0; i < select.children.length; i++){
select.children[i].selected = values.indexOf(select.children[i].value) !== -1
child=select.children[i];
if(child.children.length === 0){
child.selected = values.indexOf(child.value) !== -1;
} else {
// grouped options
for(var y=0; y < child.children.length; y++){
child.children[y].selected = values.indexOf(child.children[y].value) !== -1;
}
}
}
} else {
var domNode = this.getSelectDomNode();
@@ -147,14 +156,14 @@ SelectWidget.prototype.getSelectValues = function() {
select = this.getSelectDomNode();
result = [];
options = select && select.options;
for (var i=0; i<options.length; i++) {
for(var i=0; i<options.length; i++) {
opt = options[i];
if (opt.selected) {
if(opt.selected) {
result.push(opt.value || opt.text);
}
}
return result;
}
};
/*
Compute the internal state of the widget
@@ -183,7 +192,7 @@ Selectively refreshes the widget if needed. Returns true if the widget or any of
SelectWidget.prototype.refresh = function(changedTiddlers) {
var changedAttributes = this.computeAttributes();
// If we're using a different tiddler/field/index then completely refresh ourselves
if(changedAttributes.tiddler || changedAttributes.field || changedAttributes.index || changedAttributes.tooltip || changedAttributes.tabindex || changedAttributes.disabled) {
if(changedAttributes.tiddler || changedAttributes.field || changedAttributes.index || changedAttributes.tooltip || changedAttributes.default || changedAttributes.tabindex || changedAttributes.disabled) {
this.refreshSelf();
return true;
} else {

View File

@@ -32,16 +32,26 @@ TranscludeWidget.prototype.render = function(parent,nextSibling) {
} catch(error) {
if(error instanceof $tw.utils.TranscludeRecursionError) {
// We were infinite looping.
// We need to try and abort as much of the loop as we can, so we will keep "throwing" upward until we find a transclusion that has a different signature.
// Hopefully that will land us just outside where the loop began. That's where we want to issue an error.
// Rendering widgets beneath this point may result in a freezing browser if they explode exponentially.
// We need to try and abort as much of the loop as we
// can, so we will keep "throwing" upward until we find
// a transclusion that has a different signature.
// Hopefully that will land us just outside where the
// loop began. That's where we want to issue an error.
// Rendering widgets beneath this point may result in a
// freezing browser if they explode exponentially.
var transcludeSignature = this.getVariable("transclusion");
if(this.getAncestorCount() > $tw.utils.TranscludeRecursionError.MAX_WIDGET_TREE_DEPTH - 50) {
// For the first fifty transcludes we climb up, we simply collect signatures.
// We're assuming that those first 50 will likely include all transcludes involved in the loop.
// For the first fifty transcludes we climb up,
// we simply collect signatures.
// We're assuming those first 50 will likely
// include all transcludes involved in the loop.
error.signatures[transcludeSignature] = true;
} else if(!error.signatures[transcludeSignature]) {
// Now that we're past the first 50, let's look for the first signature that wasn't in the loop. That'll be where we print the error and resume rendering.
// Now that we're past the first 50, look for
// the first signature that wasn't in that loop.
// That's where we print the error and resume
// rendering.
this.removeChildDomNodes();
this.children = [this.makeChildWidget({type: "error", attributes: {
"$message": {type: "string", value: $tw.language.getString("Error/RecursiveTransclusion")}
}})];

View File

@@ -9,376 +9,215 @@ View widget
"use strict";
const Widget = require("$:/core/modules/widgets/widget.js").widget;
var Widget = require("$:/core/modules/widgets/widget.js").widget;
var ViewWidget = function(parseTreeNode,options) {
this.initialise(parseTreeNode,options);
};
/*
==========================================
ViewHandler Base Class
==========================================
Base class for all view format handlers.
Provides common functionality and defines the interface for subclasses.
Inherit from the base widget class
*/
class ViewHandler {
constructor(widget) {
this.widget = widget;
this.wiki = widget.wiki;
this.document = widget.document;
this.viewTitle = widget.viewTitle;
this.viewField = widget.viewField;
this.viewIndex = widget.viewIndex;
this.viewSubtiddler = widget.viewSubtiddler;
this.viewTemplate = widget.viewTemplate;
this.viewMode = widget.viewMode;
}
render(parent, nextSibling) {
this.text = this.getValue();
this.createTextNode(parent, nextSibling);
}
getValue() {
return this.widget.getValueAsText();
}
createTextNode(parent, nextSibling) {
if(this.text) {
const textNode = this.document.createTextNode(this.text);
parent.insertBefore(textNode, nextSibling);
this.widget.domNodes.push(textNode);
} else {
this.widget.makeChildWidgets();
this.widget.renderChildren(parent, nextSibling);
}
}
refresh() {
var self = this;
return false;
}
}
ViewWidget.prototype = new Widget();
/*
==========================================
Wikified View Handler Base
==========================================
Base class for wikified view handlers
Render this widget into the DOM
*/
class WikifiedViewHandler extends ViewHandler {
constructor(widget) {
super(widget);
this.fakeWidget = null;
this.fakeNode = null;
}
render(parent, nextSibling) {
this.createFakeWidget();
this.text = this.getValue();
this.createWikifiedTextNode(parent, nextSibling);
}
createFakeWidget() {
this.fakeWidget = this.wiki.makeTranscludeWidget(this.viewTitle, {
document: $tw.fakeDocument,
field: this.viewField,
index: this.viewIndex,
parseAsInline: this.viewMode !== "block",
mode: this.viewMode === "block" ? "block" : "inline",
parentWidget: this.widget,
subTiddler: this.viewSubtiddler
});
this.fakeNode = $tw.fakeDocument.createElement("div");
this.fakeWidget.makeChildWidgets();
this.fakeWidget.render(this.fakeNode, null);
}
createWikifiedTextNode(parent, nextSibling) {
const textNode = this.document.createTextNode(this.text || "");
parent.insertBefore(textNode, nextSibling);
this.widget.domNodes.push(textNode);
}
refresh(changedTiddlers) {
const refreshed = this.fakeWidget.refresh(changedTiddlers);
if(refreshed) {
const newText = this.getValue();
if(newText !== this.text) {
this.widget.domNodes[0].textContent = newText;
this.text = newText;
}
}
return refreshed;
}
}
/*
==========================================
Text View Handler
==========================================
Default handler for plain text display
*/
class TextViewHandler extends ViewHandler {}
/*
==========================================
HTML Wikified View Handler
==========================================
Handler for wikified HTML content
*/
class HTMLWikifiedViewHandler extends WikifiedViewHandler {
getValue() {
return this.fakeNode.innerHTML;
}
}
/*
==========================================
Plain Wikified View Handler
==========================================
Handler for wikified plain text content
*/
class PlainWikifiedViewHandler extends WikifiedViewHandler {
getValue() {
return this.fakeNode.textContent;
}
}
/*
==========================================
HTML Encoded Plain Wikified View Handler
==========================================
Handler for HTML-encoded wikified plain text
*/
class HTMLEncodedPlainWikifiedViewHandler extends WikifiedViewHandler {
getValue() {
return $tw.utils.htmlEncode(this.fakeNode.textContent);
}
}
/*
==========================================
HTML Encoded View Handler
==========================================
Handler for HTML-encoded text
*/
class HTMLEncodedViewHandler extends ViewHandler {
getValue() {
return $tw.utils.htmlEncode(this.widget.getValueAsText());
}
}
/*
==========================================
HTML Text Encoded View Handler
==========================================
Handler for HTML text-encoded content
*/
class HTMLTextEncodedViewHandler extends ViewHandler {
getValue() {
return $tw.utils.htmlTextEncode(this.widget.getValueAsText());
}
}
/*
==========================================
URL Encoded View Handler
==========================================
Handler for URL-encoded text
*/
class URLEncodedViewHandler extends ViewHandler {
getValue() {
return $tw.utils.encodeURIComponentExtended(this.widget.getValueAsText());
}
}
/*
==========================================
Double URL Encoded View Handler
==========================================
Handler for double URL-encoded text
*/
class DoubleURLEncodedViewHandler extends ViewHandler {
getValue() {
const text = this.widget.getValueAsText();
return $tw.utils.encodeURIComponentExtended($tw.utils.encodeURIComponentExtended(text));
}
}
/*
==========================================
Date View Handler
==========================================
Handler for date formatting
*/
class DateViewHandler extends ViewHandler {
getValue() {
const format = this.viewTemplate || "YYYY MM DD 0hh:0mm";
const rawValue = this.widget.getValueAsText();
const value = $tw.utils.parseDate(rawValue);
if(value && $tw.utils.isDate(value) && value.toString() !== "Invalid Date") {
return $tw.utils.formatDateString(value, format);
} else {
return "";
}
}
}
/*
==========================================
Relative Date View Handler
==========================================
Handler for relative date display
*/
class RelativeDateViewHandler extends ViewHandler {
getValue() {
const rawValue = this.widget.getValueAsText();
const value = $tw.utils.parseDate(rawValue);
if(value && $tw.utils.isDate(value) && value.toString() !== "Invalid Date") {
return $tw.utils.getRelativeDate((new Date()) - (new Date(value))).description;
} else {
return "";
}
}
}
/*
==========================================
Strip Comments View Handler
==========================================
Handler for stripping comments from text
*/
class StripCommentsViewHandler extends ViewHandler {
getValue() {
const lines = this.widget.getValueAsText().split("\n");
const out = [];
for(const text of lines) {
if(!/^\s*\/\/#/.test(text)) {
out.push(text);
}
}
return out.join("\n");
}
}
/*
==========================================
JS Encoded View Handler
==========================================
Handler for JavaScript string encoding
*/
class JSEncodedViewHandler extends ViewHandler {
getValue() {
return $tw.utils.stringify(this.widget.getValueAsText());
}
}
/*
==========================================
ViewHandlerFactory
==========================================
Factory for creating appropriate view handlers based on format
*/
const ViewHandlerFactory = {
handlers: {
"text": TextViewHandler,
"htmlwikified": HTMLWikifiedViewHandler,
"plainwikified": PlainWikifiedViewHandler,
"htmlencodedplainwikified": HTMLEncodedPlainWikifiedViewHandler,
"htmlencoded": HTMLEncodedViewHandler,
"htmltextencoded": HTMLTextEncodedViewHandler,
"urlencoded": URLEncodedViewHandler,
"doubleurlencoded": DoubleURLEncodedViewHandler,
"date": DateViewHandler,
"relativedate": RelativeDateViewHandler,
"stripcomments": StripCommentsViewHandler,
"jsencoded": JSEncodedViewHandler
},
createHandler(format, widget) {
const HandlerClass = this.handlers[format] || this.handlers["text"];
return new HandlerClass(widget);
},
registerHandler(format, handlerClass) {
this.handlers[format] = handlerClass;
ViewWidget.prototype.render = function(parent,nextSibling) {
this.parentDomNode = parent;
this.computeAttributes();
this.execute();
if(this.text) {
var textNode = this.document.createTextNode(this.text);
parent.insertBefore(textNode,nextSibling);
this.domNodes.push(textNode);
} else {
this.makeChildWidgets();
this.renderChildren(parent,nextSibling);
}
};
/*
==========================================
ViewWidget
==========================================
Main widget class that orchestrates view handlers
Compute the internal state of the widget
*/
class ViewWidget extends Widget {
constructor(parseTreeNode, options) {
super();
this.initialise(parseTreeNode, options);
ViewWidget.prototype.execute = function() {
// Get parameters from our attributes
this.viewTitle = this.getAttribute("tiddler",this.getVariable("currentTiddler"));
this.viewSubtiddler = this.getAttribute("subtiddler");
this.viewField = this.getAttribute("field","text");
this.viewIndex = this.getAttribute("index");
this.viewFormat = this.getAttribute("format","text");
this.viewTemplate = this.getAttribute("template","");
this.viewMode = this.getAttribute("mode","block");
switch(this.viewFormat) {
case "htmlwikified":
this.text = this.getValueAsHtmlWikified(this.viewMode);
break;
case "plainwikified":
this.text = this.getValueAsPlainWikified(this.viewMode);
break;
case "htmlencodedplainwikified":
this.text = this.getValueAsHtmlEncodedPlainWikified(this.viewMode);
break;
case "htmlencoded":
this.text = this.getValueAsHtmlEncoded();
break;
case "htmltextencoded":
this.text = this.getValueAsHtmlTextEncoded();
break;
case "urlencoded":
this.text = this.getValueAsUrlEncoded();
break;
case "doubleurlencoded":
this.text = this.getValueAsDoubleUrlEncoded();
break;
case "date":
this.text = this.getValueAsDate(this.viewTemplate);
break;
case "relativedate":
this.text = this.getValueAsRelativeDate();
break;
case "stripcomments":
this.text = this.getValueAsStrippedComments();
break;
case "jsencoded":
this.text = this.getValueAsJsEncoded();
break;
default: // "text"
this.text = this.getValueAsText();
break;
}
};
render(parent, nextSibling) {
this.parentDomNode = parent;
this.computeAttributes();
this.execute();
this.viewHandler = ViewHandlerFactory.createHandler(this.viewFormat, this);
this.viewHandler.render(parent, nextSibling);
}
/*
The various formatter functions are baked into this widget for the moment. Eventually they will be replaced by macro functions
*/
execute() {
this.viewTitle = this.getAttribute("tiddler", this.getVariable("currentTiddler"));
this.viewSubtiddler = this.getAttribute("subtiddler");
this.viewField = this.getAttribute("field", "text");
this.viewIndex = this.getAttribute("index");
this.viewFormat = this.getAttribute("format", "text");
this.viewTemplate = this.getAttribute("template", "");
this.viewMode = this.getAttribute("mode", "block");
}
getValue(options = {}) {
let value = options.asString ? "" : undefined;
if(this.viewIndex) {
value = this.wiki.extractTiddlerDataItem(this.viewTitle, this.viewIndex);
/*
Retrieve the value of the widget. Options are:
asString: Optionally return the value as a string
*/
ViewWidget.prototype.getValue = function(options) {
options = options || {};
var value = options.asString ? "" : undefined;
if(this.viewIndex) {
value = this.wiki.extractTiddlerDataItem(this.viewTitle,this.viewIndex);
} else {
var tiddler;
if(this.viewSubtiddler) {
tiddler = this.wiki.getSubTiddler(this.viewTitle,this.viewSubtiddler);
} else {
let tiddler;
if(this.viewSubtiddler) {
tiddler = this.wiki.getSubTiddler(this.viewTitle, this.viewSubtiddler);
tiddler = this.wiki.getTiddler(this.viewTitle);
}
if(tiddler) {
if(this.viewField === "text" && !this.viewSubtiddler) {
// Calling getTiddlerText() triggers lazy loading of skinny tiddlers
value = this.wiki.getTiddlerText(this.viewTitle);
} else {
tiddler = this.wiki.getTiddler(this.viewTitle);
}
if(tiddler) {
if(this.viewField === "text" && !this.viewSubtiddler) {
value = this.wiki.getTiddlerText(this.viewTitle);
} else {
if($tw.utils.hop(tiddler.fields, this.viewField)) {
if(options.asString) {
value = tiddler.getFieldString(this.viewField);
} else {
value = tiddler.fields[this.viewField];
}
if($tw.utils.hop(tiddler.fields,this.viewField)) {
if(options.asString) {
value = tiddler.getFieldString(this.viewField);
} else {
value = tiddler.fields[this.viewField];
}
}
} else {
if(this.viewField === "title") {
value = this.viewTitle;
}
}
} else {
if(this.viewField === "title") {
value = this.viewTitle;
}
}
return value;
}
return value;
};
getValueAsText() {
return this.getValue({asString: true});
ViewWidget.prototype.getValueAsText = function() {
return this.getValue({asString: true});
};
ViewWidget.prototype.getValueAsHtmlWikified = function(mode) {
return this.wiki.renderText("text/html","text/vnd.tiddlywiki",this.getValueAsText(),{
parseAsInline: mode !== "block",
parentWidget: this
});
};
ViewWidget.prototype.getValueAsPlainWikified = function(mode) {
return this.wiki.renderText("text/plain","text/vnd.tiddlywiki",this.getValueAsText(),{
parseAsInline: mode !== "block",
parentWidget: this
});
};
ViewWidget.prototype.getValueAsHtmlEncodedPlainWikified = function(mode) {
return $tw.utils.htmlEncode(this.wiki.renderText("text/plain","text/vnd.tiddlywiki",this.getValueAsText(),{
parseAsInline: mode !== "block",
parentWidget: this
}));
};
ViewWidget.prototype.getValueAsHtmlEncoded = function() {
return $tw.utils.htmlEncode(this.getValueAsText());
};
ViewWidget.prototype.getValueAsHtmlTextEncoded = function() {
return $tw.utils.htmlTextEncode(this.getValueAsText());
};
ViewWidget.prototype.getValueAsUrlEncoded = function() {
return $tw.utils.encodeURIComponentExtended(this.getValueAsText());
};
ViewWidget.prototype.getValueAsDoubleUrlEncoded = function() {
return $tw.utils.encodeURIComponentExtended($tw.utils.encodeURIComponentExtended(this.getValueAsText()));
};
ViewWidget.prototype.getValueAsDate = function(format) {
format = format || "YYYY MM DD 0hh:0mm";
var value = $tw.utils.parseDate(this.getValue());
if(value && $tw.utils.isDate(value) && value.toString() !== "Invalid Date") {
return $tw.utils.formatDateString(value,format);
} else {
return "";
}
};
refresh(changedTiddlers) {
const changedAttributes = this.computeAttributes();
if(changedAttributes.tiddler || changedAttributes.field || changedAttributes.index ||
changedAttributes.template || changedAttributes.format || changedTiddlers[this.viewTitle]) {
this.refreshSelf();
return true;
} else {
return this.viewHandler.refresh(changedTiddlers);
ViewWidget.prototype.getValueAsRelativeDate = function(format) {
var value = $tw.utils.parseDate(this.getValue());
if(value && $tw.utils.isDate(value) && value.toString() !== "Invalid Date") {
return $tw.utils.getRelativeDate((new Date()) - (new Date(value))).description;
} else {
return "";
}
};
ViewWidget.prototype.getValueAsStrippedComments = function() {
var lines = this.getValueAsText().split("\n"),
out = [];
for(var line=0; line<lines.length; line++) {
var text = lines[line];
if(!/^\s*\/\/#/.test(text)) {
out.push(text);
}
}
}
return out.join("\n");
};
exports.view = ViewWidget;
ViewWidget.prototype.getValueAsJsEncoded = function() {
return $tw.utils.stringify(this.getValueAsText());
};
/*
Selectively refreshes the widget if needed. Returns true if the widget or any of its children needed re-rendering
*/
ViewWidget.prototype.refresh = function(changedTiddlers) {
var changedAttributes = this.computeAttributes();
if(changedAttributes.tiddler || changedAttributes.field || changedAttributes.index || changedAttributes.template || changedAttributes.format || changedTiddlers[this.viewTitle]) {
this.refreshSelf();
return true;
} else {
return false;
}
};
exports.view = ViewWidget;

View File

@@ -151,7 +151,7 @@ Widget.prototype.getVariableInfo = function(name,options) {
} else if(variable.isFunctionDefinition) {
// Function evaluations
params = self.resolveVariableParameters(variable.params,actualParams);
var variables = options.variables || Object.create(null);
var variables = $tw.utils.extend({},options.variables);
// Apply default parameter values
$tw.utils.each(variable.params,function(param,index) {
if(param["default"]) {
@@ -160,7 +160,7 @@ Widget.prototype.getVariableInfo = function(name,options) {
});
// Parameters are an array of {name:, value:, multivalue:} pairs (name and multivalue are optional)
$tw.utils.each(params,function(param) {
if(param.multiValue) {
if(param.multiValue && param.multiValue.length) {
variables[param.name] = param.multiValue;
} else {
variables[param.name] = param.value || "";
@@ -233,8 +233,10 @@ Widget.prototype.resolveVariableParameters = function(formalParams,actualParams)
paramMultiValue = typeof param === "string" ? [param] : (param.multiValue || [paramValue]);
}
// If we've still not got a value, use the default, if any
paramValue = paramValue || paramInfo["default"] || "";
paramMultiValue = paramMultiValue || [paramValue];
if(!paramValue) {
paramValue = paramInfo["default"] || "";
paramMultiValue = [paramValue];
}
// Store the parameter name and value
results.push({name: paramInfo.name, value: paramValue, multiValue: paramMultiValue});
}
@@ -341,7 +343,7 @@ Widget.prototype.makeFakeWidgetWithVariables = function(variables) {
}
} else {
opts = opts || {};
opts.variables = variables;
opts.variables = $tw.utils.extend({},variables,opts.variables);
return self.getVariable(name,opts);
};
},
@@ -414,7 +416,21 @@ Widget.prototype.computeAttribute = function(attribute,options) {
value = [value];
}
} else if(attribute.type === "macro") {
var variableInfo = this.getVariableInfo(attribute.value.name,{params: attribute.value.params});
// Get the macro name
var macroName = attribute.value.attributes["$variable"].value;
// Collect macro parameters
var params = [];
$tw.utils.each(attribute.value.orderedAttributes,function(attr) {
var param = {
value: self.computeAttribute(attr)
};
if(attr.name && !attr.isPositional) {
param.name = attr.name;
}
params.push(param);
});
// Invoke the macro
var variableInfo = this.getVariableInfo(macroName,{params: params});
if(options.asList) {
value = variableInfo.resultList;
} else {
@@ -771,9 +787,9 @@ Widget.prototype.findNextSiblingDomNode = function(startIndex) {
// Refer to this widget by its index within its parents children
var parent = this.parentWidget,
index = startIndex !== undefined ? startIndex : parent.children.indexOf(this);
if(index === -1) {
throw "node not found in parents children";
}
if(index === -1) {
throw "node not found in parents children";
}
// Look for a DOM node in the later siblings
while(++index < parent.children.length) {
var domNode = parent.children[index].findFirstDomNode();
@@ -811,21 +827,60 @@ Widget.prototype.findFirstDomNode = function() {
};
/*
Remove any DOM nodes created by this widget or its children
Entry into destroy procedure
options include:
removeDOMNodes: boolean (default true)
*/
Widget.prototype.destroyChildren = function(options) {
$tw.utils.each(this.children,function(childWidget) {
childWidget.destroy(options);
});
};
/*
Legacy entry into destroy procedure
*/
Widget.prototype.removeChildDomNodes = function() {
// If this widget has directly created DOM nodes, delete them and exit. This assumes that any child widgets are contained within the created DOM nodes, which would normally be the case
if(this.domNodes.length > 0) {
$tw.utils.each(this.domNodes,function(domNode) {
domNode.parentNode.removeChild(domNode);
});
this.domNodes = [];
} else {
// Otherwise, ask the child widgets to delete their DOM nodes
$tw.utils.each(this.children,function(childWidget) {
childWidget.removeChildDomNodes();
});
this.destroy({removeDOMNodes: true});
};
/*
Default destroy
options include:
- removeDOMNodes: boolean (default true)
*/
Widget.prototype.destroy = function(options) {
const { removeDOMNodes = true } = options || {};
let removeChildDOMNodes = removeDOMNodes;
if(removeDOMNodes && this.domNodes.length > 0) {
// If this widget will remove its own DOM nodes, children should not remove theirs
removeChildDOMNodes = false;
}
// Destroy children first
this.destroyChildren({removeDOMNodes: removeChildDOMNodes});
this.children = [];
// Call custom cleanup method if implemented
if(typeof this.onDestroy === "function") {
this.onDestroy();
}
// Remove our DOM nodes if needed
if(removeDOMNodes) {
this.removeLocalDomNodes();
}
};
/*
Remove any DOM nodes created by this widget
*/
Widget.prototype.removeLocalDomNodes = function() {
for(const domNode of this.domNodes) {
if(domNode.parentNode) {
domNode.parentNode.removeChild(domNode);
}
}
this.domNodes = [];
};
/*

View File

@@ -369,31 +369,16 @@ Sort an array of tiddler titles by a specified field
isDescending: true if the sort should be descending
isCaseSensitive: true if the sort should consider upper and lower case letters to be different
*/
exports.sortTiddlers = function(titles,sortField,isDescending,isCaseSensitive,isNumeric,isAlphaNumeric) {
exports.sortTiddlers = function(titles,sortField,isDescending,isCaseSensitive,isNumeric,isAlphaNumeric,locale) {
var self = this;
if(sortField === "title") {
if(!isNumeric && !isAlphaNumeric) {
if(isCaseSensitive) {
if(isDescending) {
titles.sort(function(a,b) {
return b.localeCompare(a);
});
} else {
titles.sort(function(a,b) {
return a.localeCompare(b);
});
}
const sorter = new Intl.Collator(locale, { sensitivity: isCaseSensitive ? "variant" : "accent" });
if(isDescending) {
titles.sort((a,b) => sorter.compare(b, a));
} else {
if(isDescending) {
titles.sort(function(a,b) {
return b.toLowerCase().localeCompare(a.toLowerCase());
});
} else {
titles.sort(function(a,b) {
return a.toLowerCase().localeCompare(b.toLowerCase());
});
}
}
titles.sort((a,b) => sorter.compare(a, b));
}
} else {
titles.sort(function(a,b) {
var x,y;
@@ -414,14 +399,8 @@ exports.sortTiddlers = function(titles,sortField,isDescending,isCaseSensitive,is
}
}
}
if(isAlphaNumeric) {
return isDescending ? b.localeCompare(a,undefined,{numeric: true,sensitivity: "base"}) : a.localeCompare(b,undefined,{numeric: true,sensitivity: "base"});
}
if(!isCaseSensitive) {
a = a.toLowerCase();
b = b.toLowerCase();
}
return isDescending ? b.localeCompare(a) : a.localeCompare(b);
const sorter = new Intl.Collator(locale, { numeric: isAlphaNumeric, sensitivity: isAlphaNumeric ? "base" : isCaseSensitive ? "variant" : "accent" });
return isDescending ? sorter.compare(b, a) : sorter.compare(a, b);
});
}
} else {
@@ -463,14 +442,8 @@ exports.sortTiddlers = function(titles,sortField,isDescending,isCaseSensitive,is
}
a = String(a);
b = String(b);
if(isAlphaNumeric) {
return isDescending ? b.localeCompare(a,undefined,{numeric: true,sensitivity: "base"}) : a.localeCompare(b,undefined,{numeric: true,sensitivity: "base"});
}
if(!isCaseSensitive) {
a = a.toLowerCase();
b = b.toLowerCase();
}
return isDescending ? b.localeCompare(a) : a.localeCompare(b);
const sorter = new Intl.Collator(locale, { numeric: isAlphaNumeric, sensitivity: isAlphaNumeric ? "base" : isCaseSensitive ? "variant" : "accent" });
return isDescending ? sorter.compare(b, a) : sorter.compare(a, b);
});
}
};
@@ -1149,15 +1122,16 @@ Parse a block of text of a specified MIME type
Options include:
substitutions: an optional array of substitutions
*/
exports.getSubstitutedText = function(text,widget,options) {
exports.getSubstitutedText = function(text,thisWidget,options) {
options = options || {};
text = text || "";
var self = this,
widgetClass = widget.widget,
substitutions = options.substitutions || [],
output;
// Evaluate embedded filters and substitute with first result
output = text.replace(/\$\{([\S\s]+?)\}\$/g, function(match,filter) {
return self.filterTiddlers(filter,widget)[0] || "";
return self.filterTiddlers(filter,thisWidget)[0] || "";
});
// Process any substitutions provided in options
$tw.utils.each(substitutions,function(substitute) {
@@ -1165,7 +1139,7 @@ exports.getSubstitutedText = function(text,widget,options) {
});
// Substitute any variable references with their values
return output.replace(/\$\((.+?)\)\$/g, function(match,varname) {
return widget.getVariable(varname,{defaultValue: ""});
return widgetClass.evaluateVariable(thisWidget,varname, {defaultValue: ""})[0];
});
};
@@ -1690,12 +1664,14 @@ exports.addToStory = function(title,fromTitle,storyTitle,options) {
Generate a title for the draft of a given tiddler
*/
exports.generateDraftTitle = function(title) {
var c = 0,
draftTitle,
username = this.getTiddlerText("$:/status/UserName"),
attribution = username ? " by " + username : "";
let c = 0,
draftTitle;
const username = this.getTiddlerText("$:/status/UserName");
do {
draftTitle = "Draft " + (c ? (c + 1) + " " : "") + "of '" + title + "'" + attribution;
draftTitle = username ? $tw.language.getString("Draft/Attribution", {variables: {"draft-title": title}}) : $tw.language.getString("Draft/Title", {variables: {"draft-title": title}});
if(c) {
draftTitle = draftTitle.concat(" ", (c + 1).toString());
}
c++;
} while(this.tiddlerExists(draftTitle));
return draftTitle;

View File

@@ -0,0 +1,30 @@
title: $:/core/stylesheets/custom-properties
\rules only transcludeinline macrocallinline html transcludeblock
/* Tiddlywiki's CSS properties */
:root {
<$list filter="[[$:/palettes/Vanilla]indexes[]]">
--tpc-<<currentTiddler>>: <$transclude $variable="colour" $mode="inline" name=<<currentTiddler>>/>;
</$list>
/* CSS settings */
--tp-code-wrapping: {{$:/themes/tiddlywiki/vanilla/options/codewrapping}};
--tp-font-family: {{$:/themes/tiddlywiki/vanilla/settings/fontfamily}};
--tp-code-font-family: {{$:/themes/tiddlywiki/vanilla/settings/codefontfamily}};
--tp-editor-font-family: {{$:/themes/tiddlywiki/vanilla/settings/editorfontfamily}};
--tp-font-size: {{$:/themes/tiddlywiki/vanilla/metrics/fontsize}};
--tp-line-height: {{$:/themes/tiddlywiki/vanilla/metrics/lineheight}};
--tp-body-font-size: {{$:/themes/tiddlywiki/vanilla/metrics/bodyfontsize}};
--tp-body-line-height: {{$:/themes/tiddlywiki/vanilla/metrics/bodylineheight}};
--tp-story-left: {{$:/themes/tiddlywiki/vanilla/metrics/storyleft}};
--tp-story-top: {{$:/themes/tiddlywiki/vanilla/metrics/storytop}};
--tp-story-right: {{$:/themes/tiddlywiki/vanilla/metrics/storyright}};
--tp-story-width: {{$:/themes/tiddlywiki/vanilla/metrics/storyrwidth}};
--tp-tiddler-width: {{$:/themes/tiddlywiki/vanilla/metrics/tiddlerwidth}};
--tp-sidebar-breakpoint: {{$:/themes/tiddlywiki/vanilla/metrics/sidebarbreakpoint}};
--tp-sidebar-width: {{$:/themes/tiddlywiki/vanilla/metrics/sidebarwidth}};
--tp-animation-duration: {{{ [{$:/config/AnimationDuration}addsuffix[ms]] }}};
}

View File

@@ -25,7 +25,6 @@ title: $:/core/templates/tiddlywiki5.html
`{{{ [enlist<saveTiddlerAndShadowsFilter>tag[$:/core/wiki/rawmarkup]] ||$:/core/templates/plain-text-tiddler}}}
{{{ [enlist<saveTiddlerAndShadowsFilter>tag[$:/tags/RawMarkup]] ||$:/core/templates/plain-text-tiddler}}}
{{{ [enlist<saveTiddlerAndShadowsFilter>tag[$:/tags/RawMarkupWikified]] ||$:/core/templates/raw-static-tiddler}}}`
<!--~~ Style section start ~~-->
</head>
<body class="tc-body">
<!--~~ Raw markup for the top of the body section ~~-->

View File

@@ -18,6 +18,7 @@ caption: {{$:/language/ControlPanel/Basics/Caption}}
|<$link to="$:/config/NewTiddler/Tags"><<lingo NewTiddler/Tags/Prompt>></$link> |<$vars currentTiddler="$:/config/NewTiddler/Tags" tagField="text">{{||$:/core/ui/EditTemplate/tags}}<$list filter="[<currentTiddler>tags[]] +[limit[1]]" variable="ignore"><$button tooltip={{$:/language/ControlPanel/Basics/RemoveTags/Hint}}><<lingo RemoveTags>><$action-listops $tiddler=<<currentTiddler>> $field="text" $subfilter={{{ [<currentTiddler>get[tags]] }}}/><$action-setfield $tiddler=<<currentTiddler>> tags=""/></$button></$list></$vars> |
|<$link to="$:/config/NewJournal/Tags"><<lingo NewJournal/Tags/Prompt>></$link> |<$vars currentTiddler="$:/config/NewJournal/Tags" tagField="text">{{||$:/core/ui/EditTemplate/tags}}<$list filter="[<currentTiddler>tags[]] +[limit[1]]" variable="ignore"><$button tooltip={{$:/language/ControlPanel/Basics/RemoveTags/Hint}}><<lingo RemoveTags>><$action-listops $tiddler=<<currentTiddler>> $field="text" $subfilter={{{ [<currentTiddler>get[tags]] }}}/><$action-setfield $tiddler=<<currentTiddler>> tags=""/></$button></$list></$vars> |
|<$link to="$:/config/AutoFocus"><<lingo AutoFocus/Prompt>></$link> |{{$:/snippets/minifocusswitcher}} |
|<$link to="$:/config/AutoFocusEdit"><<lingo AutoFocusEdit/Prompt>></$link> |{{$:/snippets/minifocuseditswitcher}} |
|<<lingo Language/Prompt>> |{{$:/snippets/minilanguageswitcher}} |
|<<lingo Tiddlers/Prompt>> |<<show-filter-count "[!is[system]sort[title]]">> |
|<<lingo Tags/Prompt>> |<<show-filter-count "[tags[]sort[title]]">> |

View File

@@ -8,7 +8,7 @@ title: $:/core/ui/EditTemplate/body/editor
class="tc-edit-texteditor tc-edit-texteditor-body"
placeholder={{$:/language/EditTemplate/Body/Placeholder}}
tabindex={{$:/config/EditTabIndex}}
focus={{{ [{$:/config/AutoFocus}match[text]then[true]] ~[[false]] }}}
focus={{{ [{!!draft.of}is[tiddler]then{$:/config/AutoFocusEdit}match[text]then[true]] ~[{$:/config/AutoFocus}match[text]then[true]] ~[[false]] }}}
cancelPopups="yes"
fileDrop={{{ [{$:/config/DragAndDrop/Enable}match[no]] :else[subfilter{$:/config/Editor/EnableImportFilter}then[yes]else[no]] }}}

View File

@@ -103,9 +103,9 @@ title: $:/core/ui/EditTemplate/body/toolbar/button
<$set
name="buttonClasses"
value={{!!button-classes}}
value={{{ [subfilter{!!button-classes}] :and[join[ ]] }}}
><<toolbar-button>></$set>
\end
<<toolbar-button-outer>>
<<toolbar-button-outer>>

View File

@@ -1,157 +1,181 @@
title: $:/core/ui/EditTemplate/fields
tags: $:/tags/EditTemplate
\procedure lingo-base() $:/language/EditTemplate/
\function tf.config-title() [[$:/config/EditTemplateFields/Visibility/]addsuffix[$(currentField)$]substitute[]get[text]]
\function tf.config-filter() [[hide]] :except[title<tf.config-title>]
<!-- Beware this is duplicated from EditTemplate.tid. For details see bug #7054 -->
\procedure get-field-value-tiddler-filter() [subfilter<get-field-editor-filter>sha256[16]addprefix[/]addprefix<newFieldValueTiddlerPrefix>]
\procedure get-field-editor-filter() [<newFieldNameTiddler>get[text]else[]] :cascade[all[shadows+tiddlers]tag[$:/tags/FieldEditorFilter]!is[draft]get[text]] :and[!is[blank]else{$:/core/ui/EditTemplate/fieldEditor/default}]
\procedure prefix.bracket() [
\procedure suffix.bracket() ]
\function tf.current-tiddler-new-field-selector() [[data-tiddler-title=]addprefix[$(prefix.bracket)$]substitute[]addsuffix<currentTiddlerCSSescaped>addsuffix[$(suffix.bracket)$]substitute[]] .tc-edit-field-add-name-wrapper input :and[join[ ]]
\procedure new-field-actions()
\whitespace trim
<$action-sendmessage $message="tm-add-field" $name={{{ [<newFieldNameTiddler>get[text]] }}} $value={{{ [<newFieldNameTiddler>get[text]] :map[subfilter<get-field-value-tiddler-filter>get[text]] }}}/>
<$set name="safeNewFieldValueTiddlerPrefix" value=<<newFieldValueTiddlerPrefix>> emptyValue=<<qualify "$:/temp/NewFieldValue">> >
<$action-deletetiddler $filter="[<newFieldNameTiddler>] [prefix[$:/temp/NewFieldValue]prefix<safeNewFieldValueTiddlerPrefix>] [<storeTitle>] [<searchListState>]"/>
</$set>
<$action-sendmessage $message="tm-focus-selector" $param=<<tf.current-tiddler-new-field-selector>>/>
\end
\procedure lingo-base() $:/language/EditTemplate/
\procedure delete-state-tiddlers() <$action-deletetiddler $filter="[<newFieldNameTiddler>] [<storeTitle>] [<searchListState>]"/>
\procedure cancel-search-actions-inner()
\whitespace trim
<$list
filter="[<storeTitle>has[text]] [<newFieldNameTiddler>has[text]]"
variable="ignore"
emptyMessage="<<cancel-delete-tiddler-actions 'cancel'>>">
<<delete-state-tiddlers>>
</$list>
\procedure focus-new-field-input() <$action-sendmessage $message="tm-focus-selector" $param=`[data-tiddler-title="$(storyTiddler)$"] .tc-edit-field-add-name-wrapper input` />
\procedure new-field-actions()
<$action-setfield $tiddler=<<storyTiddler>> $field={{{ [<newFieldNameTiddler>get[text]] }}} $value={{{ [<newFieldValueTiddler>get[text]] }}} />
<$action-deletetiddler $filter="[prefix[$:/temp/NewFieldValue]prefix<newFieldValueTiddlerPrefix>]"/>
<<delete-state-tiddlers>>
<<focus-new-field-input>>
\end
\procedure delete-field-actions()
<$action-deletefield $field=<<currentField>>/>
<<focus-new-field-input>>
\end
\procedure cancel-search-actions()
\whitespace trim
<$set name="userInput" value={{{ [<storeTitle>get[text]] }}}>
<$list
filter="[<newFieldNameTiddler>get[text]!match<userInput>]"
emptyMessage="<<cancel-search-actions-inner>>">
<$action-setfield $tiddler=<<newFieldNameTiddler>> text=<<userInput>>/><$action-setfield $tiddler=<<refreshTitle>> text="yes"/>
</$list>
</$set>
<$let userInput={{{ [<storeTitle>get[text]] }}}>
<%if [<newFieldNameTiddler>get[text]!match<userInput>] %>
<$action-setfield $tiddler=<<newFieldNameTiddler>> text=<<userInput>>/>
<$action-setfield $tiddler=<<refreshTitle>> text="yes"/>
<%else%>
<%if [<storeTitle>has[text]] [<newFieldNameTiddler>has[text]] %>
<<delete-state-tiddlers>>
<%else%>
<<cancel-delete-tiddler-actions 'cancel'>>
<%endif%>
<%endif%>
</$let>
\end
\procedure new-field()
\whitespace trim
<$vars name={{{ [<newFieldNameTiddler>get[text]] }}}>
<$reveal type="nomatch" text="" default=<<name>>>
<$button tooltip={{$:/language/EditTemplate/Fields/Add/Button/Hint}}>
<$action-sendmessage $message="tm-add-field"
$name=<<name>>
$value={{{ [subfilter<get-field-value-tiddler-filter>get[text]] }}}/>
<$set name="safeNewFieldValueTiddlerPrefix" value=<<newFieldValueTiddlerPrefix>> emptyValue=<<qualify "$:/temp/NewFieldValue">> >
<$action-deletetiddler $filter="[<newFieldNameTiddler>] [prefix[$:/temp/NewFieldValue]prefix<safeNewFieldValueTiddlerPrefix>] [<storeTitle>] [<searchListState>]"/>
</$set>
<<lingo Fields/Add/Button>>
</$button>
</$reveal>
<$reveal type="match" text="" default=<<name>>>
<$button>
<<lingo Fields/Add/Button>>
</$button>
</$reveal>
</$vars>
<%if [<newFieldNameTiddler>get[text]!is[blank]] %>
<$button actions="<<new-field-actions>>" tooltip={{$:/language/EditTemplate/Fields/Add/Button/Hint}}>
<<lingo Fields/Add/Button>>
</$button>
<%else%>
<$button>
<<lingo Fields/Add/Button>>
</$button>
<%endif%>
\end
\whitespace trim
<$set name="newFieldValueTiddlerPrefix" value=<<newFieldValueTiddlerPrefix>> emptyValue=<<qualify "$:/temp/NewFieldValue">> >
<div class="tc-edit-fields">
<table class={{{ [all[current]fields[]] :filter[lookup[$:/config/EditTemplateFields/Visibility/]!match[hide]] :and[count[]!match[0]] :and[then[tc-edit-fields]] :else[[tc-edit-fields tc-edit-fields-small]] }}}>
<tbody>
<$list filter="[all[current]fields[]] :and[sort[title]]" variable="currentField" storyview="pop">
<$list filter=<<tf.config-filter>> variable="temp">
<tr class="tc-edit-field">
<td class="tc-edit-field-name">
<$text text=<<currentField>>/>:</td>
<td class="tc-edit-field-value">
<$keyboard key="((delete-field))" actions="""<$action-deletefield $field=<<currentField>>/><$set name="currentTiddlerCSSescaped" value={{{ [<currentTiddler>escapecss[]] }}}><$action-sendmessage $message="tm-focus-selector" $param=<<tf.current-tiddler-new-field-selector>>/></$set>""">
<$transclude tiddler={{{ [<currentField>] :cascade[all[shadows+tiddlers]tag[$:/tags/FieldEditorFilter]!is[draft]get[text]] :and[!is[blank]else{$:/core/ui/EditTemplate/fieldEditor/default}] }}} />
</$keyboard>
</td>
<td class="tc-edit-field-remove">
<$button class="tc-btn-invisible" tooltip={{$:/language/EditTemplate/Field/Remove/Hint}} aria-label={{$:/language/EditTemplate/Field/Remove/Caption}}>
<$action-deletefield $field=<<currentField>>/>
{{$:/core/images/delete-button}}
</$button>
</td>
</tr>
</$list>
</$list>
</tbody>
</table>
</div>
\function tf.config-filter() [lookup:show[$:/config/EditTemplateFields/Visibility/]!match[hide]]
<$fieldmangler>
<div class="tc-edit-field-add">
<em class="tc-edit tc-small-gap-right">
<<lingo Fields/Add/Prompt>>
</em>
<$vars refreshTitle=<<qualify "$:/temp/fieldname/refresh">> storeTitle=<<newFieldNameInputTiddler>> searchListState=<<newFieldNameSelectionTiddler>>>
<div class="tc-edit-field-add-name-wrapper">
<$transclude $variable="keyboard-driven-input" tiddler=<<newFieldNameTiddler>> storeTitle=<<storeTitle>> refreshTitle=<<refreshTitle>>
selectionStateTitle=<<searchListState>> tag="input" default="" placeholder={{$:/language/EditTemplate/Fields/Add/Name/Placeholder}}
focusPopup=<<qualify "$:/state/popup/field-dropdown">> class="tc-edit-texteditor tc-popup-handle" tabindex={{$:/config/EditTabIndex}}
focus={{{ [{$:/config/AutoFocus}match[fields]then[true]] :else[[false]] }}} cancelPopups="yes"
configTiddlerFilter="[[$:/config/EditMode/fieldname-filter]]" inputCancelActions=<<cancel-search-actions>> />
<$button popup=<<qualify "$:/state/popup/field-dropdown">> class="tc-btn-invisible tc-btn-dropdown tc-small-gap" tooltip={{$:/language/EditTemplate/Field/Dropdown/Hint}} aria-label={{$:/language/EditTemplate/Field/Dropdown/Caption}}>{{$:/core/images/down-arrow}}</$button>
<$reveal state=<<qualify "$:/state/popup/field-dropdown">> type="nomatch" text="" default="">
<div class="tc-block-dropdown tc-edit-type-dropdown">
<$set name="tv-show-missing-links" value="yes">
<$linkcatcher to=<<newFieldNameTiddler>>>
<div class="tc-dropdown-item">
<<lingo Fields/Add/Dropdown/User>>
</div>
<$set name="newFieldName" value={{{ [<storeTitle>get[text]] }}}>
<$list filter="[!is[shadow]!is[system]fields[]search:title<newFieldName>sort[]] :except[[created]] :except[[creator]] :except[[draft.of]] :except[[draft.title]] :except[[modified]] :except[[modifier]] :except[[tags]] :except[[text]] :except[[title]] :except[[type]]" variable="currentField">
<$list filter="[<currentField>addsuffix[-primaryList]] :except[<searchListState>get[text]]" emptyMessage="""<$link to=<<currentField>> class="tc-list-item-selected"><$text text=<<currentField>>/></$link>""">
<$link to=<<currentField>>>
<$text text=<<currentField>>/>
</$link>
</$list>
</$list>
<div class="tc-dropdown-item">
<<lingo Fields/Add/Dropdown/System>>
</div>
<$list filter="[fields[]search:title<newFieldName>sort[]] :except[!is[shadow]!is[system]fields[]]" variable="currentField">
<$list filter="[<currentField>addsuffix[-secondaryList]] :except[<searchListState>get[text]]" emptyMessage="""<$link to=<<currentField>> class="tc-list-item-selected"><$text text=<<currentField>>/></$link>""">
<$link to=<<currentField>>>
<$text text=<<currentField>>/>
</$link>
</$list>
</$list>
</$set>
</$linkcatcher>
</$set>
</div>
</$reveal>
</div>
<$let currentTiddlerCSSescaped={{{ [<currentTiddler>escapecss[]] }}} currentTiddler={{{ [subfilter<get-field-value-tiddler-filter>] }}} currentField="text" currentFieldName={{{ [<newFieldNameTiddler>get[text]] }}}>
<span class="tc-edit-field-add-value tc-small-gap-right">
<$keyboard key="((add-field))" actions=<<new-field-actions>>>
<$transclude tiddler={{{ [subfilter<get-field-editor-filter>] }}} />
</$keyboard>
</span>
<span class="tc-edit-field-add-button">
<$transclude $variable="new-field"/>
</span>
</$let>
</$vars>
</div>
</$fieldmangler>
</$set>
\function tf.field-cascade()
[<currentField>]
:cascade[all[shadows+tiddlers]tag[$:/tags/FieldEditorFilter]!is[draft]get[text]]
:and[!is[blank]else{$:/core/ui/EditTemplate/fieldEditor/default}]
\end
\function tf.get-field-editor()
[<newFieldNameTiddler>get[text]else[]]
:cascade[all[shadows+tiddlers]tag[$:/tags/FieldEditorFilter]!is[draft]get[text]]
:and[!is[blank]else{$:/core/ui/EditTemplate/fieldEditor/default}]
\end
\function tf.primary-list-exceptions() created creator draft.of draft.title modified modifier tags text title type
\function tf.list-selection-class(listSuffix) [<searchListState>get[text]removesuffix<listSuffix>match<currentField>then[tc-list-item-selected]]
<$let newFieldValueTiddlerPrefix={{{ [<newFieldValueTiddlerPrefix>!is[blank]else<qualify "$:/temp/NewFieldValue">] }}} >
<div class="tc-edit-fields">
<!-- table of user fields of the current tiddler -->
<table class=`tc-edit-fields ${ [all[current]fields[]] :filter[tf.config-filter[]] :and[count[]match[0]then[tc-edit-fields-small]] }$`>
<tbody>
<$list filter="[all[current]fields[]] :and[sort[title]]" variable="currentField" storyview="pop">
<%if [<currentField>tf.config-filter[]] %>
<tr class="tc-edit-field">
<td class="tc-edit-field-name">
<$text text=<<currentField>>/>:
</td>
<td class="tc-edit-field-value">
<$keyboard key="((delete-field))" actions="<<delete-field-actions>>">
<$transclude tiddler=<<tf.field-cascade>> />
</$keyboard>
</td>
<td class="tc-edit-field-remove">
<$button actions="<<delete-field-actions>>"
aria-label={{$:/language/EditTemplate/Field/Remove/Caption}}
class="tc-btn-invisible"
tooltip={{$:/language/EditTemplate/Field/Remove/Hint}}
>
{{$:/core/images/delete-button}}
</$button>
</td>
</tr>
<%endif%>
</$list>
</tbody>
</table>
</div>
<!-- input control for new field name with selection dropdown -->
<div class="tc-edit-field-add">
<em class="tc-edit tc-small-gap-right">
<<lingo Fields/Add/Prompt>>
</em>
<$let refreshTitle=<<qualify "$:/temp/fieldname/refresh">>
storeTitle=<<newFieldNameInputTiddler>>
searchListState=<<newFieldNameSelectionTiddler>>
>
<div class="tc-edit-field-add-name-wrapper">
<$transclude $variable="keyboard-driven-input"
cancelPopups="yes"
class=`tc-edit-texteditor tc-popup-handle ${ [<newFieldNameTiddler>get[text]] :intersection[<storyTiddler>fields[]] :then[[tc-edit-field-exists]] }$`
configTiddlerFilter="[[$:/config/EditMode/fieldname-filter]]"
default=""
focus={{{ [{!!draft.of}is[tiddler]then{$:/config/AutoFocusEdit}match[fields]then[true]] :else[{$:/config/AutoFocus}match[fields]then[true]] :else[[false]] }}}
focusPopup=<<qualify "$:/state/popup/field-dropdown">>
inputAcceptVariantActions=<<save-tiddler-actions>>
inputCancelActions=<<cancel-search-actions>>
placeholder={{$:/language/EditTemplate/Fields/Add/Name/Placeholder}}
refreshTitle=<<refreshTitle>>
selectionStateTitle=<<searchListState>>
storeTitle=<<storeTitle>>
tag="input"
tabindex={{$:/config/EditTabIndex}}
tiddler=<<newFieldNameTiddler>>
/>
<$button aria-label={{$:/language/EditTemplate/Field/Dropdown/Caption}}
class="tc-btn-invisible tc-btn-dropdown tc-small-gap"
popup=<<qualify "$:/state/popup/field-dropdown">>
tooltip={{$:/language/EditTemplate/Field/Dropdown/Hint}}
>
{{$:/core/images/down-arrow}}
</$button>
<$reveal state=<<qualify "$:/state/popup/field-dropdown">> type="nomatch" text="" default="" tag="div" class="tc-block-dropdown tc-edit-type-dropdown">
<$let tv-show-missing-links="yes">
<$linkcatcher to=<<newFieldNameTiddler>>>
<div class="tc-dropdown-item">
<<lingo Fields/Add/Dropdown/User>>
</div>
<$let newFieldName={{{ [<storeTitle>get[text]] }}}
primaryListFields={{{ [!is[shadow]!is[system]fields[]format:titlelist[]join[ ]] }}}
>
<$list filter="[enlist<primaryListFields>search:title<newFieldName>sort[]] :except[tf.primary-list-exceptions[]]" variable="currentField">
<$link to=<<currentField>> class=<<tf.list-selection-class "-primaryList">> >
<$text text=<<currentField>>/>
</$link>
</$list>
<div class="tc-dropdown-item">
<<lingo Fields/Add/Dropdown/System>>
</div>
<$list filter="[fields[]search:title<newFieldName>!enlist<primaryListFields>sort[]]" variable="currentField">
<$link to=<<currentField>> class=<<tf.list-selection-class "-secondaryList">>>
<$text text=<<currentField>>/>
</$link>
</$list>
</$let>
</$linkcatcher>
</$let>
</$reveal>
</div>
<!-- input control for new field content -->
<$let currentFieldName={{{ [<newFieldNameTiddler>get[text]] }}}
fieldEditor=<<tf.get-field-editor>>
newFieldValueTiddler={{{ [<newFieldValueTiddlerPrefix>] [[/]] [<fieldEditor>sha256[16]] :and[join[]] }}}
currentTiddler=<<newFieldValueTiddler>>
>
<span class="tc-edit-field-add-value tc-small-gap-right">
<$keyboard key="((add-field))" actions="<<new-field-actions>>">
<$transclude $tiddler=<<fieldEditor>> />
</$keyboard>
</span>
<span class="tc-edit-field-add-button">
<$transclude $variable="new-field"/>
</span>
</$let>
</$let>
</div>
</$let>

View File

@@ -17,7 +17,7 @@ tags: $:/tags/EditTemplate
<$let backgroundColor=<<colour>> >
<span class="tc-tag-label tc-tag-list-item tc-small-gap-right"
data-tag-title=<<currentTiddler>>
style=`color:$(foregroundColor)$; background-color:$(backgroundColor)$;`
style=`color:$(foregroundColor)$; background-color:$(backgroundColor)$; --tp-remove-tag-button-color:$(foregroundColor)$`
>
<$transclude tiddler=<<icon>>/>
<$view field="title" format="text"/>

View File

@@ -2,7 +2,11 @@ title: $:/core/ui/EditTemplate/title
tags: $:/tags/EditTemplate
\whitespace trim
<$edit-text field="draft.title" class="tc-titlebar tc-edit-texteditor" focus={{{ [{$:/config/AutoFocus}match[title]then[true]] ~[[false]] }}} tabindex={{$:/config/EditTabIndex}} cancelPopups="yes"/>
<$edit-text field="draft.title" class="tc-titlebar tc-edit-texteditor"
focus={{{ [{!!draft.of}is[tiddler]then{$:/config/AutoFocusEdit}match[title]then[true]] ~[{$:/config/AutoFocus}match[title]then[true]] ~[[false]] }}}
tabindex={{$:/config/EditTabIndex}}
cancelPopups="yes"
/>
<$vars pattern="""[\|\[\]{}]""" bad-chars="""`| [ ] { }`""">

View File

@@ -4,13 +4,28 @@ first-search-filter: [all[shadows+tiddlers]prefix[$:/language/Docs/Types/]sort[d
\procedure lingo-base() $:/language/EditTemplate/
\procedure input-cancel-actions() <$list filter="[<storeTitle>get[text]] [<currentTiddler>get[type]] :and[limit[1]]" emptyMessage="""<<cancel-delete-tiddler-actions "cancel">>"""><$action-sendmessage $message="tm-remove-field" $param="type"/><$action-deletetiddler $filter="[<typeInputTiddler>] [<refreshTitle>] [<typeSelectionTiddler>]"/></$list>
\whitespace trim
<$set name="refreshTitle" value=<<qualify "$:/temp/type-search/refresh">>>
<div class="tc-edit-type-selector-wrapper">
<em class="tc-edit tc-small-gap-right"><<lingo Type/Prompt>></em>
<div class="tc-type-selector-dropdown-wrapper">
<div class="tc-type-selector"><$fieldmangler>
<$transclude $variable="keyboard-driven-input" tiddler=<<currentTiddler>> storeTitle=<<typeInputTiddler>> refreshTitle=<<refreshTitle>> selectionStateTitle=<<typeSelectionTiddler>> field="type" tag="input" default="" placeholder={{$:/language/EditTemplate/Type/Placeholder}} focusPopup=<<qualify "$:/state/popup/type-dropdown">> class="tc-edit-typeeditor tc-edit-texteditor tc-popup-handle tc-keep-focus" tabindex={{$:/config/EditTabIndex}} focus={{{ [{$:/config/AutoFocus}match[type]then[true]] :else[[false]] }}} cancelPopups="yes" configTiddlerFilter="[[$:/core/ui/EditTemplate/type]]" inputCancelActions=<<input-cancel-actions>>/><$button popup=<<qualify "$:/state/popup/type-dropdown">> class="tc-btn-invisible tc-btn-dropdown tc-small-gap" tooltip={{$:/language/EditTemplate/Type/Dropdown/Hint}} aria-label={{$:/language/EditTemplate/Type/Dropdown/Caption}}>{{$:/core/images/down-arrow}}</$button><$button message="tm-remove-field" param="type" class="tc-btn-invisible tc-btn-icon" tooltip={{$:/language/EditTemplate/Type/Delete/Hint}} aria-label={{$:/language/EditTemplate/Type/Delete/Caption}}>{{$:/core/images/delete-button}}<$action-deletetiddler $filter="[<typeInputTiddler>] [<storeTitle>] [<refreshTitle>] [<selectionStateTitle>]"/></$button>
<div class="tc-type-selector">
<$fieldmangler>
<$transclude $variable="keyboard-driven-input" tiddler=<<currentTiddler>> storeTitle=<<typeInputTiddler>> refreshTitle=<<refreshTitle>>
selectionStateTitle=<<typeSelectionTiddler>> field="type" tag="input" default="" placeholder={{$:/language/EditTemplate/Type/Placeholder}}
focusPopup=<<qualify "$:/state/popup/type-dropdown">> class="tc-edit-typeeditor tc-edit-texteditor tc-popup-handle tc-keep-focus"
tabindex={{$:/config/EditTabIndex}}
focus={{{ [{!!draft.of}is[tiddler]then{$:/config/AutoFocusEdit}match[type]then[true]] :else[{$:/config/AutoFocus}match[type]then[true]] :else[[false]] }}}
cancelPopups="yes" configTiddlerFilter="[[$:/core/ui/EditTemplate/type]]"
inputCancelActions=<<input-cancel-actions>>
/>
<$button popup=<<qualify "$:/state/popup/type-dropdown">> class="tc-btn-invisible tc-btn-dropdown tc-small-gap" tooltip={{$:/language/EditTemplate/Type/Dropdown/Hint}} aria-label={{$:/language/EditTemplate/Type/Dropdown/Caption}}>
{{$:/core/images/down-arrow}}
</$button>
<$button message="tm-remove-field" param="type" class="tc-btn-invisible tc-btn-icon" tooltip={{$:/language/EditTemplate/Type/Delete/Hint}} aria-label={{$:/language/EditTemplate/Type/Delete/Caption}}>
{{$:/core/images/delete-button}}<$action-deletetiddler $filter="[<typeInputTiddler>] [<storeTitle>] [<refreshTitle>] [<selectionStateTitle>]"/>
</$button>
</$fieldmangler></div>
<div class="tc-block-dropdown-wrapper">

View File

@@ -1,5 +1,5 @@
title: $:/core/Filters/StoryList
tags: $:/tags/Filter
filter: [list[$:/StoryList]] -$:/AdvancedSearch
filter: [<tv-story-list>is[variable]then<tv-story-list>else[$:/StoryList]] =>storylist [list<storylist>] -$:/AdvancedSearch
description: {{$:/language/Filters/StoryList}}

View File

@@ -8,6 +8,8 @@ code-body: yes
<$set name="languageTitle" value={{!!name}}>
<$transclude $tiddler="$:/core/stylesheets/custom-properties" $mode="block"/>
<$list filter="[all[shadows+tiddlers]tag[$:/tags/Stylesheet]!has[draft.of]]">
<$transclude mode="block"/>
</$list>

View File

@@ -1,7 +1,7 @@
title: $:/core/ui/PageTemplate/alerts
tags: $:/tags/PageTemplate
<div class="tc-alerts" role="region" aria-label="Alerts">
<div class="tc-alerts" role="region" aria-label={{$:/language/Alerts}}>
<$list filter="[all[shadows+tiddlers]tag[$:/tags/Alert]!is[draft]]" template="$:/core/ui/AlertTemplate" storyview="pop"/>

View File

@@ -1,15 +0,0 @@
title: $:/core/ui/RootStylesheet
code-body: yes
\import [subfilter{$:/core/config/GlobalImportFilter}]
\whitespace trim
<$let currentTiddler={{$:/language}} languageTitle={{!!name}}>
<style type="text/css">
<$view tiddler="$:/themes/tiddlywiki/vanilla/reset" format="text" mode="block"/>
</style>
<$list filter="[all[shadows+tiddlers]tag[$:/tags/Stylesheet]!is[draft]] -[[$:/themes/tiddlywiki/vanilla/reset]]">
<style type="text/css">
<$view format={{{ [<currentTiddler>tag[$:/tags/Stylesheet/Static]then[text]else[plainwikified]] }}} mode="block"/>
</style>
</$list>
</$let>

View File

@@ -15,7 +15,7 @@ caption: {{$:/language/SideBar/Open/Caption}}
\define droppable-item(button)
\whitespace trim
<$droppable actions=<<drop-actions>> enable=<<tv-allow-drag-and-drop>> tag="div">
<$droppable actions=<<drop-actions>> enable=<<tv-enable-drag-and-drop>> tag="div">
<<placeholder>>
<div>
$button$

View File

@@ -2,7 +2,7 @@ title: $:/core/ui/TiddlerInfo
\whitespace trim
<div style="position:relative;">
<div class="tc-tiddler-controls" style="position:absolute;right:0;">
<div class="tc-tiddler-controls tc-tiddler-info-controls">
<$reveal state="$:/config/TiddlerInfo/Mode" type="match" text="sticky">
<$button set=<<tiddlerInfoState>> setTo="" tooltip={{$:/language/Buttons/Info/Hint}} aria-label={{$:/language/Buttons/Info/Caption}} class="tc-btn-invisible">
{{$:/core/images/close-button}}

View File

@@ -0,0 +1,38 @@
title: $:/core/ui/TiddlerInfo/Advanced/CascadeInfo
tags: $:/tags/TiddlerInfo/Advanced
\define lingo-base() $:/language/TiddlerInfo/Advanced/CascadeInfo/
<$let infoTiddler=<<currentTiddler>>>
''<<lingo Heading>>''
<table class="tc-max-width">
<thead>
<$list filter="[[View]] [[Active Cascade Filter]] [[Template]]" variable="heading">
<th><<heading>></th>
</$list>
</thead>
<$list filter="[[$:/tags/ViewTemplate]tagging[]]" variable="ViewTemplate">
<tr>
<$let
view={{{ [<ViewTemplate>]+[split[/]last[]] }}}
tagFilter=`$:/tags/ViewTemplate${ [<view>titlecase[]] }$Filter`
activeCascadeFilterTiddler={{{ [all[shadows+tiddlers]tag<tagFilter>!is[draft]]:filter[<storyTiddler>subfilter{!!text}]+[first[]] }}}
activeCascadeFilter={{{ [<activeCascadeFilterTiddler>get[text]] }}}
activeTemplateTiddler={{{ [<currentTiddler>]:cascade[all[shadows+tiddlers]tag<tagFilter>!is[draft]get[text]] }}}
>
<%if [<activeCascadeFilterTiddler>!is[blank]]%>
<td>
<$link to=<<ViewTemplate>> ><<view>></$link>
</td>
<td>
<$link to=<<activeCascadeFilterTiddler>> />
</td>
<td style="text-align:center;">
<$link class="tc-btn-invisible" to=<<activeTemplateTiddler>>><$button class="tc-btn-invisible">{{$:/core/images/file}}</$button></$link>
</td>
<%endif%>
</$let>
</tr>
</$list>
</table>

View File

@@ -11,9 +11,13 @@ tags: $:/tags/ViewTemplate
storyview="pop"
variable="listItem"
>
<$set name="tv-config-toolbar-class" filter="[<tv-config-toolbar-class>] [<listItem>encodeuricomponent[]addprefix[tc-btn-]]">
<$transclude tiddler=<<listItem>>/>
</$set>
<$let condition={{{ [<listItem>get[condition]] }}}>
<%if [<condition>!is[blank]] :and[<currentTiddler>subfilter<condition>limit[1]] :else[<condition>is[blank]then[true]] %>
<$set name="tv-config-toolbar-class" filter="[<tv-config-toolbar-class>] [<listItem>encodeuricomponent[]addprefix[tc-btn-]]">
<$transclude tiddler=<<listItem>>/>
</$set>
<%endif%>
</$let>
</$list>
</span>
<$set name="tv-wikilinks" value={{$:/config/Tiddlers/TitleLinks}}>

View File

@@ -41,6 +41,8 @@ Drag this link to copy this tool to another wiki
</$wikify>
\end capture-item-wikified
\function get.shadow.source() [shadowsource[]]
\procedure capture-wiki-info(tempWikiInfo)
<$transclude $variable="capture-item-wikified" label="TiddlyWiki Version" value="<<version>>"/>
<$transclude $variable="capture-item" label="Current palette" value={{$:/palette}}/>
@@ -64,6 +66,7 @@ Drag this link to copy this tool to another wiki
<$transclude $variable="capture-item" label="Keyboard shortcuts that have been customised" value={{{ [all[tiddlers]prefix[$:/config/shortcuts]] +[join[,]] }}}/>
<$transclude $variable="capture-item" label="Disabled plugins" value={{{ [all[tiddlers]prefix[$:/config/Plugins/Disabled/]] :filter[{!!text}match[yes]] :map[<currentTiddler>removeprefix[$:/config/Plugins/Disabled/]] +[join[,]] }}}/>
<$transclude $variable="capture-item" label="Plugins" value={{{ [has[plugin-type]sort[]] :filter[<currentTiddler>addprefix[$:/config/Plugins/Disabled/]get[text]else[no]!match[yes]] :map[{!!version}addprefix[ - ]addprefix<currentTiddler>] +[addprefix[ ]addprefix<crlf>join[]] }}}/>
<$transclude $variable="capture-item" label="Stylesheets" value={{{ [all[shadows+tiddlers]tag[$:/tags/Stylesheet]!is[draft]] :map[is[shadow]addsuffix[ ∈ ]addsuffix<get.shadow.source>else<currentTiddler>] +[addprefix[ ]addprefix<crlf>join[]] }}}/>
\end capture-wiki-info
\procedure template-header()

View File

@@ -0,0 +1,5 @@
title: $:/core/wiki/config/MediaQueryTrackers/DarkLightPreferred
tags: $:/tags/MediaQueryTracker
media-query: (prefers-color-scheme: dark)
info-tiddler: $:/info/browser/darkmode
info-tiddler-alt: $:/info/darkmode

View File

@@ -0,0 +1,39 @@
title: $:/core/macros/CSS/property
<!-- CSS property macros -->
<!-- TODO: Deprecate the following CSS macros once 2020 baseline is supported -->
\procedure margin-start(size)
-webkit-margin-start: <<size>>;
margin-inline-start: <<size>>;
\end
\procedure margin-end(size)
-webkit-margin-end: <<size>>;
margin-inline-end: <<size>>;
\end
\procedure padding-start(size)
-webkit-padding-start: <<size>>;
padding-inline-start: <<size>>;
\end
\procedure padding-end(size)
-webkit-padding-end: <<size>>;
padding-inline-end: <<size>>;
\end
\procedure margin-inline(start,end)
-webkit-margin-start: <<start>>;
margin-inline-start: <<start>>;
-webkit-margin-end: <<end>>;
margin-inline-end: <<end>>;
\end
\procedure padding-inline(start,end)
-webkit-padding-start: <<start>>;
padding-inline-start: <<start>>;
-webkit-padding-end: <<end>>;
padding-inline-end: <<end>>;
\end

View File

@@ -22,7 +22,7 @@ tags: $:/tags/Macro
<$action-listops $tiddler=<<targetTiddler>> $field=<<targetField>> $subfilter="+[insertbefore<actionTiddler>,<currentTiddler>]"/>
\end
\define list-links-draggable(tiddler,field:"list",emptyMessage,type:"ul",subtype:"li",class:"",itemTemplate)
\define list-links-draggable(tiddler,field:"list",emptyMessage,type:"ul",subtype:"li",class:"",itemTemplate,displayField:"caption",startactions,endactions)
\whitespace trim
<$set name="_tiddler" value="""$tiddler$""" emptyValue=<<currentTiddler>> >
<$let field-reference={{{ [<_tiddler>] "!!" [[$field$]] +[join[]] }}}
@@ -39,10 +39,13 @@ tags: $:/tags/Macro
>
<div class="tc-droppable-placeholder"/>
<div>
<$transclude tiddler="""$itemTemplate$""">
<$link to={{!!title}}>
<$transclude tiddler=<<__itemTemplate__>>>
<$link to={{!!title}}
startactions=<<__startactions__>>
endactions=<<__endactions__>>
>
<$let tv-wikilinks="no">
<$transclude field="caption">
<$transclude field=<<__displayField__>>>
<$view field="title"/>
</$transclude>
</$let>
@@ -92,7 +95,7 @@ tags: $:/tags/Macro
</$set>
\end
\define list-tagged-draggable(tag,subFilter,emptyMessage,itemTemplate,elementTag:"div",storyview:"")
\define list-tagged-draggable(tag,subFilter,emptyMessage,itemTemplate,elementTag:"div",storyview:"",displayField:"title",startactions,endactions)
\whitespace trim
<span class="tc-tagged-draggable-list">
<$set name="tag" value=<<__tag__>>>
@@ -108,9 +111,16 @@ tags: $:/tags/Macro
>
<$genesis $type=<<__elementTag__>> class="tc-droppable-placeholder"/>
<$genesis $type=<<__elementTag__>>>
<$transclude tiddler="""$itemTemplate$""">
<$link to={{!!title}}>
<$view field="title"/>
<$transclude tiddler=<<__itemTemplate__>>>
<$link to={{!!title}}
startactions=<<__startactions__>>
endactions=<<__endactions__>>
>
<$let tv-wikilinks="no">
<$transclude field=<<__displayField__>>>
<$view field="title"/>
</$transclude>
</$let>
</$link>
</$transclude>
</$genesis>

View File

@@ -9,8 +9,9 @@ code-body: yes
setTo=<<currentTab>>
default=<<__default__>>
selectedClass="tc-tab-selected"
selectedAria="aria-selected"
tooltip={{!!tooltip}}
role="switch"
role="tab"
data-tab-title=<<currentTab>>
>
<$tiddler tiddler=<<save-currentTiddler>>>
@@ -57,12 +58,12 @@ code-body: yes
\whitespace trim
<$qualify title=<<__state__>> name="qualifiedState">
<$let tabsState={{{ [<__explicitState__>minlength[1]] ~[<qualifiedState>] }}}>
<div class={{{ [[tc-tab-set]addsuffix[ ]addsuffix<__class__>] }}}>
<div class={{{ [[tc-tab-set]addsuffix[ ]addsuffix<__class__>] }}} role="tablist">
<div class={{{ [[tc-tab-buttons]addsuffix[ ]addsuffix<__class__>] }}}>
<<tabs-tab-list>>
</div>
<div class={{{ [[tc-tab-divider]addsuffix[ ]addsuffix<__class__>] }}}/>
<div class={{{ [[tc-tab-content]addsuffix[ ]addsuffix<__class__>] }}}>
<div class={{{ [[tc-tab-content]addsuffix[ ]addsuffix<__class__>] }}} role="tabpanel">
<<tabs-tab-body>>
</div>
</div>

View File

@@ -21,7 +21,7 @@ second-search-filter: [subfilter<tagListFilter>is[system]search:title<userInput>
<!-- clean up temporary tiddlers, so the next "pick" starts with a clean input -->
<!-- This could probably be optimized / removed if we would use different temp-tiddlers
(future improvement because keeping track is comlex for humans)
(future improvement because keeping track is complex for humans)
-->
\procedure delete-tag-state-tiddlers()
<$action-deletetiddler $filter="[<newTagNameTiddler>] [<storeTitle>] [<tagSelectionState>]"/>
@@ -111,13 +111,14 @@ The second ESC tries to close the "draft tiddler"
refreshTitle=<<refreshTitle>>
selectionStateTitle=<<tagSelectionState>>
inputAcceptActions=<<add-tag-actions>>
inputAcceptVariantActions=<<save-tiddler-actions>>
inputCancelActions=<<clear-tags-actions>>
tag="input"
placeholder={{$:/language/EditTemplate/Tags/Add/Placeholder}}
focusPopup=<<tf.tagpicker-dropdown-id>>
class="tc-edit-texteditor tc-popup-handle"
tabindex=<<tabIndex>>
focus={{{ [{$:/config/AutoFocus}match[tags]then[true]] :else[[false]] }}}
focus={{{ [{!!draft.of}is[tiddler]then{$:/config/AutoFocusEdit}match[tags]then[true]] :else[{$:/config/AutoFocus}match[tags]then[true]] :else[[false]] }}}
filterMinLength={{$:/config/Tags/MinLength}}
cancelPopups=<<cancelPopups>>
configTiddlerFilter="[[$:/core/macros/tag-picker]]"

View File

@@ -15,7 +15,18 @@ tags: $:/tags/Macro
</span>
\end
\define toc-body(tag,sort:"",itemClassFilter,exclude,path)
\define toc-level-indicator()
\whitespace trim
<%if [<__level__>compare:number:gt[0]]%>
<%if [<currentTiddler>tagging[]] %>
<span class="tc-tiny-gap-left">{{$:/core/images/new-button}}</span>
<%else%>
<span class="tc-tiny-gap-left">{{$:/core/images/blank}}</span>
<%endif%>
<% endif %>
\end
\define toc-body(tag,sort:"",itemClassFilter,exclude,path,level)
\whitespace trim
<ol class="tc-toc">
<$list filter="""[all[shadows+tiddlers]tag<__tag__>!has[draft.of]$sort$] -[<__tag__>] -[subfilter<__exclude__>]""">
@@ -23,10 +34,26 @@ tags: $:/tags/Macro
<$set name="excluded" filter="[subfilter<__exclude__>] [<__tag__>]">
<$set name="toc-item-class" filter=<<__itemClassFilter__>> emptyValue="toc-item-selected" value="toc-item">
<li class=<<toc-item-class>>>
<$list filter="[all[current]toc-link[no]]" emptyMessage="<$link to={{{ [<currentTiddler>get[target]else<currentTiddler>] }}}><<toc-caption>></$link>">
<$list filter="[all[current]toc-link[no]]" >
<$list-empty>
<!-- link to target-field or currentTiddler -->
<$link to={{{ [<currentTiddler>get[target]else<currentTiddler>] }}}>
<<toc-level-indicator>>
<<toc-caption>>
</$link>
</$list-empty>
<!-- toc-link = no -->
<<toc-level-indicator>>
<<toc-caption>>
</$list>
<$macrocall $name="toc-body" tag=<<item>> sort=<<__sort__>> itemClassFilter=<<__itemClassFilter__>> exclude=<<excluded>> path=<<path>>/>
<$let _level={{{ [<__level__>subtract[1]] }}}>
<%if [<_level>compare:number:gt[0]]%>
<$macrocall $name="toc-body" tag=<<item>> sort=<<__sort__>> itemClassFilter=<<__itemClassFilter__>> exclude=<<excluded>> path=<<path>> level=<<_level>>/>
<%elseif [<_level>match[-1]]%>
<!-- show full toc, no level defined -->
<$macrocall $name="toc-body" tag=<<item>> sort=<<__sort__>> itemClassFilter=<<__itemClassFilter__>> exclude=<<excluded>> path=<<path>>/>
<%endif%>
</$let>
</li>
</$set>
</$set>
@@ -35,10 +62,10 @@ tags: $:/tags/Macro
</ol>
\end
\define toc(tag,sort:"",itemClassFilter:"", exclude)
\define toc(tag,sort:"",itemClassFilter:"",exclude,level)
\whitespace trim
<$let __tag__={{{ [<__tag__>is[blank]then<currentTiddler>else<__tag__>] }}} >
<$macrocall $name="toc-body" tag=<<__tag__>> sort=<<__sort__>> itemClassFilter=<<__itemClassFilter__>> exclude=<<__exclude__>>/>
<$macrocall $name="toc-body" tag=<<__tag__>> sort=<<__sort__>> itemClassFilter=<<__itemClassFilter__>> exclude=<<__exclude__>> level=<<__level__>>/>
</$let>
\end

View File

@@ -0,0 +1,8 @@
title: $:/snippets/minifocuseditswitcher
\whitespace trim
<$select tiddler="$:/config/AutoFocusEdit" default={{$:/config/AutoFocus}}>
<$list filter="title tags text">
<option><<currentTiddler>></option>
</$list>
</$select>

View File

@@ -1,2 +1,2 @@
title: $:/tags/TiddlerInfo/Advanced
list: [[$:/core/ui/TiddlerInfo/Advanced/ShadowInfo]] [[$:/core/ui/TiddlerInfo/Advanced/PluginInfo]]
list: [[$:/core/ui/TiddlerInfo/Advanced/ShadowInfo]] [[$:/core/ui/TiddlerInfo/Advanced/PluginInfo]] [[$:/core/ui/TiddlerInfo/Advanced/CascadeInfo]]

View File

@@ -20,8 +20,7 @@
"index": [
"--rendertiddler","$:/core/save/all","index.html","text/plain"],
"empty": [
"--rendertiddler","$:/editions/de-AT-DE/download-empty","empty.html","text/plain",
"--rendertiddler","$:/editions/de-AT-DE/download-empty","empty.hta","text/plain"],
"--rendertiddler","$:/editions/de-AT-DE/download-empty","empty.html","text/plain"],
"favicon": [
"--savetiddler","$:/favicon.ico","favicon.ico"],
"static": [

View File

@@ -24,8 +24,7 @@
"index": [
"--rendertiddler","$:/core/save/all","index.html","text/plain"],
"empty": [
"--rendertiddler","$:/editions/de-AT-DE/download-empty","empty.html","text/plain",
"--rendertiddler","$:/editions/de-AT-DE/download-empty","empty.hta","text/plain"],
"--rendertiddler","$:/editions/de-AT-DE/download-empty","empty.html","text/plain"],
"favicon": [
"--savetiddler","$:/favicon.ico","favicon.ico"],
"static": [

View File

@@ -10,8 +10,7 @@
"index": [
"--render","$:/core/save/all","index.html","text/plain"],
"empty": [
"--render","$:/core/save/all","empty.html","text/plain",
"--render","$:/core/save/all","empty.hta","text/plain"],
"--render","$:/core/save/all","empty.html","text/plain"],
"emptyexternalcore": [
"--render","$:/core/save/offline-external-js","empty-external-core.html","text/plain",
"--render","$:/core/templates/tiddlywiki5.js","[[tiddlywikicore-]addsuffix<version>addsuffix[.js]]","text/plain"],

View File

@@ -27,8 +27,7 @@
"--setfield","[tag[external-image]] [tag[external-text]]","text","","text/plain",
"--rendertiddler","$:/core/save/all","index.html","text/plain"],
"empty": [
"--rendertiddler","$:/editions/es-ES/download-empty","empty.html","text/plain",
"--rendertiddler","$:/editions/es-ES/download-empty","empty.hta","text/plain"],
"--rendertiddler","$:/editions/es-ES/download-empty","empty.html","text/plain"],
"favicon": [
"--savetiddler","$:/favicon.ico","favicon.ico",
"--savetiddler","$:/green_favicon.ico","static/favicon.ico"],

View File

@@ -1,11 +1,13 @@
created: 20150220191009000
modified: 20150602092431500
title: $:/editions/tw5.com/railroad/macro-parameter-value
title: $:/editions/tw5.com/railroad/call-parameter-value
type: text/vnd.tiddlywiki.railroad
( '"""' [:{/'tout sauf """'/}] '"""'
| '"' [:{/'tout sauf "'/}] '"'
| "'" [:{/"tout sauf '"/}] "'"
| "[[" [:{/"tout sauf ]"/}] "]]"
| "`" [:{/"tout sauf `"/}] "`"
| "```" [:{/"tout sauf ```"/}] "```"
| {/"""tout sauf ' " ou espacevierge"""/}
)

View File

@@ -25,4 +25,4 @@ The <<.place param-nom>> is a sequence of letters (`A`--`Z`, `a`--`z`), digits (
The <<.place valeur>> is specified as follows<<dp>>
<$railroad text={{$:/editions/tw5.com/railroad/macro-parameter-value}}/>
<$railroad text={{$:/editions/tw5.com/railroad/call-parameter-value}}/>

View File

@@ -33,7 +33,7 @@ parametre.nom [: [:espace] ":" [:espace] defaut ]
La valeur par <<.place défaut>> d'un paramètre est spécifiée comme suit<<:>>
<$railroad text={{$:/editions/tw5.com/railroad/macro-parameter-value}}/>
<$railroad text={{$:/editions/tw5.com/railroad/call-parameter-value}}/>
La définition de la <<.place suite>> se fait comme suit<<:>>

View File

@@ -1,17 +0,0 @@
caption: savetiddlers
color: #4DB6AC
community-author: Buggyj
created: 20171109171935039
delivery: Browser Extension
description: Extension pour les navigateurs Chrome et Firefox
fr-title:
method: save
modified: 20220402105820520
tags: Chrome Firefox Saving [[Other Resources]] plugins
title: "savetiddlers" Extension for Chrome and Firefox by buggyj
type: text/vnd.tiddlywiki
url: https://github.com/buggyj/savetiddlers
Une extension pour Google Chrome et Mozilla Firefox qui fluidifie l'utilisation de [[l'enregistreur HTML5 par défaut|Saving with the HTML5 fallback saver]] de <<tw>>, et le rend presque aussi convivial que ~TiddlyFox une fois configurée.
https://github.com/buggyj/savetiddlers

View File

@@ -0,0 +1,17 @@
caption: savetiddlers
color: #4DB6AC
community-author: buggyj
created: 20171109171935039
delivery: Browser Extension
description: Extension pour les navigateur Firefox
fr-title:
method: save
modified: 20250809092435788
tags: Firefox Saving [[Other Resources]] plugins
title: savetiddlers: Extension for Firefox by buggyj
type: text/vnd.tiddlywiki
url: https://github.com/buggyj/savetiddlers
Une extension Mozilla Firefox qui fluidifie l'utilisation de [[l'enregistreur HTML5 par défaut|Saving with the HTML5 fallback saver]] de <<tw>>, et le rend presque aussi convivial que [[TiddlyFox]] une fois configurée.
{{!!url}}

View File

@@ -27,8 +27,7 @@
"--setfield","[tag[external-image]] [tag[external-text]]","text","","text/plain",
"--rendertiddler","$:/core/save/all","index.html","text/plain"],
"empty": [
"--rendertiddler","$:/editions/fr-FR/download-empty","empty.html","text/plain",
"--rendertiddler","$:/editions/fr-FR/download-empty","empty.hta","text/plain"],
"--rendertiddler","$:/editions/fr-FR/download-empty","empty.html","text/plain"],
"favicon": [
"--savetiddler","$:/favicon.ico","favicon.ico",
"--savetiddler","$:/green_favicon.ico","static/favicon.ico"],

View File

@@ -1,18 +0,0 @@
caption: savetiddlers
color: #4DB6AC
community-author: Buggyj
created: 20171109171935039
delivery: Browser Extension
description: ChromeとFirefoxのブラウザ拡張機能
method: save
modified: 20241014110546647
original-modified: 20210106151027189
tags: Chrome Firefox Saving [[Other Resources]] plugins
title: "savetiddlers" Extension for Chrome and Firefox by buggyj
ja-title: buggyjによるChromeとFirefoxの"savetiddlers"拡張機能
type: text/vnd.tiddlywiki
url: https://github.com/buggyj/savetiddlers
Google ChromeとMozilla Firefoxの拡張機能で、TiddlyWikiの組み込み[[HTML5セーバー|Saving with the HTML5 saver]]による使いにくさの一部を解消し、正しく設定すればTiddlyFoxとほぼ同じくらい簡単に使用できるようになります。
https://github.com/buggyj/savetiddlers

View File

@@ -0,0 +1,18 @@
caption: savetiddlers
color: #4DB6AC
community-author: buggyj
created: 20171109171935039
delivery: Browser Extension
description: Firefoxのブラウザ拡張機能
method: save
modified: 20250809092435788
original-modified: 20250809092435788
tags: Firefox Saving [[Other Resources]] plugins
title: savetiddlers: Extension for Firefox by buggyj
ja-title: buggyjによるFirefoxの"savetiddlers"拡張機能
type: text/vnd.tiddlywiki
url: https://github.com/buggyj/savetiddlers
Mozilla Firefoxの拡張機能で、TiddlyWikiの組み込み[[HTML5セーバー|Saving with the HTML5 saver]]による使いにくさの一部を解消し、正しく設定すれば[[TiddlyFox]]とほぼ同じくらい簡単に使用できるようになります。
{{!!url}}

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